@pluv/io 0.19.0 → 0.21.0
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/.turbo/turbo-build.log +16 -16
- package/CHANGELOG.md +80 -0
- package/dist/index.d.mts +115 -83
- package/dist/index.d.ts +115 -83
- package/dist/index.js +323 -212
- package/dist/index.mjs +323 -212
- package/package.json +9 -9
- package/src/AbstractPlatform.ts +64 -20
- package/src/AbstractWebSocket.ts +50 -16
- package/src/IORoom.ts +262 -176
- package/src/PluvIO.ts +27 -14
- package/src/PluvProcedure.ts +11 -29
- package/src/PluvRouter.ts +4 -4
- package/src/PluvServer.ts +27 -12
- package/src/authorize.ts +2 -2
- package/src/constants.ts +1 -0
- package/src/createIO.ts +4 -4
- package/src/index.ts +7 -5
- package/src/types.ts +24 -11
- package/src/version.generated.ts +1 -1
package/src/IORoom.ts
CHANGED
|
@@ -18,16 +18,21 @@ import type {
|
|
|
18
18
|
import colors from "kleur";
|
|
19
19
|
import type {
|
|
20
20
|
AbstractPlatform,
|
|
21
|
-
|
|
22
|
-
InferPlatformRoomContextType,
|
|
21
|
+
InferPlatformWebSocketSource,
|
|
23
22
|
InferPlatformWebSocketType,
|
|
23
|
+
InferRoomContextType,
|
|
24
24
|
} from "./AbstractPlatform";
|
|
25
|
-
import { AbstractMessageEvent } from "./AbstractWebSocket";
|
|
25
|
+
import { AbstractCloseEvent, AbstractErrorEvent, AbstractMessageEvent, AbstractWebSocket } from "./AbstractWebSocket";
|
|
26
26
|
import type { PluvRouter, PluvRouterEventConfig } from "./PluvRouter";
|
|
27
27
|
import { authorize } from "./authorize";
|
|
28
|
-
import
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
import { PING_TIMEOUT_MS } from "./constants";
|
|
29
|
+
import type {
|
|
30
|
+
EventResolverContext,
|
|
31
|
+
IORoomListenerEvent,
|
|
32
|
+
SendMessageOptions,
|
|
33
|
+
WebSocketSession,
|
|
34
|
+
WebSocketType,
|
|
35
|
+
} from "./types";
|
|
31
36
|
|
|
32
37
|
type BroadcastMessage<TIO extends IORoom<any, any, any, any>> =
|
|
33
38
|
| InferEventMessage<InferIOInput<TIO>>
|
|
@@ -51,12 +56,12 @@ export type BroadcastProxy<TIO extends IORoom<any, any, any, any>> = (<TEvent ex
|
|
|
51
56
|
|
|
52
57
|
export type IORoomConfig<
|
|
53
58
|
TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>,
|
|
54
|
-
TAuthorize extends IOAuthorize<any, any,
|
|
59
|
+
TAuthorize extends IOAuthorize<any, any, InferRoomContextType<TPlatform>> = BaseIOAuthorize,
|
|
55
60
|
TContext extends JsonObject = {},
|
|
56
61
|
TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext> = {},
|
|
57
62
|
> = Partial<IORoomListeners<TPlatform>> & {
|
|
58
63
|
authorize?: TAuthorize;
|
|
59
|
-
context: TContext &
|
|
64
|
+
context: TContext & InferRoomContextType<TPlatform>;
|
|
60
65
|
crdt?: { doc: (value: any) => AbstractCrdtDocFactory<any> };
|
|
61
66
|
debug: boolean;
|
|
62
67
|
platform: TPlatform;
|
|
@@ -64,30 +69,29 @@ export type IORoomConfig<
|
|
|
64
69
|
};
|
|
65
70
|
|
|
66
71
|
interface SendMessageSender {
|
|
67
|
-
|
|
72
|
+
sessionId: string | null;
|
|
68
73
|
user: JsonObject | null;
|
|
69
74
|
}
|
|
70
75
|
|
|
71
|
-
export
|
|
76
|
+
export interface WebsocketRegisterOptions {
|
|
72
77
|
token?: string | null;
|
|
73
|
-
}
|
|
74
|
-
InferPlatformEventContextType<TPlatform>;
|
|
78
|
+
}
|
|
75
79
|
|
|
76
80
|
export class IORoom<
|
|
77
81
|
TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>,
|
|
78
|
-
TAuthorize extends IOAuthorize<any, any,
|
|
82
|
+
TAuthorize extends IOAuthorize<any, any, InferRoomContextType<TPlatform>> = BaseIOAuthorize,
|
|
79
83
|
TContext extends Record<string, any> = {},
|
|
80
84
|
TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext> = {},
|
|
81
85
|
> implements IOLike<TAuthorize, TEvents>
|
|
82
86
|
{
|
|
83
|
-
private readonly _context: TContext &
|
|
87
|
+
private readonly _context: TContext & InferRoomContextType<TPlatform>;
|
|
84
88
|
private readonly _debug: boolean;
|
|
85
89
|
private readonly _docFactory: AbstractCrdtDocFactory<any>;
|
|
86
90
|
private readonly _platform: TPlatform;
|
|
87
91
|
|
|
88
92
|
private _doc: AbstractCrdtDoc<any>;
|
|
89
93
|
private _listeners: IORoomListeners<TPlatform>;
|
|
90
|
-
private _sessions = new Map<string,
|
|
94
|
+
private _sessions = new Map<string, AbstractWebSocket>();
|
|
91
95
|
private _uninitialize: (() => Promise<void>) | null = null;
|
|
92
96
|
|
|
93
97
|
readonly _authorize: TAuthorize | null = null;
|
|
@@ -124,7 +128,7 @@ export class IORoom<
|
|
|
124
128
|
this._docFactory = crdt.doc(() => ({}));
|
|
125
129
|
this._doc = this._docFactory.getEmpty();
|
|
126
130
|
this._router = router;
|
|
127
|
-
this._platform = platform;
|
|
131
|
+
this._platform = platform.initialize({ context });
|
|
128
132
|
|
|
129
133
|
this.id = id;
|
|
130
134
|
|
|
@@ -133,6 +137,20 @@ export class IORoom<
|
|
|
133
137
|
};
|
|
134
138
|
|
|
135
139
|
if (authorize) this._authorize = authorize;
|
|
140
|
+
|
|
141
|
+
const webSockets = this._platform.getWebSockets() as readonly InferPlatformWebSocketSource<TPlatform>[];
|
|
142
|
+
|
|
143
|
+
if (!webSockets.length) return;
|
|
144
|
+
|
|
145
|
+
webSockets.forEach((webSocket) => {
|
|
146
|
+
const deserialized = this._platform.getSerializedState(webSocket);
|
|
147
|
+
const sessionId = this._platform.getSessionId(webSocket);
|
|
148
|
+
|
|
149
|
+
if (!deserialized) return;
|
|
150
|
+
if (typeof sessionId !== "string") return;
|
|
151
|
+
|
|
152
|
+
this._sessions.set(sessionId, this._platform.convertWebSocket(webSocket, { room: this.id }));
|
|
153
|
+
});
|
|
136
154
|
}
|
|
137
155
|
|
|
138
156
|
public getSize(): number {
|
|
@@ -144,147 +162,177 @@ export class IORoom<
|
|
|
144
162
|
* @author leedavidcs
|
|
145
163
|
* @date December 21, 2022
|
|
146
164
|
*/
|
|
147
|
-
return Array.from(this._sessions.values()).reduce((count,
|
|
148
|
-
if (
|
|
165
|
+
return Array.from(this._sessions.values()).reduce((count, pluvWs) => {
|
|
166
|
+
if (pluvWs.state.quit) return count;
|
|
167
|
+
|
|
168
|
+
const pingTime = this._platform.getLastPing(pluvWs) ?? pluvWs.state.timers.ping;
|
|
149
169
|
|
|
150
|
-
return currentTime -
|
|
170
|
+
return currentTime - pingTime > PING_TIMEOUT_MS ? count : count + 1;
|
|
151
171
|
}, 0);
|
|
152
172
|
}
|
|
153
173
|
|
|
174
|
+
public onClose(webSocket: WebSocketType<TPlatform>): (event: AbstractCloseEvent) => void {
|
|
175
|
+
this._ensureDetached();
|
|
176
|
+
|
|
177
|
+
const wsSession = this._getAbstractWs(webSocket);
|
|
178
|
+
|
|
179
|
+
if (!wsSession) return (): void => undefined;
|
|
180
|
+
|
|
181
|
+
return this._onClose(wsSession);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
public onError(webSocket: WebSocketType<TPlatform>): (event: AbstractErrorEvent) => void {
|
|
185
|
+
this._ensureDetached();
|
|
186
|
+
|
|
187
|
+
const wsSession = this._getAbstractWs(webSocket);
|
|
188
|
+
|
|
189
|
+
if (!wsSession) return (): void => undefined;
|
|
190
|
+
|
|
191
|
+
return this._onClose(wsSession);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
public onMessage(webSocket: WebSocketType<TPlatform>): (event: AbstractMessageEvent) => void {
|
|
195
|
+
this._ensureDetached();
|
|
196
|
+
|
|
197
|
+
const wsSession = this._getAbstractWs(webSocket);
|
|
198
|
+
|
|
199
|
+
if (!wsSession) return (): void => undefined;
|
|
200
|
+
|
|
201
|
+
return this._onMessage(wsSession);
|
|
202
|
+
}
|
|
203
|
+
|
|
154
204
|
public async register(
|
|
155
|
-
webSocket:
|
|
156
|
-
options: WebsocketRegisterOptions
|
|
205
|
+
webSocket: InferPlatformWebSocketSource<TPlatform>,
|
|
206
|
+
options: WebsocketRegisterOptions = {},
|
|
157
207
|
): Promise<void> {
|
|
158
|
-
const { token
|
|
159
|
-
const platformEventContext = _platformEventContext as InferPlatformRoomContextType<TPlatform> &
|
|
160
|
-
InferPlatformEventContextType<TPlatform>;
|
|
208
|
+
const { token } = options;
|
|
161
209
|
|
|
162
210
|
if (!this._uninitialize) await this._initialize();
|
|
163
211
|
|
|
164
|
-
const user = await this._getAuthorizedUser(token
|
|
212
|
+
const user = await this._getAuthorizedUser(token);
|
|
213
|
+
const ioAuthorize = this._getIOAuthorize();
|
|
214
|
+
const isUnauthorized = !!ioAuthorize?.required && !user;
|
|
165
215
|
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
userId: user?.id ?? null,
|
|
171
|
-
});
|
|
216
|
+
// There is an attempt for multiple of the same identities. Probably malicious.
|
|
217
|
+
const isTokenInUse: boolean =
|
|
218
|
+
!!user &&
|
|
219
|
+
(await this._getSessions().then((sessions) => sessions.some((session) => session.user?.id === user.id)));
|
|
172
220
|
|
|
173
|
-
this.
|
|
221
|
+
const pluvWs = await this._platform.convertWebSocket(webSocket, { room: this.id });
|
|
174
222
|
|
|
175
|
-
|
|
176
|
-
const ioAuthorize = this._getIOAuthorize(options);
|
|
223
|
+
this._logDebug(`${colors.blue(`Registering connection for room ${this.id}:`)} ${pluvWs.sessionId}`);
|
|
177
224
|
|
|
178
|
-
|
|
179
|
-
// There is an attempt for multiple of the same identities. Probably malicious.
|
|
180
|
-
const isTokenInUse =
|
|
181
|
-
!!user && Array.from(this._sessions.values()).some((session) => session.user?.id === user.id);
|
|
225
|
+
await this._platform.acceptWebSocket(pluvWs);
|
|
182
226
|
|
|
183
227
|
if (isUnauthorized || isTokenInUse) {
|
|
184
|
-
this._logDebug(`${colors.blue("Authorization failed for connection:")} ${sessionId}`);
|
|
228
|
+
this._logDebug(`${colors.blue("Authorization failed for connection:")} ${pluvWs.sessionId}`);
|
|
185
229
|
|
|
186
|
-
pluvWs.handleError({ error: new Error("Not authorized") });
|
|
230
|
+
pluvWs.handleError({ error: new Error("Not authorized"), room: this.id });
|
|
187
231
|
pluvWs.close(3000, "WebSocket unauthorized.");
|
|
188
232
|
|
|
189
233
|
return;
|
|
190
234
|
}
|
|
191
235
|
|
|
192
|
-
|
|
193
|
-
const session: WebSocketSession<TAuthorize> = {
|
|
194
|
-
id: sessionId,
|
|
195
|
-
presence: null,
|
|
196
|
-
quit: false,
|
|
197
|
-
room: this.id,
|
|
198
|
-
timers: {
|
|
199
|
-
ping: currentTime,
|
|
200
|
-
},
|
|
201
|
-
webSocket: pluvWs,
|
|
202
|
-
user,
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
this._sessions.set(session.id, session);
|
|
236
|
+
this._sessions.set(pluvWs.sessionId, pluvWs);
|
|
206
237
|
|
|
207
|
-
await this._platform.persistance.addUser(this.id, sessionId, user ?? {});
|
|
238
|
+
await this._platform.persistance.addUser(this.id, pluvWs.sessionId, user ?? {});
|
|
208
239
|
|
|
209
|
-
|
|
210
|
-
|
|
240
|
+
if (this._platform._registrationMode === "attached") {
|
|
241
|
+
const onClose = this._onClose(pluvWs).bind(this);
|
|
242
|
+
const onMessage = this._onMessage(pluvWs).bind(this);
|
|
211
243
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
244
|
+
pluvWs.addEventListener("close", onClose);
|
|
245
|
+
pluvWs.addEventListener("error", onClose);
|
|
246
|
+
pluvWs.addEventListener("message", onMessage);
|
|
247
|
+
}
|
|
215
248
|
|
|
216
|
-
this._emitRegistered(
|
|
249
|
+
await this._emitRegistered(pluvWs);
|
|
217
250
|
|
|
218
|
-
this._logDebug(`${colors.blue(`Registered connection for room ${this.id}:`)} ${sessionId}`);
|
|
251
|
+
this._logDebug(`${colors.blue(`Registered connection for room ${this.id}:`)} ${pluvWs.sessionId}`);
|
|
219
252
|
|
|
220
253
|
const size = this.getSize();
|
|
221
254
|
|
|
222
255
|
this._logDebug(`${colors.blue(`Room ${this.id} size:`)} ${size}`);
|
|
223
256
|
}
|
|
224
257
|
|
|
225
|
-
private _broadcast(params: BroadcastParams<this>): void {
|
|
258
|
+
private async _broadcast(params: BroadcastParams<this>): Promise<void> {
|
|
226
259
|
const { message, senderId } = params;
|
|
227
260
|
|
|
228
|
-
const
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
const quitters = Array.from(this._sessions.values()).filter(
|
|
232
|
-
(session) => session.quit || currentTime - session.timers.ping > PING_TIMEOUT_MS,
|
|
233
|
-
);
|
|
234
|
-
|
|
235
|
-
quitters.forEach((session) => {
|
|
236
|
-
this._sessions.delete(session.id);
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
this._emitQuitters(quitters);
|
|
261
|
+
const sender = senderId ? (this._sessions.get(senderId) ?? null) : null;
|
|
262
|
+
const session = (await sender?.getSession()) ?? null;
|
|
263
|
+
const user = session?.user ?? null;
|
|
240
264
|
|
|
241
265
|
this._platform.pubSub.publish(this.id, {
|
|
242
266
|
connectionId: senderId ?? null,
|
|
243
267
|
room: this.id,
|
|
244
|
-
user
|
|
268
|
+
user,
|
|
245
269
|
...message,
|
|
246
270
|
});
|
|
247
271
|
}
|
|
248
272
|
|
|
249
|
-
private _emitQuitters(
|
|
250
|
-
|
|
251
|
-
|
|
273
|
+
private async _emitQuitters(): Promise<void> {
|
|
274
|
+
const currentTime = new Date().getTime();
|
|
275
|
+
const quitters = Array.from(this._sessions.values()).filter(
|
|
276
|
+
(pluvWs) => pluvWs.state.quit || currentTime - pluvWs.state.timers.ping > PING_TIMEOUT_MS,
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
quitters.forEach((pluvWs) => {
|
|
280
|
+
this._sessions.delete(pluvWs.sessionId);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const promises = quitters.map(async (quitter) => {
|
|
284
|
+
await this._broadcast({
|
|
252
285
|
message: {
|
|
253
286
|
type: "$EXIT",
|
|
254
|
-
data: { sessionId: quitter.
|
|
287
|
+
data: { sessionId: quitter.sessionId },
|
|
255
288
|
},
|
|
256
|
-
senderId: quitter.
|
|
289
|
+
senderId: quitter.sessionId,
|
|
257
290
|
});
|
|
258
291
|
});
|
|
292
|
+
|
|
293
|
+
await Promise.all(promises);
|
|
259
294
|
}
|
|
260
295
|
|
|
261
|
-
private _emitRegistered(
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
session,
|
|
268
|
-
);
|
|
296
|
+
private async _emitRegistered(pluvWs: AbstractWebSocket): Promise<void> {
|
|
297
|
+
const session = await pluvWs.getSession();
|
|
298
|
+
const sessionId = session.id;
|
|
299
|
+
const user = session.user;
|
|
300
|
+
|
|
301
|
+
await this._sendSelfMessage({ type: "$REGISTERED", data: { sessionId } }, { sessionId, user });
|
|
269
302
|
}
|
|
270
303
|
|
|
271
|
-
private
|
|
272
|
-
if (
|
|
273
|
-
|
|
304
|
+
private _ensureDetached(): void {
|
|
305
|
+
if (this._platform._registrationMode === "detached") return;
|
|
306
|
+
|
|
307
|
+
throw new Error("Platform must use detached mode");
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private _getAbstractWs(webSocket: WebSocketType<TPlatform>): AbstractWebSocket | null {
|
|
311
|
+
if ((webSocket as unknown as any) instanceof AbstractWebSocket) return webSocket;
|
|
312
|
+
|
|
313
|
+
const sessionId = this._platform.getSessionId(webSocket);
|
|
314
|
+
|
|
315
|
+
if (typeof sessionId === "string") {
|
|
316
|
+
const session = this._sessions.get(sessionId) ?? null;
|
|
317
|
+
|
|
318
|
+
if (session) return session;
|
|
274
319
|
}
|
|
275
320
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
user: InputZodLike<any>;
|
|
280
|
-
} | null;
|
|
321
|
+
const sessions = Array.from(this._sessions.values());
|
|
322
|
+
|
|
323
|
+
return sessions.find((pluvWs) => pluvWs.webSocket === webSocket) ?? null;
|
|
281
324
|
}
|
|
282
325
|
|
|
283
|
-
private async
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
326
|
+
private async _getSession(webSocket: WebSocketType<TPlatform>): Promise<WebSocketSession<TAuthorize>> {
|
|
327
|
+
const pluvWs = this._getAbstractWs(webSocket);
|
|
328
|
+
|
|
329
|
+
if (!pluvWs) throw new Error("Session could not be found");
|
|
330
|
+
|
|
331
|
+
return await pluvWs.getSession<TAuthorize>();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private async _getAuthorizedUser(token: Maybe<string>): Promise<InferIOAuthorizeUser<InferIOAuthorize<this>>> {
|
|
335
|
+
const ioAuthorize = this._getIOAuthorize();
|
|
288
336
|
|
|
289
337
|
if (!ioAuthorize) return null as InferIOAuthorizeUser<InferIOAuthorize<this>>;
|
|
290
338
|
if (!token) return null as InferIOAuthorizeUser<InferIOAuthorize<this>>;
|
|
@@ -318,6 +366,18 @@ export class IORoom<
|
|
|
318
366
|
}
|
|
319
367
|
}
|
|
320
368
|
|
|
369
|
+
private _getIOAuthorize() {
|
|
370
|
+
if (typeof this._authorize === "function") {
|
|
371
|
+
return this._authorize(this._context);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return this._authorize as {
|
|
375
|
+
required: any;
|
|
376
|
+
secret: string;
|
|
377
|
+
user: InputZodLike<any>;
|
|
378
|
+
} | null;
|
|
379
|
+
}
|
|
380
|
+
|
|
321
381
|
private _getProcedure(
|
|
322
382
|
message: EventMessage<string, any>,
|
|
323
383
|
): (typeof this._router)["_events"][keyof (typeof this._router)["_events"]] | null {
|
|
@@ -332,6 +392,12 @@ export class IORoom<
|
|
|
332
392
|
return procedure.config.input ? procedure.config.input.parse(message.data) : message.data;
|
|
333
393
|
}
|
|
334
394
|
|
|
395
|
+
private async _getSessions() {
|
|
396
|
+
const promises = Array.from(this._sessions.values()).map(async (pluvWs) => await pluvWs.getSession());
|
|
397
|
+
|
|
398
|
+
return await Promise.all(promises);
|
|
399
|
+
}
|
|
400
|
+
|
|
335
401
|
private async _initialize(): Promise<void> {
|
|
336
402
|
this._logDebug(`${colors.blue(`Initializing room ${this.id}:`)}`);
|
|
337
403
|
|
|
@@ -341,14 +407,15 @@ export class IORoom<
|
|
|
341
407
|
this._uninitialize = null;
|
|
342
408
|
}
|
|
343
409
|
|
|
344
|
-
const pubSubId = await this._platform.pubSub.subscribe(
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
410
|
+
const pubSubId = await this._platform.pubSub.subscribe(
|
|
411
|
+
this.id,
|
|
412
|
+
async ({ options = {}, ...message }): Promise<void> => {
|
|
413
|
+
const sessionId = message.connectionId;
|
|
414
|
+
const user = message.user;
|
|
349
415
|
|
|
350
|
-
|
|
351
|
-
|
|
416
|
+
await this._sendMessage(message, { sessionId, user }, options);
|
|
417
|
+
},
|
|
418
|
+
);
|
|
352
419
|
|
|
353
420
|
this._uninitialize = async () => {
|
|
354
421
|
this._platform.pubSub.unsubscribe(pubSubId);
|
|
@@ -372,29 +439,27 @@ export class IORoom<
|
|
|
372
439
|
this._debug && console.log(...data);
|
|
373
440
|
}
|
|
374
441
|
|
|
375
|
-
private _onClose(
|
|
442
|
+
private _onClose(webSocket: AbstractWebSocket): () => void {
|
|
376
443
|
return (): void => {
|
|
377
444
|
if (!this._uninitialize) return;
|
|
378
445
|
|
|
379
|
-
|
|
446
|
+
webSocket.state.quit = true;
|
|
380
447
|
|
|
381
|
-
this._logDebug(`${colors.blue(`
|
|
382
|
-
this._sessions.delete(
|
|
448
|
+
this._logDebug(`${colors.blue(`Unregistering connection for room ${this.id}:`)} ${webSocket.sessionId}`);
|
|
449
|
+
this._sessions.delete(webSocket.sessionId);
|
|
383
450
|
|
|
384
|
-
this._platform.persistance.deleteUser(this.id,
|
|
385
|
-
this._broadcast({
|
|
451
|
+
this._platform.persistance.deleteUser(this.id, webSocket.sessionId).finally(async () => {
|
|
452
|
+
await this._broadcast({
|
|
386
453
|
message: {
|
|
387
454
|
type: "$EXIT",
|
|
388
|
-
data: { sessionId:
|
|
455
|
+
data: { sessionId: webSocket.sessionId },
|
|
389
456
|
},
|
|
390
|
-
senderId:
|
|
457
|
+
senderId: webSocket.sessionId,
|
|
391
458
|
});
|
|
392
459
|
|
|
393
|
-
callback?.();
|
|
394
|
-
|
|
395
460
|
const size = this.getSize();
|
|
396
461
|
|
|
397
|
-
this._logDebug(`${colors.blue(`Unregistered connection for room ${this.id}:`)} ${
|
|
462
|
+
this._logDebug(`${colors.blue(`Unregistered connection for room ${this.id}:`)} ${webSocket.sessionId}`);
|
|
398
463
|
this._logDebug(`${colors.blue(`Room ${this.id} size:`)} ${size}`);
|
|
399
464
|
|
|
400
465
|
if (size) return;
|
|
@@ -404,23 +469,29 @@ export class IORoom<
|
|
|
404
469
|
};
|
|
405
470
|
}
|
|
406
471
|
|
|
407
|
-
private _onMessage(
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
472
|
+
private _onMessage(webSocket: AbstractWebSocket): (event: AbstractMessageEvent) => void {
|
|
473
|
+
return async (event: AbstractMessageEvent): Promise<void> => {
|
|
474
|
+
const pluvWs = this._getAbstractWs(webSocket as WebSocketType<TPlatform>);
|
|
475
|
+
|
|
476
|
+
if (!pluvWs) throw new Error("Could not get session");
|
|
477
|
+
|
|
478
|
+
const [session, sessions] = await Promise.all([
|
|
479
|
+
this._getSession(pluvWs as WebSocketType<TPlatform>),
|
|
480
|
+
this._getSessions(),
|
|
481
|
+
]);
|
|
482
|
+
|
|
412
483
|
const baseContext: EventResolverContext<TPlatform, TAuthorize, TContext> = {
|
|
413
484
|
context: this._context,
|
|
414
485
|
doc: this._doc,
|
|
415
486
|
room: this.id,
|
|
416
487
|
session,
|
|
417
|
-
sessions
|
|
488
|
+
sessions,
|
|
418
489
|
};
|
|
419
490
|
|
|
420
491
|
if (!this._uninitialize) return;
|
|
421
492
|
|
|
422
|
-
if (
|
|
423
|
-
|
|
493
|
+
if (pluvWs.state.quit) {
|
|
494
|
+
pluvWs.close(1011, "WebSocket broken.");
|
|
424
495
|
|
|
425
496
|
return;
|
|
426
497
|
}
|
|
@@ -438,9 +509,10 @@ export class IORoom<
|
|
|
438
509
|
try {
|
|
439
510
|
inputs = this._getProcedureInputs(message);
|
|
440
511
|
} catch (error) {
|
|
441
|
-
|
|
512
|
+
pluvWs.handleError({
|
|
442
513
|
error,
|
|
443
514
|
message: "Invalid input",
|
|
515
|
+
room: this.id,
|
|
444
516
|
session,
|
|
445
517
|
});
|
|
446
518
|
|
|
@@ -450,30 +522,32 @@ export class IORoom<
|
|
|
450
522
|
const extendedContext: EventResolverContext<
|
|
451
523
|
TPlatform,
|
|
452
524
|
TAuthorize,
|
|
453
|
-
TContext &
|
|
454
|
-
> = {
|
|
455
|
-
...baseContext,
|
|
456
|
-
context: { ...this._context, ...platformEventContext },
|
|
457
|
-
};
|
|
525
|
+
TContext & InferRoomContextType<TPlatform>
|
|
526
|
+
> = { ...baseContext, context: { ...this._context } };
|
|
458
527
|
|
|
459
528
|
Promise.all([
|
|
460
529
|
procedure.config.broadcast?.(inputs, extendedContext),
|
|
461
530
|
procedure.config.self?.(inputs, extendedContext),
|
|
462
531
|
procedure.config.sync?.(inputs, baseContext),
|
|
463
|
-
]).then(([broadcast, self, sync]) => {
|
|
532
|
+
]).then(async ([broadcast, self, sync]) => {
|
|
533
|
+
const sessionId = session.id;
|
|
534
|
+
const user = session.user;
|
|
535
|
+
|
|
464
536
|
if (broadcast) {
|
|
465
537
|
Object.entries(broadcast).forEach(([type, data]: any) => {
|
|
466
538
|
this._broadcast({
|
|
467
539
|
message: { data, type },
|
|
468
|
-
senderId:
|
|
540
|
+
senderId: sessionId,
|
|
469
541
|
});
|
|
470
542
|
});
|
|
471
543
|
}
|
|
472
544
|
|
|
473
545
|
if (self) {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
546
|
+
await Promise.all(
|
|
547
|
+
Object.entries(self).map(async ([type, data]) => {
|
|
548
|
+
await this._sendSelfMessage({ data, type }, { sessionId, user });
|
|
549
|
+
}),
|
|
550
|
+
);
|
|
477
551
|
}
|
|
478
552
|
|
|
479
553
|
if (sync) {
|
|
@@ -481,13 +555,15 @@ export class IORoom<
|
|
|
481
555
|
connectionId: session.id,
|
|
482
556
|
options: { type: "sync" },
|
|
483
557
|
room: this.id,
|
|
484
|
-
user
|
|
558
|
+
user,
|
|
485
559
|
...message,
|
|
486
560
|
});
|
|
487
561
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
562
|
+
await Promise.all(
|
|
563
|
+
Object.entries(sync).map(async ([type, data]) => {
|
|
564
|
+
await this._sendSelfMessage({ data, type }, { sessionId, user });
|
|
565
|
+
}),
|
|
566
|
+
);
|
|
491
567
|
}
|
|
492
568
|
});
|
|
493
569
|
};
|
|
@@ -507,19 +583,19 @@ export class IORoom<
|
|
|
507
583
|
}
|
|
508
584
|
}
|
|
509
585
|
|
|
510
|
-
private _sendMessage(
|
|
586
|
+
private async _sendMessage(
|
|
511
587
|
message: EventMessage<string, any>,
|
|
512
588
|
sender: SendMessageSender | null,
|
|
513
589
|
options: SendMessageOptions = {},
|
|
514
|
-
): void {
|
|
590
|
+
): Promise<void> {
|
|
515
591
|
switch (options.type) {
|
|
516
592
|
case "self": {
|
|
517
|
-
this._sendSelfMessage(message, sender);
|
|
593
|
+
await this._sendSelfMessage(message, sender);
|
|
518
594
|
|
|
519
595
|
return;
|
|
520
596
|
}
|
|
521
597
|
case "sync": {
|
|
522
|
-
this._sendSyncMessage(message, sender);
|
|
598
|
+
await this._sendSyncMessage(message, sender);
|
|
523
599
|
|
|
524
600
|
return;
|
|
525
601
|
}
|
|
@@ -527,63 +603,73 @@ export class IORoom<
|
|
|
527
603
|
default: {
|
|
528
604
|
const sessionIds = options.sessionIds;
|
|
529
605
|
|
|
530
|
-
this._sendBroadcastMessage(message, sender, sessionIds);
|
|
606
|
+
await this._sendBroadcastMessage(message, sender, sessionIds);
|
|
531
607
|
}
|
|
532
608
|
}
|
|
533
609
|
}
|
|
534
610
|
|
|
535
|
-
private _sendBroadcastMessage(
|
|
611
|
+
private async _sendBroadcastMessage(
|
|
536
612
|
message: EventMessage<string, any>,
|
|
537
613
|
sender: SendMessageSender | null,
|
|
538
614
|
sessionIds?: readonly string[],
|
|
539
|
-
): void {
|
|
540
|
-
const
|
|
615
|
+
): Promise<void> {
|
|
616
|
+
const { data, type } = message;
|
|
617
|
+
const connectionId = sender?.sessionId ?? null;
|
|
618
|
+
const room = this.id;
|
|
541
619
|
|
|
542
|
-
const
|
|
620
|
+
const webSockets =
|
|
543
621
|
sessionIds?.reduce((dict, id) => {
|
|
544
|
-
const
|
|
622
|
+
const pluvWs = this._sessions.get(id);
|
|
545
623
|
|
|
546
|
-
return
|
|
547
|
-
}, new Map<string,
|
|
624
|
+
return pluvWs ? dict.set(id, pluvWs) : dict;
|
|
625
|
+
}, new Map<string, AbstractWebSocket>()) ?? this._sessions;
|
|
548
626
|
|
|
549
|
-
Array.from(
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
...message,
|
|
555
|
-
});
|
|
627
|
+
const promises = Array.from(webSockets.values()).map(async (pluvWs) => {
|
|
628
|
+
const session = await pluvWs.getSession();
|
|
629
|
+
const user = session.user ?? null;
|
|
630
|
+
|
|
631
|
+
pluvWs.sendMessage({ connectionId, data, room, type, user });
|
|
556
632
|
});
|
|
633
|
+
|
|
634
|
+
await Promise.all(promises);
|
|
557
635
|
}
|
|
558
636
|
|
|
559
|
-
private _sendSelfMessage(
|
|
560
|
-
|
|
637
|
+
private async _sendSelfMessage(
|
|
638
|
+
message: EventMessage<string, any>,
|
|
639
|
+
sender: SendMessageSender | null,
|
|
640
|
+
): Promise<void> {
|
|
641
|
+
const senderId = sender?.sessionId ?? null;
|
|
642
|
+
|
|
643
|
+
if (typeof senderId !== "string") return;
|
|
561
644
|
|
|
562
|
-
|
|
645
|
+
const pluvWs = this._sessions.get(senderId);
|
|
563
646
|
|
|
564
|
-
|
|
647
|
+
if (!pluvWs) return;
|
|
648
|
+
if (message.type === "$PONG") await this._emitQuitters();
|
|
565
649
|
|
|
566
|
-
|
|
650
|
+
const session = await pluvWs.getSession();
|
|
651
|
+
const user = session.user ?? null;
|
|
567
652
|
|
|
568
|
-
|
|
569
|
-
connectionId:
|
|
653
|
+
pluvWs.sendMessage({
|
|
654
|
+
connectionId: senderId,
|
|
570
655
|
room: this.id,
|
|
571
|
-
user
|
|
656
|
+
user,
|
|
572
657
|
...message,
|
|
573
658
|
});
|
|
574
659
|
}
|
|
575
660
|
|
|
576
|
-
private _sendSyncMessage(
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
661
|
+
private async _sendSyncMessage(
|
|
662
|
+
message: EventMessage<string, any>,
|
|
663
|
+
sender: SendMessageSender | null,
|
|
664
|
+
): Promise<void> {
|
|
665
|
+
if (!sender) return;
|
|
580
666
|
|
|
581
667
|
const context: EventResolverContext<TPlatform, TAuthorize, TContext> = {
|
|
582
668
|
context: this._context,
|
|
583
669
|
doc: this._doc,
|
|
584
670
|
room: this.id,
|
|
585
671
|
session: null,
|
|
586
|
-
sessions: this.
|
|
672
|
+
sessions: await this._getSessions(),
|
|
587
673
|
};
|
|
588
674
|
|
|
589
675
|
const resolver = this._getProcedure(message)?.config.sync;
|
|
@@ -605,7 +691,7 @@ export class IORoom<
|
|
|
605
691
|
const data = output[type] ?? {};
|
|
606
692
|
|
|
607
693
|
this._platform.pubSub.publish(this.id, {
|
|
608
|
-
connectionId:
|
|
694
|
+
connectionId: sender.sessionId,
|
|
609
695
|
data,
|
|
610
696
|
options: { type: "self" },
|
|
611
697
|
room: this.id,
|