@budibase/server 2.6.19-alpha.36 → 2.6.19-alpha.38
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/builder/assets/{index.0ce47fa3.js → index.30093977.js} +243 -242
- package/builder/index.html +1 -1
- package/dist/automation.js +37 -11
- package/dist/automation.js.map +4 -4
- package/dist/index.js +229 -90
- package/dist/index.js.map +4 -4
- package/dist/query.js +29 -6
- package/dist/query.js.map +4 -4
- package/package.json +8 -8
- package/src/api/controllers/deploy/index.ts +1 -0
- package/src/utilities/redis.ts +4 -4
- package/src/websockets/builder.ts +45 -40
- package/src/websockets/client.ts +2 -2
- package/src/websockets/grid.ts +22 -44
- package/src/websockets/websocket.ts +186 -11
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.
|
|
4
|
+
"version": "2.6.19-alpha.38",
|
|
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.
|
|
50
|
-
"@budibase/client": "2.6.19-alpha.
|
|
51
|
-
"@budibase/pro": "2.6.19-alpha.
|
|
52
|
-
"@budibase/shared-core": "2.6.19-alpha.
|
|
53
|
-
"@budibase/string-templates": "2.6.19-alpha.
|
|
54
|
-
"@budibase/types": "2.6.19-alpha.
|
|
49
|
+
"@budibase/backend-core": "2.6.19-alpha.38",
|
|
50
|
+
"@budibase/client": "2.6.19-alpha.38",
|
|
51
|
+
"@budibase/pro": "2.6.19-alpha.38",
|
|
52
|
+
"@budibase/shared-core": "2.6.19-alpha.38",
|
|
53
|
+
"@budibase/string-templates": "2.6.19-alpha.38",
|
|
54
|
+
"@budibase/types": "2.6.19-alpha.38",
|
|
55
55
|
"@bull-board/api": "3.7.0",
|
|
56
56
|
"@bull-board/koa": "3.9.4",
|
|
57
57
|
"@elastic/elasticsearch": "7.10.0",
|
|
@@ -196,5 +196,5 @@
|
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
},
|
|
199
|
-
"gitHead": "
|
|
199
|
+
"gitHead": "67e4eb65b65c13813fbbb5e21e180234e3328755"
|
|
200
200
|
}
|
package/src/utilities/redis.ts
CHANGED
|
@@ -4,9 +4,10 @@ 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
|
|
8
8
|
|
|
9
9
|
// We need to maintain a duplicate client for socket.io pub/sub
|
|
10
|
+
let socketClient: any
|
|
10
11
|
let socketSubClient: any
|
|
11
12
|
|
|
12
13
|
// We init this as we want to keep the connection open all the time
|
|
@@ -15,21 +16,20 @@ export async function init() {
|
|
|
15
16
|
devAppClient = new redis.Client(redis.utils.Databases.DEV_LOCKS)
|
|
16
17
|
debounceClient = new redis.Client(redis.utils.Databases.DEBOUNCE)
|
|
17
18
|
flagClient = new redis.Client(redis.utils.Databases.FLAGS)
|
|
18
|
-
socketClient = new redis.Client(redis.utils.Databases.SOCKET_IO)
|
|
19
19
|
await devAppClient.init()
|
|
20
20
|
await debounceClient.init()
|
|
21
21
|
await flagClient.init()
|
|
22
|
-
await socketClient.init()
|
|
23
22
|
|
|
24
23
|
// Duplicate the socket client for pub/sub
|
|
24
|
+
socketClient = await redis.clients.getSocketClient()
|
|
25
25
|
socketSubClient = socketClient.getClient().duplicate()
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export async function shutdown() {
|
|
29
|
+
console.log("REDIS SHUTDOWN")
|
|
29
30
|
if (devAppClient) await devAppClient.finish()
|
|
30
31
|
if (debounceClient) await debounceClient.finish()
|
|
31
32
|
if (flagClient) await flagClient.finish()
|
|
32
|
-
if (socketClient) await socketClient.finish()
|
|
33
33
|
if (socketSubClient) socketSubClient.disconnect()
|
|
34
34
|
// shutdown core clients
|
|
35
35
|
await redis.clients.shutdown()
|
|
@@ -1,69 +1,74 @@
|
|
|
1
1
|
import authorized from "../middleware/authorized"
|
|
2
|
-
import
|
|
2
|
+
import { BaseSocket } from "./websocket"
|
|
3
3
|
import { permissions } from "@budibase/backend-core"
|
|
4
4
|
import http from "http"
|
|
5
5
|
import Koa from "koa"
|
|
6
|
-
import { Datasource, Table } from "@budibase/types"
|
|
6
|
+
import { Datasource, Table, SocketSession, ContextUser } from "@budibase/types"
|
|
7
7
|
import { gridSocket } from "./index"
|
|
8
8
|
import { clearLock } from "../utilities/redis"
|
|
9
|
+
import { Socket } from "socket.io"
|
|
10
|
+
import { BuilderSocketEvent } from "@budibase/shared-core"
|
|
9
11
|
|
|
10
|
-
export default class BuilderSocket extends
|
|
12
|
+
export default class BuilderSocket extends BaseSocket {
|
|
11
13
|
constructor(app: Koa, server: http.Server) {
|
|
12
14
|
super(app, server, "/socket/builder", [authorized(permissions.BUILDER)])
|
|
15
|
+
}
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
})
|
|
17
|
+
async onConnect(socket: Socket) {
|
|
18
|
+
// Initial identification of selected app
|
|
19
|
+
socket.on(BuilderSocketEvent.SelectApp, async (appId, callback) => {
|
|
20
|
+
await this.joinRoom(socket, appId)
|
|
28
21
|
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
// Reply with all users in current room
|
|
23
|
+
const sessions = await this.getRoomSessions(appId)
|
|
24
|
+
callback({ users: sessions })
|
|
25
|
+
})
|
|
26
|
+
}
|
|
32
27
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
} catch (e) {
|
|
44
|
-
// This is fine, just means this user didn't hold the lock
|
|
45
|
-
}
|
|
28
|
+
async onDisconnect(socket: Socket) {
|
|
29
|
+
// Remove app lock from this user if they have no other connections
|
|
30
|
+
try {
|
|
31
|
+
// @ts-ignore
|
|
32
|
+
const session: SocketSession = socket.data
|
|
33
|
+
const { _id, sessionId, room } = session
|
|
34
|
+
const sessions = await this.getRoomSessions(room)
|
|
35
|
+
const hasOtherSession = sessions.some(otherSession => {
|
|
36
|
+
return _id === otherSession._id && sessionId !== otherSession.sessionId
|
|
46
37
|
})
|
|
47
|
-
|
|
38
|
+
if (!hasOtherSession && room) {
|
|
39
|
+
// @ts-ignore
|
|
40
|
+
const user: ContextUser = { _id: socket.data._id }
|
|
41
|
+
await clearLock(room, user)
|
|
42
|
+
}
|
|
43
|
+
} catch (e) {
|
|
44
|
+
// This is fine, just means this user didn't hold the lock
|
|
45
|
+
}
|
|
48
46
|
}
|
|
49
47
|
|
|
50
48
|
emitTableUpdate(ctx: any, table: Table) {
|
|
51
|
-
this.io
|
|
49
|
+
this.io
|
|
50
|
+
.in(ctx.appId)
|
|
51
|
+
.emit(BuilderSocketEvent.TableChange, { id: table._id, table })
|
|
52
52
|
gridSocket.emitTableUpdate(table)
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
emitTableDeletion(ctx: any, id: string) {
|
|
56
|
-
this.io
|
|
56
|
+
this.io
|
|
57
|
+
.in(ctx.appId)
|
|
58
|
+
.emit(BuilderSocketEvent.TableChange, { id, table: null })
|
|
57
59
|
gridSocket.emitTableDeletion(id)
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
emitDatasourceUpdate(ctx: any, datasource: Datasource) {
|
|
61
|
-
this.io
|
|
62
|
-
.
|
|
63
|
-
|
|
63
|
+
this.io.in(ctx.appId).emit(BuilderSocketEvent.DatasourceChange, {
|
|
64
|
+
id: datasource._id,
|
|
65
|
+
datasource,
|
|
66
|
+
})
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
emitDatasourceDeletion(ctx: any, id: string) {
|
|
67
|
-
this.io
|
|
70
|
+
this.io
|
|
71
|
+
.in(ctx.appId)
|
|
72
|
+
.emit(BuilderSocketEvent.DatasourceChange, { id, datasource: null })
|
|
68
73
|
}
|
|
69
74
|
}
|
package/src/websockets/client.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { BaseSocket } from "./websocket"
|
|
2
2
|
import authorized from "../middleware/authorized"
|
|
3
3
|
import http from "http"
|
|
4
4
|
import Koa from "koa"
|
|
5
5
|
import { permissions } from "@budibase/backend-core"
|
|
6
6
|
|
|
7
|
-
export default class ClientAppWebsocket extends
|
|
7
|
+
export default class ClientAppWebsocket extends BaseSocket {
|
|
8
8
|
constructor(app: Koa, server: http.Server) {
|
|
9
9
|
super(app, server, "/socket/client", [authorized(permissions.BUILDER)])
|
|
10
10
|
}
|
package/src/websockets/grid.ts
CHANGED
|
@@ -1,73 +1,51 @@
|
|
|
1
1
|
import authorized from "../middleware/authorized"
|
|
2
|
-
import
|
|
2
|
+
import { BaseSocket } from "./websocket"
|
|
3
3
|
import { permissions } from "@budibase/backend-core"
|
|
4
4
|
import http from "http"
|
|
5
5
|
import Koa from "koa"
|
|
6
6
|
import { getTableId } from "../api/controllers/row/utils"
|
|
7
7
|
import { Row, Table } from "@budibase/types"
|
|
8
|
+
import { Socket } from "socket.io"
|
|
9
|
+
import { GridSocketEvent } from "@budibase/shared-core"
|
|
8
10
|
|
|
9
|
-
export default class GridSocket extends
|
|
11
|
+
export default class GridSocket extends BaseSocket {
|
|
10
12
|
constructor(app: Koa, server: http.Server) {
|
|
11
13
|
super(app, server, "/socket/grid", [authorized(permissions.BUILDER)])
|
|
14
|
+
}
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
let currentRoom: string
|
|
18
|
-
|
|
19
|
-
// Initial identification of connected spreadsheet
|
|
20
|
-
socket.on("select-table", async (tableId, callback) => {
|
|
21
|
-
// Leave current room
|
|
22
|
-
if (currentRoom) {
|
|
23
|
-
socket.to(currentRoom).emit("user-disconnect", user)
|
|
24
|
-
socket.leave(currentRoom)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Join new room
|
|
28
|
-
currentRoom = tableId
|
|
29
|
-
socket.join(currentRoom)
|
|
30
|
-
socket.to(currentRoom).emit("user-update", user)
|
|
31
|
-
|
|
32
|
-
// Reply with all users in current room
|
|
33
|
-
const sockets = await this.io.in(currentRoom).fetchSockets()
|
|
34
|
-
callback({
|
|
35
|
-
users: sockets.map(socket => socket.data.user),
|
|
36
|
-
})
|
|
37
|
-
})
|
|
16
|
+
async onConnect(socket: Socket) {
|
|
17
|
+
// Initial identification of connected spreadsheet
|
|
18
|
+
socket.on(GridSocketEvent.SelectTable, async (tableId, callback) => {
|
|
19
|
+
await this.joinRoom(socket, tableId)
|
|
38
20
|
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
socket.to(currentRoom).emit("user-update", user)
|
|
44
|
-
}
|
|
45
|
-
})
|
|
21
|
+
// Reply with all users in current room
|
|
22
|
+
const sessions = await this.getRoomSessions(tableId)
|
|
23
|
+
callback({ users: sessions })
|
|
24
|
+
})
|
|
46
25
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
socket.to(currentRoom).emit("user-disconnect", user)
|
|
51
|
-
}
|
|
52
|
-
})
|
|
26
|
+
// Handle users selecting a new cell
|
|
27
|
+
socket.on(GridSocketEvent.SelectCell, cellId => {
|
|
28
|
+
this.updateUser(socket, { focusedCellId: cellId })
|
|
53
29
|
})
|
|
54
30
|
}
|
|
55
31
|
|
|
56
32
|
emitRowUpdate(ctx: any, row: Row) {
|
|
57
33
|
const tableId = getTableId(ctx)
|
|
58
|
-
this.io.in(tableId).emit(
|
|
34
|
+
this.io.in(tableId).emit(GridSocketEvent.RowChange, { id: row._id, row })
|
|
59
35
|
}
|
|
60
36
|
|
|
61
37
|
emitRowDeletion(ctx: any, id: string) {
|
|
62
38
|
const tableId = getTableId(ctx)
|
|
63
|
-
this.io.in(tableId).emit(
|
|
39
|
+
this.io.in(tableId).emit(GridSocketEvent.RowChange, { id, row: null })
|
|
64
40
|
}
|
|
65
41
|
|
|
66
42
|
emitTableUpdate(table: Table) {
|
|
67
|
-
this.io
|
|
43
|
+
this.io
|
|
44
|
+
.in(table._id!)
|
|
45
|
+
.emit(GridSocketEvent.TableChange, { id: table._id, table })
|
|
68
46
|
}
|
|
69
47
|
|
|
70
48
|
emitTableDeletion(id: string) {
|
|
71
|
-
this.io.in(id).emit(
|
|
49
|
+
this.io.in(id).emit(GridSocketEvent.TableChange, { id, table: null })
|
|
72
50
|
}
|
|
73
51
|
}
|
|
@@ -3,14 +3,18 @@ import http from "http"
|
|
|
3
3
|
import Koa from "koa"
|
|
4
4
|
import Cookies from "cookies"
|
|
5
5
|
import { userAgent } from "koa-useragent"
|
|
6
|
-
import { auth } from "@budibase/backend-core"
|
|
6
|
+
import { auth, redis } from "@budibase/backend-core"
|
|
7
7
|
import currentApp from "../middleware/currentapp"
|
|
8
8
|
import { createAdapter } from "@socket.io/redis-adapter"
|
|
9
|
+
import { Socket } from "socket.io"
|
|
9
10
|
import { getSocketPubSubClients } from "../utilities/redis"
|
|
10
|
-
import
|
|
11
|
+
import { SocketEvent, SocketSessionTTL } from "@budibase/shared-core"
|
|
12
|
+
import { SocketSession } from "@budibase/types"
|
|
11
13
|
|
|
12
|
-
export
|
|
14
|
+
export class BaseSocket {
|
|
13
15
|
io: Server
|
|
16
|
+
path: string
|
|
17
|
+
redisClient?: redis.Client
|
|
14
18
|
|
|
15
19
|
constructor(
|
|
16
20
|
app: Koa,
|
|
@@ -18,6 +22,7 @@ export default class Socket {
|
|
|
18
22
|
path: string = "/",
|
|
19
23
|
additionalMiddlewares?: any[]
|
|
20
24
|
) {
|
|
25
|
+
this.path = path
|
|
21
26
|
this.io = new Server(server, {
|
|
22
27
|
path,
|
|
23
28
|
})
|
|
@@ -65,18 +70,14 @@ export default class Socket {
|
|
|
65
70
|
// Middlewares are finished
|
|
66
71
|
// Extract some data from our enriched koa context to persist
|
|
67
72
|
// as metadata for the socket
|
|
68
|
-
// Add user info, including a deterministic color and label
|
|
69
73
|
const { _id, email, firstName, lastName } = ctx.user
|
|
70
|
-
socket.data
|
|
74
|
+
socket.data = {
|
|
71
75
|
_id,
|
|
72
76
|
email,
|
|
73
77
|
firstName,
|
|
74
78
|
lastName,
|
|
75
|
-
sessionId:
|
|
79
|
+
sessionId: socket.id,
|
|
76
80
|
}
|
|
77
|
-
|
|
78
|
-
// Add app ID to help split sockets into rooms
|
|
79
|
-
socket.data.appId = ctx.appId
|
|
80
81
|
next()
|
|
81
82
|
}
|
|
82
83
|
})
|
|
@@ -86,10 +87,184 @@ export default class Socket {
|
|
|
86
87
|
}
|
|
87
88
|
})
|
|
88
89
|
|
|
89
|
-
//
|
|
90
|
+
// Initialise redis before handling connections
|
|
91
|
+
this.initialise().then(() => {
|
|
92
|
+
this.io.on("connection", async socket => {
|
|
93
|
+
// Add built in handler for heartbeats
|
|
94
|
+
socket.on(SocketEvent.Heartbeat, async () => {
|
|
95
|
+
console.log(socket.data.email, "heartbeat received")
|
|
96
|
+
await this.extendSessionTTL(socket.data.sessionId)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
// Add early disconnection handler to clean up and leave room
|
|
100
|
+
socket.on("disconnect", async () => {
|
|
101
|
+
// Run any custom disconnection logic before we leave the room,
|
|
102
|
+
// so that we have access to their room etc before disconnection
|
|
103
|
+
await this.onDisconnect(socket)
|
|
104
|
+
|
|
105
|
+
// Leave the current room when the user disconnects if we're in one
|
|
106
|
+
await this.leaveRoom(socket)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
// Add handlers for this socket
|
|
110
|
+
await this.onConnect(socket)
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async initialise() {
|
|
116
|
+
// Instantiate redis adapter.
|
|
117
|
+
// We use a fully qualified key name here as this bypasses the normal
|
|
118
|
+
// redis client#s key prefixing.
|
|
90
119
|
const { pub, sub } = getSocketPubSubClients()
|
|
91
|
-
const opts = {
|
|
120
|
+
const opts = {
|
|
121
|
+
key: `${redis.utils.Databases.SOCKET_IO}-${this.path}-pubsub`,
|
|
122
|
+
}
|
|
92
123
|
this.io.adapter(createAdapter(pub, sub, opts))
|
|
124
|
+
|
|
125
|
+
// Fetch redis client
|
|
126
|
+
this.redisClient = await redis.clients.getSocketClient()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Gets the redis key for a certain session ID
|
|
130
|
+
getSessionKey(sessionId: string) {
|
|
131
|
+
return `${this.path}-session:${sessionId}`
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Gets the redis key for certain room name
|
|
135
|
+
getRoomKey(room: string) {
|
|
136
|
+
return `${this.path}-room:${room}`
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async extendSessionTTL(sessionId: string) {
|
|
140
|
+
const key = this.getSessionKey(sessionId)
|
|
141
|
+
await this.redisClient?.setExpiry(key, SocketSessionTTL)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Gets an array of all redis keys of users inside a certain room
|
|
145
|
+
async getRoomSessionIds(room: string): Promise<string[]> {
|
|
146
|
+
const keys = await this.redisClient?.get(this.getRoomKey(room))
|
|
147
|
+
return keys || []
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Sets the list of redis keys for users inside a certain room.
|
|
151
|
+
// There is no TTL on the actual room key map itself.
|
|
152
|
+
async setRoomSessionIds(room: string, ids: string[]) {
|
|
153
|
+
await this.redisClient?.store(this.getRoomKey(room), ids)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Gets a list of all users inside a certain room
|
|
157
|
+
async getRoomSessions(room?: string): Promise<SocketSession[]> {
|
|
158
|
+
if (room) {
|
|
159
|
+
const sessionIds = await this.getRoomSessionIds(room)
|
|
160
|
+
const keys = sessionIds.map(this.getSessionKey.bind(this))
|
|
161
|
+
const sessions = await this.redisClient?.bulkGet(keys)
|
|
162
|
+
return Object.values(sessions || {})
|
|
163
|
+
} else {
|
|
164
|
+
return []
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Detects keys which have been pruned from redis due to TTL expiry in a certain
|
|
169
|
+
// room and broadcasts disconnection messages to ensure clients are aware
|
|
170
|
+
async pruneRoom(room: string) {
|
|
171
|
+
const sessionIds = await this.getRoomSessionIds(room)
|
|
172
|
+
const sessionsExist = await Promise.all(
|
|
173
|
+
sessionIds.map(id => this.redisClient?.exists(this.getSessionKey(id)))
|
|
174
|
+
)
|
|
175
|
+
const prunedSessionIds = sessionIds.filter((id, idx) => {
|
|
176
|
+
if (!sessionsExist[idx]) {
|
|
177
|
+
this.io.to(room).emit(SocketEvent.UserDisconnect, sessionIds[idx])
|
|
178
|
+
return false
|
|
179
|
+
}
|
|
180
|
+
return true
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
// Store new pruned keys
|
|
184
|
+
await this.setRoomSessionIds(room, prunedSessionIds)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Adds a user to a certain room
|
|
188
|
+
async joinRoom(socket: Socket, room: string) {
|
|
189
|
+
if (!room) {
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
// Prune room before joining
|
|
193
|
+
await this.pruneRoom(room)
|
|
194
|
+
|
|
195
|
+
// Check if we're already in a room, as we'll need to leave if we are before we
|
|
196
|
+
// can join a different room
|
|
197
|
+
const oldRoom = socket.data.room
|
|
198
|
+
if (oldRoom && oldRoom !== room) {
|
|
199
|
+
await this.leaveRoom(socket)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Join new room
|
|
203
|
+
if (!oldRoom || oldRoom !== room) {
|
|
204
|
+
socket.join(room)
|
|
205
|
+
socket.data.room = room
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Store in redis
|
|
209
|
+
// @ts-ignore
|
|
210
|
+
let user: SocketSession = socket.data
|
|
211
|
+
const { sessionId } = user
|
|
212
|
+
const key = this.getSessionKey(sessionId)
|
|
213
|
+
await this.redisClient?.store(key, user, SocketSessionTTL)
|
|
214
|
+
const sessionIds = await this.getRoomSessionIds(room)
|
|
215
|
+
if (!sessionIds.includes(sessionId)) {
|
|
216
|
+
await this.setRoomSessionIds(room, [...sessionIds, sessionId])
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Notify other users
|
|
220
|
+
socket.to(room).emit(SocketEvent.UserUpdate, user)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Disconnects a socket from its current room
|
|
224
|
+
async leaveRoom(socket: Socket) {
|
|
225
|
+
// @ts-ignore
|
|
226
|
+
let user: SocketSession = socket.data
|
|
227
|
+
const { room, sessionId } = user
|
|
228
|
+
if (!room) {
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Leave room
|
|
233
|
+
socket.leave(room)
|
|
234
|
+
socket.data.room = undefined
|
|
235
|
+
|
|
236
|
+
// Delete from redis
|
|
237
|
+
const key = this.getSessionKey(sessionId)
|
|
238
|
+
await this.redisClient?.delete(key)
|
|
239
|
+
const sessionIds = await this.getRoomSessionIds(room)
|
|
240
|
+
await this.setRoomSessionIds(
|
|
241
|
+
room,
|
|
242
|
+
sessionIds.filter(id => id !== sessionId)
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
// Notify other users
|
|
246
|
+
socket.to(room).emit(SocketEvent.UserDisconnect, sessionId)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Updates a connected user's metadata, assuming a room change is not required.
|
|
250
|
+
async updateUser(socket: Socket, patch: Object) {
|
|
251
|
+
socket.data = {
|
|
252
|
+
...socket.data,
|
|
253
|
+
...patch,
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// If we're in a room, notify others of this change and update redis
|
|
257
|
+
if (socket.data.room) {
|
|
258
|
+
await this.joinRoom(socket, socket.data.room)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async onConnect(socket: Socket) {
|
|
263
|
+
// Override
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async onDisconnect(socket: Socket) {
|
|
267
|
+
// Override
|
|
93
268
|
}
|
|
94
269
|
|
|
95
270
|
// Emit an event to all sockets
|