@budibase/server 2.6.19-alpha.26 → 2.6.19-alpha.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@budibase/server",
3
3
  "email": "hi@budibase.com",
4
- "version": "2.6.19-alpha.26",
4
+ "version": "2.6.19-alpha.27",
5
5
  "description": "Budibase Web Server",
6
6
  "main": "src/index.ts",
7
7
  "repository": {
@@ -46,12 +46,12 @@
46
46
  "license": "GPL-3.0",
47
47
  "dependencies": {
48
48
  "@apidevtools/swagger-parser": "10.0.3",
49
- "@budibase/backend-core": "2.6.19-alpha.26",
50
- "@budibase/client": "2.6.19-alpha.26",
51
- "@budibase/pro": "2.6.19-alpha.26",
52
- "@budibase/shared-core": "2.6.19-alpha.26",
53
- "@budibase/string-templates": "2.6.19-alpha.26",
54
- "@budibase/types": "2.6.19-alpha.26",
49
+ "@budibase/backend-core": "2.6.19-alpha.27",
50
+ "@budibase/client": "2.6.19-alpha.27",
51
+ "@budibase/pro": "2.6.19-alpha.27",
52
+ "@budibase/shared-core": "2.6.19-alpha.27",
53
+ "@budibase/string-templates": "2.6.19-alpha.27",
54
+ "@budibase/types": "2.6.19-alpha.27",
55
55
  "@bull-board/api": "3.7.0",
56
56
  "@bull-board/koa": "3.9.4",
57
57
  "@elastic/elasticsearch": "7.10.0",
@@ -59,6 +59,7 @@
59
59
  "@koa/router": "8.0.8",
60
60
  "@sendgrid/mail": "7.1.1",
61
61
  "@sentry/node": "6.17.7",
62
+ "@socket.io/redis-adapter": "^8.2.1",
62
63
  "airtable": "0.10.1",
63
64
  "arangojs": "7.2.0",
64
65
  "aws-sdk": "2.1030.0",
@@ -194,5 +195,5 @@
194
195
  }
195
196
  }
196
197
  },
197
- "gitHead": "dbeebc571d598a9a6696dbc15e9b1e38e66a80a3"
198
+ "gitHead": "84334ce650f8782d49ca9c7b5a7186e5232ad91a"
198
199
  }
@@ -29,7 +29,7 @@ import { USERS_TABLE_SCHEMA } from "../../constants"
29
29
  import { buildDefaultDocs } from "../../db/defaultData/datasource_bb_default"
30
30
  import { removeAppFromUserRoles } from "../../utilities/workerRequests"
31
31
  import { stringToReadStream, isQsTrue } from "../../utilities"
32
- import { getLocksById } from "../../utilities/redis"
32
+ import { getLocksById, doesUserHaveLock } from "../../utilities/redis"
33
33
  import {
34
34
  updateClientLibrary,
35
35
  backupClientLibrary,
@@ -227,6 +227,7 @@ export async function fetchAppPackage(ctx: UserCtx) {
227
227
  screens,
228
228
  layouts,
229
229
  clientLibPath,
230
+ hasLock: await doesUserHaveLock(application.appId, ctx.user),
230
231
  }
231
232
  }
232
233
 
@@ -26,6 +26,7 @@ import {
26
26
  DatasourcePlus,
27
27
  } from "@budibase/types"
28
28
  import sdk from "../../sdk"
29
+ import { builderSocket } from "../../websockets"
29
30
 
