diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js
index 8160939df9..9dfd11a537 100644
--- a/packages/builder/src/builderStore/store/frontend.js
+++ b/packages/builder/src/builderStore/store/frontend.js
@@ -61,6 +61,9 @@ const INITIAL_FRONTEND_STATE = {
showNotificationAction: false,
sidePanel: false,
},
+ features: {
+ componentValidation: false,
+ },
errors: [],
hasAppPackage: false,
libraries: null,
@@ -148,6 +151,10 @@ export const getFrontendStore = () => {
navigation: application.navigation || {},
usedPlugins: application.usedPlugins || [],
hasLock,
+ features: {
+ ...INITIAL_FRONTEND_STATE.features,
+ ...application.features,
+ },
initialised: true,
}))
screenHistoryStore.reset()
@@ -283,9 +290,12 @@ export const getFrontendStore = () => {
}
},
save: async screen => {
- // Validate screen structure
- // Temporarily disabled to accommodate migration issues
- // store.actions.screens.validate(screen)
+ const state = get(store)
+
+ // Validate screen structure if the app supports it
+ if (state.features?.componentValidation) {
+ store.actions.screens.validate(screen)
+ }
// Check screen definition for any component settings which need updated
store.actions.screens.enrichEmptySettings(screen)
@@ -296,7 +306,6 @@ export const getFrontendStore = () => {
const routesResponse = await API.fetchAppRoutes()
// If plugins changed we need to fetch the latest app metadata
- const state = get(store)
let usedPlugins = state.usedPlugins
if (savedScreen.pluginAdded) {
const { application } = await API.fetchAppPackage(state.appId)
diff --git a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte
index c7723f2bc0..e2dd1b4cc3 100644
--- a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte
+++ b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte
@@ -12,8 +12,10 @@
customQueryText,
} from "helpers/data/utils"
import IntegrationIcon from "./IntegrationIcon.svelte"
+ import { TableNames } from "constants"
let openDataSources = []
+
$: enrichedDataSources = enrichDatasources(
$datasources,
$params,
@@ -71,6 +73,13 @@
$goto(`./datasource/${datasource._id}`)
}
+ const selectTable = tableId => {
+ tables.select(tableId)
+ if (!$isActive("./table/:tableId")) {
+ $goto(`./table/${tableId}`)
+ }
+ }
+
function closeNode(datasource) {
openDataSources = openDataSources.filter(id => datasource._id !== id)
}
@@ -151,9 +160,16 @@
{#if $database?._id}
+
selectTable(TableNames.USERS)}
+ />
{#each enrichedDataSources as datasource, idx}
0}
+ border
text={datasource.name}
opened={datasource.open}
selected={$isActive("./datasource") && datasource.selected}
@@ -174,7 +190,7 @@
{#if datasource.open}
-
+
{#each $queries.list.filter(query => query.datasourceId === datasource._id) as query}
b.name?.toLowerCase() ? 1 : -1
export let sourceId
+ export let selectTable
$: sortedTables = $tables.list
- .filter(table => table.sourceId === sourceId)
+ .filter(
+ table => table.sourceId === sourceId && table._id !== TableNames.USERS
+ )
.sort(alphabetical)
-
- const selectTable = tableId => {
- tables.select(tableId)
- if (!$isActive("./table/:tableId")) {
- $goto(`./table/${tableId}`)
- }
- }
{#if $database?._id}
diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte
index c95988e90c..beb205fa81 100644
--- a/packages/builder/src/components/common/bindings/BindingPanel.svelte
+++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte
@@ -469,10 +469,12 @@
display: flex;
flex-direction: column;
gap: var(--spacing-xl);
+ overflow: hidden;
}
.overlay-wrap {
position: relative;
flex: 1;
+ overflow: hidden;
}
.mode-overlay {
position: absolute;
diff --git a/packages/builder/src/pages/builder/app/[application]/data/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/data/_layout.svelte
index c0813fd2b8..a2db33306c 100644
--- a/packages/builder/src/pages/builder/app/[application]/data/_layout.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/data/_layout.svelte
@@ -2,8 +2,17 @@
import { Button, Layout } from "@budibase/bbui"
import DatasourceNavigator from "components/backend/DatasourceNavigator/DatasourceNavigator.svelte"
import Panel from "components/design/Panel.svelte"
- import { isActive, goto } from "@roxi/routify"
+ import { isActive, goto, redirect } from "@roxi/routify"
import BetaButton from "./_components/BetaButton.svelte"
+ import { datasources } from "stores/backend"
+
+ $: {
+ // If we ever don't have any data other than the users table, prompt the
+ // user to add some
+ if (!$datasources.hasData) {
+ $redirect("./new")
+ }
+ }
diff --git a/packages/builder/src/pages/builder/app/[application]/data/datasource/bb_internal/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/datasource/bb_internal/index.svelte
index 33f3b626a2..a2ee0cf19b 100644
--- a/packages/builder/src/pages/builder/app/[application]/data/datasource/bb_internal/index.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/data/datasource/bb_internal/index.svelte
@@ -6,12 +6,15 @@
import { goto } from "@roxi/routify"
import { onMount } from "svelte"
import { BUDIBASE_INTERNAL_DB_ID } from "constants/backend"
+ import { TableNames } from "constants"
let modal
$: internalTablesBySourceId = $tables.list.filter(
table =>
- table.type !== "external" && table.sourceId === BUDIBASE_INTERNAL_DB_ID
+ table.type !== "external" &&
+ table.sourceId === BUDIBASE_INTERNAL_DB_ID &&
+ table._id !== TableNames.USERS
)
onMount(() => {
diff --git a/packages/builder/src/pages/builder/app/[application]/data/datasource/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/datasource/index.svelte
index d3b962a226..347e1138d4 100644
--- a/packages/builder/src/pages/builder/app/[application]/data/datasource/index.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/data/datasource/index.svelte
@@ -4,11 +4,13 @@
import { onMount } from "svelte"
onMount(async () => {
- const { list, selected } = $datasources
+ const { list, selected, hasData } = $datasources
if (selected) {
$redirect(`./${selected?._id}`)
- } else {
+ } else if (hasData && list?.length) {
$redirect(`./${list[0]._id}`)
+ } else {
+ $redirect("../new")
}
})
diff --git a/packages/builder/src/pages/builder/app/[application]/data/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/index.svelte
index 47939f09b4..9ccc9bffcf 100644
--- a/packages/builder/src/pages/builder/app/[application]/data/index.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/data/index.svelte
@@ -1,17 +1,13 @@
diff --git a/packages/builder/src/pages/builder/app/[application]/data/table/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/table/index.svelte
index 6f12ae38ac..f513d8ceb9 100644
--- a/packages/builder/src/pages/builder/app/[application]/data/table/index.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/data/table/index.svelte
@@ -1,14 +1,16 @@
diff --git a/packages/builder/src/stores/backend/datasources.js b/packages/builder/src/stores/backend/datasources.js
index 3d7c54a84c..4aca51b9dd 100644
--- a/packages/builder/src/stores/backend/datasources.js
+++ b/packages/builder/src/stores/backend/datasources.js
@@ -1,8 +1,13 @@
import { writable, derived, get } from "svelte/store"
-import { IntegrationTypes, DEFAULT_BB_DATASOURCE_ID } from "constants/backend"
-import { queries, tables } from "./"
+import {
+ IntegrationTypes,
+ DEFAULT_BB_DATASOURCE_ID,
+ BUDIBASE_INTERNAL_DB_ID,
+} from "constants/backend"
+import { tables, queries } from "./"
import { API } from "api"
import { DatasourceFeature } from "@budibase/types"
+import { TableNames } from "constants"
export class ImportTableError extends Error {
constructor(message) {
@@ -23,13 +28,40 @@ export function createDatasourcesStore() {
schemaError: null,
})
- const derivedStore = derived(store, $store => ({
- ...$store,
- selected: $store.list?.find(ds => ds._id === $store.selectedDatasourceId),
- hasDefaultData: $store.list.some(
- datasource => datasource._id === DEFAULT_BB_DATASOURCE_ID
- ),
- }))
+ const derivedStore = derived([store, tables], ([$store, $tables]) => {
+ // Set the internal datasource entities from the table list, which we're
+ // able to keep updated unlike the egress generated definition of the
+ // internal datasource
+ let internalDS = $store.list?.find(ds => ds._id === BUDIBASE_INTERNAL_DB_ID)
+ let otherDS = $store.list?.filter(ds => ds._id !== BUDIBASE_INTERNAL_DB_ID)
+ if (internalDS) {
+ internalDS = {
+ ...internalDS,
+ entities: $tables.list?.filter(table => {
+ return (
+ table.sourceId === BUDIBASE_INTERNAL_DB_ID &&
+ table._id !== TableNames.USERS
+ )
+ }),
+ }
+ }
+
+ // Build up enriched DS list
+ // Only add the internal DS if we have at least one non-users table
+ let list = []
+ if (internalDS?.entities?.length) {
+ list.push(internalDS)
+ }
+ list = list.concat(otherDS || [])
+
+ return {
+ ...$store,
+ list,
+ selected: list?.find(ds => ds._id === $store.selectedDatasourceId),
+ hasDefaultData: list?.some(ds => ds._id === DEFAULT_BB_DATASOURCE_ID),
+ hasData: list?.length > 0,
+ }
+ })
const fetch = async () => {
const datasources = await API.getDatasources()
@@ -50,20 +82,14 @@ export function createDatasourcesStore() {
const updateDatasource = response => {
const { datasource, error } = response
- store.update(state => {
- const currentIdx = state.list.findIndex(ds => ds._id === datasource._id)
- const sources = state.list
- if (currentIdx >= 0) {
- sources.splice(currentIdx, 1, datasource)
- } else {
- sources.push(datasource)
- }
- return {
- list: sources,
- selectedDatasourceId: datasource._id,
+ if (error) {
+ store.update(state => ({
+ ...state,
schemaError: error,
- }
- })
+ }))
+ }
+ replaceDatasource(datasource._id, datasource)
+ select(datasource._id)
return datasource
}
@@ -134,18 +160,14 @@ export function createDatasourcesStore() {
}
const deleteDatasource = async datasource => {
+ if (!datasource?._id || !datasource?._rev) {
+ return
+ }
await API.deleteDatasource({
- datasourceId: datasource?._id,
- datasourceRev: datasource?._rev,
+ datasourceId: datasource._id,
+ datasourceRev: datasource._rev,
})
- store.update(state => {
- const sources = state.list.filter(
- existing => existing._id !== datasource._id
- )
- return { list: sources, selected: null }
- })
- await queries.fetch()
- await tables.fetch()
+ replaceDatasource(datasource._id, null)
}
const removeSchemaError = () => {
@@ -154,7 +176,6 @@ export function createDatasourcesStore() {
})
}
- // Handles external updates of datasources
const replaceDatasource = (datasourceId, datasource) => {
if (!datasourceId) {
return
@@ -166,6 +187,8 @@ export function createDatasourcesStore() {
...state,
list: state.list.filter(x => x._id !== datasourceId),
}))
+ tables.removeDatasourceTables(datasourceId)
+ queries.removeDatasourceQueries(datasourceId)
return
}
diff --git a/packages/builder/src/stores/backend/queries.js b/packages/builder/src/stores/backend/queries.js
index be7bcb8c5b..1ec23b300c 100644
--- a/packages/builder/src/stores/backend/queries.js
+++ b/packages/builder/src/stores/backend/queries.js
@@ -121,6 +121,13 @@ export function createQueriesStore() {
return await save(datasourceId, newQuery)
}
+ const removeDatasourceQueries = datasourceId => {
+ store.update(state => ({
+ ...state,
+ list: state.list.filter(table => table.datasourceId !== datasourceId),
+ }))
+ }
+
return {
subscribe: derivedStore.subscribe,
fetch,
@@ -131,6 +138,7 @@ export function createQueriesStore() {
delete: deleteQuery,
preview,
duplicate,
+ removeDatasourceQueries,
}
}
diff --git a/packages/builder/src/stores/backend/tables.js b/packages/builder/src/stores/backend/tables.js
index d79ed6f072..201a67824d 100644
--- a/packages/builder/src/stores/backend/tables.js
+++ b/packages/builder/src/stores/backend/tables.js
@@ -67,12 +67,12 @@ export function createTablesStore() {
}
const deleteTable = async table => {
- if (!table?._id || !table?._rev) {
+ if (!table?._id) {
return
}
await API.deleteTable({
tableId: table._id,
- tableRev: table._rev,
+ tableRev: table._rev || "rev",
})
replaceTable(table._id, null)
}
@@ -161,6 +161,13 @@ export function createTablesStore() {
}
}
+ const removeDatasourceTables = datasourceId => {
+ store.update(state => ({
+ ...state,
+ list: state.list.filter(table => table.sourceId !== datasourceId),
+ }))
+ }
+
return {
...store,
subscribe: derivedStore.subscribe,
@@ -172,6 +179,7 @@ export function createTablesStore() {
saveField,
deleteField,
replaceTable,
+ removeDatasourceTables,
}
}
diff --git a/packages/client/manifest.json b/packages/client/manifest.json
index 355373c854..8e7d2bf21f 100644
--- a/packages/client/manifest.json
+++ b/packages/client/manifest.json
@@ -2216,7 +2216,7 @@
"name": "Form",
"icon": "Form",
"hasChildren": true,
- "illegalChildren": ["section", "form"],
+ "illegalChildren": ["section", "form", "formblock"],
"actions": [
"ValidateForm",
"ClearForm",
@@ -2304,7 +2304,7 @@
"name": "Form Step",
"icon": "AssetsAdded",
"hasChildren": true,
- "illegalChildren": ["section", "form", "form step"],
+ "illegalChildren": ["section", "form", "formstep", "formblock"],
"styles": ["size"],
"size": {
"width": 400,
diff --git a/packages/frontend-core/src/components/grid/layout/Grid.svelte b/packages/frontend-core/src/components/grid/layout/Grid.svelte
index e94cf5da93..5207f293b1 100644
--- a/packages/frontend-core/src/components/grid/layout/Grid.svelte
+++ b/packages/frontend-core/src/components/grid/layout/Grid.svelte
@@ -68,6 +68,7 @@
rowHeight,
contentLines,
gridFocused,
+ error,
} = context
// Keep config store up to date with props
@@ -149,8 +150,15 @@
+ {:else if $error}
+
+
There was a problem loading your grid
+
+ {$error}
+
+
{/if}
- {#if $loading}
+ {#if $loading && !$error}
@@ -273,6 +281,25 @@
opacity: 0.6;
}
+ /* Error */
+ .grid-error {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ gap: 8px;
+ }
+ .grid-error-title {
+ font-size: 18px;
+ font-weight: 600;
+ }
+ .grid-error-subtitle {
+ font-size: 16px;
+ }
+
/* Disable checkbox animation anywhere in the grid data */
.grid-data-outer :global(.spectrum-Checkbox-box:before),
.grid-data-outer :global(.spectrum-Checkbox-box:after),
diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js
index 1a98ef1848..d348d5caa5 100644
--- a/packages/frontend-core/src/components/grid/stores/rows.js
+++ b/packages/frontend-core/src/components/grid/stores/rows.js
@@ -14,6 +14,7 @@ export const createStores = () => {
const rowChangeCache = writable({})
const inProgressChanges = writable({})
const hasNextPage = writable(false)
+ const error = writable(null)
// Generate a lookup map to quick find a row by ID
const rowLookupMap = derived(
@@ -47,6 +48,7 @@ export const createStores = () => {
rowChangeCache,
inProgressChanges,
hasNextPage,
+ error,
}
}
@@ -68,6 +70,7 @@ export const deriveStores = context => {
inProgressChanges,
previousFocusedRowId,
hasNextPage,
+ error,
} = context
const instanceLoaded = writable(false)
const fetch = writable(null)
@@ -122,7 +125,17 @@ export const deriveStores = context => {
// Subscribe to changes of this fetch model
unsubscribe = newFetch.subscribe(async $fetch => {
- if ($fetch.loaded && !$fetch.loading) {
+ if ($fetch.error) {
+ // Present a helpful error to the user
+ let message = "An unknown error occurred"
+ if ($fetch.error.status === 403) {
+ message = "You don't have access to this data"
+ } else if ($fetch.error.message) {
+ message = $fetch.error.message
+ }
+ error.set(message)
+ } else if ($fetch.loaded && !$fetch.loading) {
+ error.set(null)
hasNextPage.set($fetch.hasNextPage)
const $instanceLoaded = get(instanceLoaded)
const resetRows = $fetch.resetKey !== lastResetKey
diff --git a/packages/frontend-core/src/fetch/DataFetch.js b/packages/frontend-core/src/fetch/DataFetch.js
index b456753d4a..f9d7fd2624 100644
--- a/packages/frontend-core/src/fetch/DataFetch.js
+++ b/packages/frontend-core/src/fetch/DataFetch.js
@@ -57,6 +57,7 @@ export default class DataFetch {
cursor: null,
cursors: [],
resetKey: Math.random(),
+ error: null,
})
// Merge options with their default values
@@ -252,6 +253,10 @@ export default class DataFetch {
try {
return await this.API.fetchTableDefinition(datasource.tableId)
} catch (error) {
+ this.store.update(state => ({
+ ...state,
+ error,
+ }))
return null
}
}
diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts
index cf7e8e122b..53453b8538 100644
--- a/packages/server/src/api/controllers/application.ts
+++ b/packages/server/src/api/controllers/application.ts
@@ -308,6 +308,9 @@ async function performAppCreate(ctx: UserCtx) {
customTheme: {
buttonBorderRadius: "16px",
},
+ features: {
+ componentValidation: true,
+ },
}
// If we used a template or imported an app there will be an existing doc.
diff --git a/packages/server/src/websockets/grid.ts b/packages/server/src/websockets/grid.ts
index 2a797623cf..ffe26828bc 100644
--- a/packages/server/src/websockets/grid.ts
+++ b/packages/server/src/websockets/grid.ts
@@ -1,12 +1,15 @@
import authorized from "../middleware/authorized"
+import currentApp from "../middleware/currentapp"
import { BaseSocket } from "./websocket"
-import { context, permissions } from "@budibase/backend-core"
+import { auth, permissions } from "@budibase/backend-core"
import http from "http"
import Koa from "koa"
import { getTableId } from "../api/controllers/row/utils"
import { Row, Table } from "@budibase/types"
import { Socket } from "socket.io"
import { GridSocketEvent } from "@budibase/shared-core"
+import { userAgent } from "koa-useragent"
+import { createContext, runMiddlewares } from "./middleware"
const { PermissionType, PermissionLevel } = permissions
@@ -26,28 +29,27 @@ export default class GridSocket extends BaseSocket {
return
}
- // Check if the user has permission to read this resource
- const middleware = authorized(
- PermissionType.TABLE,
- PermissionLevel.READ
- )
- const ctx = {
- appId,
+ // Create context
+ const ctx = createContext(this.app, socket, {
resourceId: tableId,
- roleId: socket.data.roleId,
- user: { _id: socket.data._id },
- isAuthenticated: socket.data.isAuthenticated,
- request: {
- url: "/fake",
- },
- get: () => null,
- throw: () => {
- // If they don't have access, immediately disconnect them
- socket.disconnect(true)
- },
- }
- await context.doInAppContext(appId, async () => {
- await middleware(ctx, async () => {
+ appId,
+ })
+
+ // Construct full middleware chain to assess permissions
+ const middlewares = [
+ userAgent,
+ auth.buildAuthMiddleware([], {
+ publicAllowed: true,
+ }),
+ currentApp,
+ authorized(PermissionType.TABLE, PermissionLevel.READ),
+ ]
+
+ // Run all koa middlewares
+ try {
+ await runMiddlewares(ctx, middlewares, async () => {
+ // Middlewares are finished and we have permission
+ // Join room for this resource
const room = `${appId}-${tableId}`
await this.joinRoom(socket, room)
@@ -55,7 +57,9 @@ export default class GridSocket extends BaseSocket {
const sessions = await this.getRoomSessions(room)
callback({ users: sessions })
})
- })
+ } catch (error) {
+ socket.disconnect(true)
+ }
}
)
diff --git a/packages/server/src/websockets/middleware.ts b/packages/server/src/websockets/middleware.ts
new file mode 100644
index 0000000000..0a52dcbdeb
--- /dev/null
+++ b/packages/server/src/websockets/middleware.ts
@@ -0,0 +1,85 @@
+import { Socket } from "socket.io"
+import Cookies from "cookies"
+import http from "http"
+import Koa from "koa"
+import { Header } from "@budibase/backend-core"
+
+/**
+ * Constructs a fake Koa context to use for manually running middlewares in
+ * sockets
+ * @param app the Koa app
+ * @param socket the socket.io socket instance
+ * @param options additional metadata to populate the context with
+ */
+export const createContext = (
+ app: Koa,
+ socket: Socket,
+ options?: WebsocketContextOptions
+) => {
+ const res = new http.ServerResponse(socket.request)
+ const context: WebsocketContext = {
+ ...app.createContext(socket.request, res),
+
+ // Additional overrides needed to make our middlewares work with this
+ // fake koa context
+ resourceId: options?.resourceId,
+ path: "/fake",
+ request: {
+ url: "/fake",
+ headers: {
+ [Header.APP_ID]: options?.appId,
+ },
+ },
+ cookies: new Cookies(socket.request, res),
+ get: (field: string) => socket.request.headers?.[field] as string,
+ throw: (...params: any[]) => {
+ // Throw has a bunch of different signatures, so we'll just stringify
+ // whatever params we get given
+ throw new Error(
+ ...(params?.join(" ") || "Unknown error in socket middleware")
+ )
+ },
+
+ // Needed for koa-useragent middleware
+ headers: socket.request.headers,
+ header: socket.request.headers,
+ }
+ return context
+}
+
+/**
+ * Runs a list of middlewares, nesting each callback inside each other to mimic
+ * how the real middlewares run and ensuring that app and tenant contexts work
+ * as expected
+ * @param ctx the Koa context
+ * @param middlewares the array of middlewares to run
+ * @param callback a final callback for when all middlewares are completed
+ */
+export const runMiddlewares = async (
+ ctx: any,
+ middlewares: any[],
+ callback: Function
+) => {
+ if (!middlewares[0]) {
+ await callback()
+ } else {
+ await middlewares[0](ctx, async () => {
+ await runMiddlewares(ctx, middlewares.slice(1), callback)
+ })
+ }
+}
+
+export interface WebsocketContext extends Omit {
+ request: {
+ url: string
+ headers: {
+ [key: string]: string | undefined
+ }
+ }
+ cookies: Cookies
+}
+
+export interface WebsocketContextOptions {
+ appId?: string
+ resourceId?: string
+}
diff --git a/packages/server/src/websockets/websocket.ts b/packages/server/src/websockets/websocket.ts
index 52351aea36..24cac3c37d 100644
--- a/packages/server/src/websockets/websocket.ts
+++ b/packages/server/src/websockets/websocket.ts
@@ -1,7 +1,6 @@
import { Server } from "socket.io"
import http from "http"
import Koa from "koa"
-import Cookies from "cookies"
import { userAgent } from "koa-useragent"
import { auth, Header, redis } from "@budibase/backend-core"
import { createAdapter } from "@socket.io/redis-adapter"
@@ -10,6 +9,7 @@ import { getSocketPubSubClients } from "../utilities/redis"
import { SocketEvent, SocketSessionTTL } from "@budibase/shared-core"
import { SocketSession } from "@budibase/types"
import { v4 as uuid } from "uuid"
+import { createContext, runMiddlewares } from "./middleware"
const anonUser = () => ({
_id: uuid(),
@@ -18,6 +18,7 @@ const anonUser = () => ({
})
export class BaseSocket {
+ app: Koa
io: Server
path: string
redisClient?: redis.Client
@@ -28,6 +29,7 @@ export class BaseSocket {
path: string = "/",
additionalMiddlewares?: any[]
) {
+ this.app = app
this.path = path
this.io = new Server(server, {
path,
@@ -45,52 +47,25 @@ export class BaseSocket {
// Apply middlewares
this.io.use(async (socket, next) => {
- // Build fake koa context
- const res = new http.ServerResponse(socket.request)
- const ctx: any = {
- ...app.createContext(socket.request, res),
+ const ctx = createContext(this.app, socket)
- // Additional overrides needed to make our middlewares work with this
- // fake koa context
- cookies: new Cookies(socket.request, res),
- get: (field: string) => socket.request.headers[field],
- throw: (code: number, message: string) => {
- throw new Error(message)
- },
-
- // Needed for koa-useragent middleware
- headers: socket.request.headers,
- header: socket.request.headers,
-
- // We don't really care about the path since it will never contain
- // an app ID
- path: "/socket",
- }
-
- // Run all koa middlewares
try {
- for (let [idx, middleware] of middlewares.entries()) {
- await middleware(ctx, () => {
- if (idx === middlewares.length - 1) {
- // Middlewares are finished
- // Extract some data from our enriched koa context to persist
- // as metadata for the socket
- const user = ctx.user?._id ? ctx.user : anonUser()
- const { _id, email, firstName, lastName } = user
- socket.data = {
- _id,
- email,
- firstName,
- lastName,
- sessionId: socket.id,
- connectedAt: Date.now(),
- isAuthenticated: ctx.isAuthenticated,
- roleId: ctx.roleId,
- }
- next()
- }
- })
- }
+ await runMiddlewares(ctx, middlewares, () => {
+ // Middlewares are finished
+ // Extract some data from our enriched koa context to persist
+ // as metadata for the socket
+ const user = ctx.user?._id ? ctx.user : anonUser()
+ const { _id, email, firstName, lastName } = user
+ socket.data = {
+ _id,
+ email,
+ firstName,
+ lastName,
+ sessionId: socket.id,
+ connectedAt: Date.now(),
+ }
+ next()
+ })
} catch (error: any) {
next(error)
}
diff --git a/packages/types/src/documents/app/app.ts b/packages/types/src/documents/app/app.ts
index c91d575714..258eaef297 100644
--- a/packages/types/src/documents/app/app.ts
+++ b/packages/types/src/documents/app/app.ts
@@ -20,6 +20,7 @@ export interface App extends Document {
navigation?: AppNavigation
automationErrors?: AppMetadataErrors
icon?: AppIcon
+ features?: AppFeatures
}
export interface AppInstance {
@@ -60,3 +61,7 @@ export interface AppIcon {
name: string
color: string
}
+
+export interface AppFeatures {
+ componentValidation?: boolean
+}