30
31
  function getErrorTables(errors: any, errorType: string) {
31
32
  return Object.entries(errors)
@@ -296,6 +297,7 @@ export async function update(ctx: UserCtx<any, UpdateDatasourceResponse>) {
296
297
  ctx.body = {
297
298
  datasource: await sdk.datasources.removeSecretSingle(datasource),
298
299
  }
300
+ builderSocket.emitDatasourceUpdate(ctx, datasource)
299
301
  }
300
302
 
301
303
  export async function save(
@@ -338,6 +340,7 @@ export async function save(
338
340
  response.error = schemaError
339
341
  }
340
342
  ctx.body = response
343
+ builderSocket.emitDatasourceUpdate(ctx, datasource)
341
344
  }
342
345
 
343
346
  async function destroyInternalTablesBySourceId(datasourceId: string) {
@@ -397,6 +400,7 @@ export async function destroy(ctx: UserCtx) {
397
400
 
398
401
  ctx.message = `Datasource deleted.`
399
402
  ctx.status = 200
403
+ builderSocket.emitDatasourceDeletion(ctx, datasourceId)
400
404
  }
401
405
 
402
406
  export async function find(ctx: UserCtx) {
@@ -4,6 +4,7 @@ import * as external from "./external"
4
4
  import { isExternalTable } from "../../../integrations/utils"
5
5
  import { Ctx } from "@budibase/types"
6
6
  import * as utils from "./utils"
7
+ import { gridSocket } from "../../../websockets"
7
8
 
8
9
  function pickApi(tableId: any) {
9
10
  if (isExternalTable(tableId)) {
@@ -12,21 +13,9 @@ function pickApi(tableId: any) {
12
13
  return internal
13
14
  }
14
15
 
15
- function getTableId(ctx: any) {
16
- if (ctx.request.body && ctx.request.body.tableId) {
17
- return ctx.request.body.tableId
18
- }
19
- if (ctx.params && ctx.params.tableId) {
20
- return ctx.params.tableId
21
- }
22
- if (ctx.params && ctx.params.viewName) {
23
- return ctx.params.viewName
24
- }
25
- }
26
-
27
16
  export async function patch(ctx: any): Promise<any> {
28
17
  const appId = ctx.appId
29
- const tableId = getTableId(ctx)
18
+ const tableId = utils.getTableId(ctx)
30
19
  const body = ctx.request.body
31
20
  // if it doesn't have an _id then its save
32
21
  if (body && !body._id) {
@@ -47,6 +36,7 @@ export async function patch(ctx: any): Promise<any> {
47
36
  ctx.eventEmitter.emitRow(`row:update`, appId, row, table)
48
37
  ctx.message = `${table.name} updated successfully.`
49
38
  ctx.body = row
39
+ gridSocket?.emitRowUpdate(ctx, row)
50
40
  } catch (err) {
51
41
  ctx.throw(400, err)
52
42
  }
@@ -54,7 +44,7 @@ export async function patch(ctx: any): Promise<any> {
54
44
 
55
45
  export const save = async (ctx: any) => {
56
46
  const appId = ctx.appId
57
- const tableId = getTableId(ctx)
47
+ const tableId = utils.getTableId(ctx)
58
48
  const body = ctx.request.body
59
49
  // if it has an ID already then its a patch
60
50
  if (body && body._id) {
@@ -69,23 +59,24 @@ export const save = async (ctx: any) => {
69
59
  ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:save`, appId, row, table)
70
60
  ctx.message = `${table.name} saved successfully`
71
61
  ctx.body = row
62
+ gridSocket?.emitRowUpdate(ctx, row)
72
63
  }
73
64
  export async function fetchView(ctx: any) {
74
- const tableId = getTableId(ctx)
65
+ const tableId = utils.getTableId(ctx)
75
66
  ctx.body = await quotas.addQuery(() => pickApi(tableId).fetchView(ctx), {
76
67
  datasourceId: tableId,
77
68
  })
78
69
  }
79
70
 
80
71
  export async function fetch(ctx: any) {
81
- const tableId = getTableId(ctx)
72
+ const tableId = utils.getTableId(ctx)
82
73
  ctx.body = await quotas.addQuery(() => pickApi(tableId).fetch(ctx), {
83
74
  datasourceId: tableId,
84
75
  })
85
76
  }
86
77
 
87
78
  export async function find(ctx: any) {
88
- const tableId = getTableId(ctx)
79
+ const tableId = utils.getTableId(ctx)
89
80
  ctx.body = await quotas.addQuery(() => pickApi(tableId).find(ctx), {
90
81
  datasourceId: tableId,
91
82
  })
@@ -94,7 +85,7 @@ export async function find(ctx: any) {
94
85
  export async function destroy(ctx: any) {
95
86
  const appId = ctx.appId
96
87
  const inputs = ctx.request.body
97
- const tableId = getTableId(ctx)
88
+ const tableId = utils.getTableId(ctx)
98
89
  let response, row
99
90
  if (inputs.rows) {
100
91
  let { rows } = await quotas.addQuery(
@@ -107,6 +98,7 @@ export async function destroy(ctx: any) {
107
98
  response = rows
108
99
  for (let row of rows) {
109
100
  ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:delete`, appId, row)
101
+ gridSocket?.emitRowDeletion(ctx, row._id)
110
102
  }
111
103
  } else {
112
104
  let resp = await quotas.addQuery(() => pickApi(tableId).destroy(ctx), {
@@ -116,6 +108,7 @@ export async function destroy(ctx: any) {
116
108
  response = resp.response
117
109
  row = resp.row
118
110
  ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:delete`, appId, row)
111
+ gridSocket?.emitRowDeletion(ctx, row._id)
119
112
  }
120
113
  ctx.status = 200
121
114
  // for automations include the row that was deleted
@@ -124,7 +117,7 @@ export async function destroy(ctx: any) {
124
117
  }
125
118
 
126
119
  export async function search(ctx: any) {
127
- const tableId = getTableId(ctx)
120
+ const tableId = utils.getTableId(ctx)
128
121
  ctx.status = 200
129
122
  ctx.body = await quotas.addQuery(() => pickApi(tableId).search(ctx), {
130
123
  datasourceId: tableId,
@@ -132,7 +125,7 @@ export async function search(ctx: any) {
132
125
  }
133
126
 
134
127
  export async function validate(ctx: Ctx) {
135
- const tableId = getTableId(ctx)
128
+ const tableId = utils.getTableId(ctx)
136
129
  // external tables are hard to validate currently
137
130
  if (isExternalTable(tableId)) {
138
131
  ctx.body = { valid: true }
@@ -145,7 +138,7 @@ export async function validate(ctx: Ctx) {
145
138
  }
146
139
 
147
140
  export async function fetchEnrichedRow(ctx: any) {
148
- const tableId = getTableId(ctx)
141
+ const tableId = utils.getTableId(ctx)
149
142
  ctx.body = await quotas.addQuery(
150
143
  () => pickApi(tableId).fetchEnrichedRow(ctx),
151
144
  {
@@ -155,7 +148,7 @@ export async function fetchEnrichedRow(ctx: any) {
155
148
  }
156
149
 
157
150
  export const exportRows = async (ctx: any) => {
158
- const tableId = getTableId(ctx)
151
+ const tableId = utils.getTableId(ctx)
159
152
  ctx.body = await quotas.addQuery(() => pickApi(tableId).exportRows(ctx), {
160
153
  datasourceId: tableId,
161
154
  })
@@ -154,3 +154,15 @@ export function cleanExportRows(
154
154
 
155
155
  return cleanRows
156
156
  }
157
+
158
+ export function getTableId(ctx: any) {
159
+ if (ctx.request.body && ctx.request.body.tableId) {
160
+ return ctx.request.body.tableId
161
+ }
162
+ if (ctx.params && ctx.params.tableId) {
163
+ return ctx.params.tableId
164
+ }
165
+ if (ctx.params && ctx.params.viewName) {
166
+ return ctx.params.viewName
167
+ }
168
+ }
@@ -11,6 +11,7 @@ import { context, events } from "@budibase/backend-core"
11
11
  import { Table, UserCtx } from "@budibase/types"
12
12
  import sdk from "../../../sdk"
13
13
  import { jsonFromCsvString } from "../../../utilities/csv"
14
+ import { builderSocket } from "../../../websockets"
14
15
 
15
16
  function pickApi({ tableId, table }: { tableId?: string; table?: Table }) {
16
17
  if (table && !tableId) {
@@ -77,6 +78,7 @@ export async function save(ctx: UserCtx) {
77
78
  ctx.eventEmitter &&
78
79
  ctx.eventEmitter.emitTable(`table:save`, appId, savedTable)
79
80
  ctx.body = savedTable
81
+ builderSocket.emitTableUpdate(ctx, savedTable)
80
82
  }
81
83
 
82
84
  export async function destroy(ctx: UserCtx) {
@@ -89,6 +91,7 @@ export async function destroy(ctx: UserCtx) {
89
91
  ctx.status = 200
90
92
  ctx.table = deletedTable
91
93
  ctx.body = { message: `Table ${tableId} deleted.` }
94
+ builderSocket.emitTableDeletion(ctx, tableId)
92
95
  }
93
96
 
94
97
  export async function bulkImport(ctx: UserCtx) {
@@ -16,6 +16,7 @@ import {
16
16
  View,
17
17
  } from "@budibase/types"
18
18
  import { cleanExportRows } from "../row/utils"
19
+ import { builderSocket } from "../../../websockets"
19
20
 
20
21
  const { cloneDeep, isEqual } = require("lodash")
21
22
 
@@ -48,7 +49,7 @@ export async function save(ctx: Ctx) {
48
49
  if (!view.meta.schema) {
49
50
  view.meta.schema = table.schema
50
51
  }
51
- table.views[viewName] = view.meta
52
+ table.views[viewName] = { ...view.meta, name: viewName }
52
53
  if (originalName) {
53
54
  delete table.views[originalName]
54
55
  existingTable.views[viewName] = existingTable.views[originalName]
@@ -56,10 +57,8 @@ export async function save(ctx: Ctx) {
56
57
  await db.put(table)
57
58
  await handleViewEvents(existingTable.views[viewName], table.views[viewName])
58
59
 
59
- ctx.body = {
60
- ...table.views[viewToSave.name],
61
- name: viewToSave.name,
62
- }
60
+ ctx.body = table.views[viewName]
61
+ builderSocket.emitTableUpdate(ctx, table)
63
62
  }
64
63
 
65
64
  export async function calculationEvents(existingView: View, newView: View) {
@@ -128,6 +127,7 @@ export async function destroy(ctx: Ctx) {
128
127
  await events.view.deleted(view)
129
128
 
130
129
  ctx.body = view
130
+ builderSocket.emitTableUpdate(ctx, table)
131
131
  }
132
132
 
133
133
  export async function exportView(ctx: Ctx) {
package/src/app.ts CHANGED
@@ -61,7 +61,6 @@ if (env.isProd()) {
61
61
 
62
62
  const server = http.createServer(app.callback())
63
63
  destroyable(server)
64
- initialiseWebsockets(app, server)
65
64
 
66
65
  let shuttingDown = false,
67
66
  errCode = 0
@@ -35,12 +35,11 @@ async function checkDevAppLocks(ctx: BBContext) {
35
35
  if (!appId || !appId.startsWith(APP_DEV_PREFIX)) {
36
36
  return
37
37
  }
38
- if (!(await doesUserHaveLock(appId, ctx.user))) {
39
- ctx.throw(400, "User does not hold app lock.")
40
- }
41
38
 
42
- // they do have lock, update it
43
- await updateLock(appId, ctx.user)
39
+ // If this user already owns the lock, then update it
40
+ if (await doesUserHaveLock(appId, ctx.user)) {
41
+ await updateLock(appId, ctx.user)
42
+ }
44
43
  }
45
44
 
46
45
  async function updateAppUpdatedAt(ctx: BBContext) {
package/src/startup.ts CHANGED
@@ -16,6 +16,7 @@ import * as bullboard from "./automations/bullboard"
16
16
  import * as pro from "@budibase/pro"
17
17
  import * as api from "./api"
18
18
  import sdk from "./sdk"
19
+ import { initialise as initialiseWebsockets } from "./websockets"
19
20
 
20
21
  let STARTUP_RAN = false
21
22
 
@@ -64,6 +65,7 @@ export async function startup(app?: any, server?: any) {
64
65
  fileSystem.init()
65
66
  await redis.init()
66
67
  eventInit()
68
+ initialiseWebsockets(app, server)
67
69
 
68
70
  // run migrations on startup if not done via http
69
71
  // not recommended in a clustered environment
@@ -4,23 +4,33 @@ import { ContextUser } from "@budibase/types"
4
4
 
5
5
  const APP_DEV_LOCK_SECONDS = 600
6
6
  const AUTOMATION_TEST_FLAG_SECONDS = 60
7
- let devAppClient: any, debounceClient: any, flagClient: any
7
+ let devAppClient: any, debounceClient: any, flagClient: any, socketClient: any
8
8
 
9
- // we init this as we want to keep the connection open all the time
9
+ // We need to maintain a duplicate client for socket.io pub/sub
10
+ let socketSubClient: any
11
+
12
+ // We init this as we want to keep the connection open all the time
10
13
  // reduces the performance hit
11
14
  export async function init() {
12
15
  devAppClient = new redis.Client(redis.utils.Databases.DEV_LOCKS)
13
16
  debounceClient = new redis.Client(redis.utils.Databases.DEBOUNCE)
14
17
  flagClient = new redis.Client(redis.utils.Databases.FLAGS)
18
+ socketClient = new redis.Client(redis.utils.Databases.SOCKET_IO)
15
19
  await devAppClient.init()
16
20
  await debounceClient.init()
17
21
  await flagClient.init()
22
+ await socketClient.init()
23
+
24
+ // Duplicate the socket client for pub/sub
25
+ socketSubClient = socketClient.getClient().duplicate()
18
26
  }
19
27
 
20
28
  export async function shutdown() {
21
29
  if (devAppClient) await devAppClient.finish()
22
30
  if (debounceClient) await debounceClient.finish()
23
31
  if (flagClient) await flagClient.finish()
32
+ if (socketClient) await socketClient.finish()
33
+ if (socketSubClient) socketSubClient.disconnect()
24
34
  // shutdown core clients
25
35
  await redis.clients.shutdown()
26
36
  console.log("Redis shutdown")
@@ -86,3 +96,10 @@ export async function checkTestFlag(id: string) {
86
96
  export async function clearTestFlag(id: string) {
87
97
  await devAppClient.delete(id)
88
98
  }
99
+
100
+ export function getSocketPubSubClients() {
101
+ return {
102
+ pub: socketClient.getClient(),
103
+ sub: socketSubClient,
104
+ }
105
+ }
@@ -0,0 +1,69 @@
1
+ import authorized from "../middleware/authorized"
2
+ import Socket from "./websocket"
3
+ import { permissions } from "@budibase/backend-core"
4
+ import http from "http"
5
+ import Koa from "koa"
6
+ import { Datasource, Table } from "@budibase/types"
7
+ import { gridSocket } from "./index"
8
+ import { clearLock } from "../utilities/redis"
9
+
10
+ export default class BuilderSocket extends Socket {
11
+ constructor(app: Koa, server: http.Server) {
12
+ super(app, server, "/socket/builder", [authorized(permissions.BUILDER)])
13
+
14
+ this.io.on("connection", socket => {
15
+ // Join a room for this app
16
+ const user = socket.data.user
17
+ const appId = socket.data.appId
18
+ socket.join(appId)
19
+ socket.to(appId).emit("user-update", user)
20
+
21
+ // Initial identification of connected spreadsheet
22
+ socket.on("get-users", async (payload, callback) => {
23
+ const sockets = await this.io.in(appId).fetchSockets()
24
+ callback({
25
+ users: sockets.map(socket => socket.data.user),
26
+ })
27
+ })
28
+
29
+ // Disconnection cleanup
30
+ socket.on("disconnect", async () => {
31
+ socket.to(appId).emit("user-disconnect", user)
32
+
33
+ // Remove app lock from this user if they have no other connections
34
+ try {
35
+ const sockets = await this.io.in(appId).fetchSockets()
36
+ const hasOtherConnection = sockets.some(socket => {
37
+ const { _id, sessionId } = socket.data.user
38
+ return _id === user._id && sessionId !== user.sessionId
39
+ })
40
+ if (!hasOtherConnection) {
41
+ await clearLock(appId, user)
42
+ }
43
+ } catch (e) {
44
+ // This is fine, just means this user didn't hold the lock
45
+ }
46
+ })
47
+ })
48
+ }
49
+
50
+ emitTableUpdate(ctx: any, table: Table) {
51
+ this.io.in(ctx.appId).emit("table-change", { id: table._id, table })
52
+ gridSocket.emitTableUpdate(table)
53
+ }
54
+
55
+ emitTableDeletion(ctx: any, id: string) {
56
+ this.io.in(ctx.appId).emit("table-change", { id, table: null })
57
+ gridSocket.emitTableDeletion(id)
58
+ }
59
+
60
+ emitDatasourceUpdate(ctx: any, datasource: Datasource) {
61
+ this.io
62
+ .in(ctx.appId)
63
+ .emit("datasource-change", { id: datasource._id, datasource })
64
+ }
65
+
66
+ emitDatasourceDeletion(ctx: any, id: string) {
67
+ this.io.in(ctx.appId).emit("datasource-change", { id, datasource: null })
68
+ }
69
+ }
@@ -3,6 +3,8 @@ import Socket from "./websocket"
3
3
  import { permissions } from "@budibase/backend-core"
4
4
  import http from "http"
5
5
  import Koa from "koa"
6
+ import { getTableId } from "../api/controllers/row/utils"
7
+ import { Row, Table } from "@budibase/types"
6
8
 
7
9
  export default class GridSocket extends Socket {
8
10
  constructor(app: Koa, server: http.Server) {
@@ -10,7 +12,6 @@ export default class GridSocket extends Socket {
10
12
 
11
13
  this.io.on("connection", socket => {
12
14
  const user = socket.data.user
13
- console.log(`Spreadsheet user connected: ${user?.id}`)
14
15
 
15
16
  // Socket state
16
17
  let currentRoom: string
@@ -19,37 +20,54 @@ export default class GridSocket extends Socket {
19
20
  socket.on("select-table", async (tableId, callback) => {
20
21
  // Leave current room
21
22
  if (currentRoom) {
22
- socket.to(currentRoom).emit("user-disconnect", socket.data.user)
23
+ socket.to(currentRoom).emit("user-disconnect", user)
23
24
  socket.leave(currentRoom)
24
25
  }
25
26
 
26
27
  // Join new room
27
28
  currentRoom = tableId
28
29
  socket.join(currentRoom)
29
- socket.to(currentRoom).emit("user-update", socket.data.user)
30
+ socket.to(currentRoom).emit("user-update", user)
30
31
 
31
32
  // Reply with all users in current room
32
33
  const sockets = await this.io.in(currentRoom).fetchSockets()
33
34
  callback({
34
35
  users: sockets.map(socket => socket.data.user),
35
- id: user.id,
36
36
  })
37
37
  })
38
38
 
39
39
  // Handle users selecting a new cell
40
40
  socket.on("select-cell", cellId => {
41
- socket.data.user.selectedCellId = cellId
41
+ socket.data.user.focusedCellId = cellId
42
42
  if (currentRoom) {
43
- socket.to(currentRoom).emit("user-update", socket.data.user)
43
+ socket.to(currentRoom).emit("user-update", user)
44
44
  }
45
45
  })
46
46
 
47
47
  // Disconnection cleanup
48
48
  socket.on("disconnect", () => {
49
49
  if (currentRoom) {
50
- socket.to(currentRoom).emit("user-disconnect", socket.data.user)
50
+ socket.to(currentRoom).emit("user-disconnect", user)
51
51
  }
52
52
  })
53
53
  })
54
54
  }
55
+
56
+ emitRowUpdate(ctx: any, row: Row) {
57
+ const tableId = getTableId(ctx)
58
+ this.io.in(tableId).emit("row-change", { id: row._id, row })
59
+ }
60
+
61
+ emitRowDeletion(ctx: any, id: string) {
62
+ const tableId = getTableId(ctx)
63
+ this.io.in(tableId).emit("row-change", { id, row: null })
64
+ }
65
+
66
+ emitTableUpdate(table: Table) {
67
+ this.io.in(table._id!).emit("table-change", { id: table._id, table })
68
+ }
69
+
70
+ emitTableDeletion(id: string) {
71
+ this.io.in(id).emit("table-change", { id, table: null })
72
+ }
55
73
  }
@@ -1,14 +1,17 @@
1
1
  import http from "http"
2
2
  import Koa from "koa"
3
- import GridSocket from "./grid"
4
3
  import ClientAppSocket from "./client"
4
+ import GridSocket from "./grid"
5
+ import BuilderSocket from "./builder"
5
6
 
6
7
  let clientAppSocket: ClientAppSocket
7
8
  let gridSocket: GridSocket
9
+ let builderSocket: BuilderSocket
8
10
 
9
11
  export const initialise = (app: Koa, server: http.Server) => {
10
12
  clientAppSocket = new ClientAppSocket(app, server)
11
13
  gridSocket = new GridSocket(app, server)
14
+ builderSocket = new BuilderSocket(app, server)
12
15
  }
13
16
 
14
- export { clientAppSocket, gridSocket }
17
+ export { clientAppSocket, gridSocket, builderSocket }
@@ -5,6 +5,9 @@ import Cookies from "cookies"
5
5
  import { userAgent } from "koa-useragent"
6
6
  import { auth } from "@budibase/backend-core"
7
7
  import currentApp from "../middleware/currentapp"
8
+ import { createAdapter } from "@socket.io/redis-adapter"
9
+ import { getSocketPubSubClients } from "../utilities/redis"
10
+ import uuid from "uuid"
8
11
 
9
12
  export default class Socket {
10
13
  io: Server
@@ -12,7 +15,7 @@ export default class Socket {
12
15
  constructor(
13
16
  app: Koa,
14
17
  server: http.Server,
15
- path: string,
18
+ path: string = "/",
16
19
  additionalMiddlewares?: any[]
17
20
  ) {
18
21
  this.io = new Server(server, {
@@ -59,13 +62,21 @@ export default class Socket {
59
62
  for (let [idx, middleware] of middlewares.entries()) {
60
63
  await middleware(ctx, () => {
61
64
  if (idx === middlewares.length - 1) {
62
- // Middlewares are finished.
65
+ // Middlewares are finished
63
66
  // Extract some data from our enriched koa context to persist
64
67
  // as metadata for the socket
68
+ // Add user info, including a deterministic color and label
69
+ const { _id, email, firstName, lastName } = ctx.user
65
70
  socket.data.user = {
66
- id: ctx.user._id,
67
- email: ctx.user.email,
71
+ _id,
72
+ email,
73
+ firstName,
74
+ lastName,
75
+ sessionId: uuid.v4(),
68
76
  }
77
+
78
+ // Add app ID to help split sockets into rooms
79
+ socket.data.appId = ctx.appId
69
80
  next()
70
81
  }
71
82
  })
@@ -74,6 +85,11 @@ export default class Socket {
74
85
  next(error)
75
86
  }
76
87
  })
88
+
89
+ // Instantiate redis adapter
90
+ const { pub, sub } = getSocketPubSubClients()
91
+ const opts = { key: `socket.io-${path}` }
92
+ this.io.adapter(createAdapter(pub, sub, opts))
77
93
  }
78
94
 
79
95
  // Emit an event to all sockets