@pluv/io 0.17.2 → 0.18.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 +9 -9
- package/CHANGELOG.md +136 -0
- package/dist/index.d.mts +121 -57
- package/dist/index.d.ts +121 -57
- package/dist/index.js +300 -284
- package/dist/index.mjs +297 -284
- package/package.json +6 -6
- package/src/AbstractPersistance.ts +4 -16
- package/src/AbstractPlatform.ts +4 -14
- package/src/AbstractPubSub.ts +1 -4
- package/src/AbstractWebSocket.ts +6 -21
- package/src/IORoom.ts +117 -270
- package/src/Persistance.ts +3 -11
- package/src/PluvIO.ts +156 -473
- package/src/PluvProcedure.ts +203 -0
- package/src/PluvRouter.ts +46 -0
- package/src/PluvServer.ts +182 -0
- package/src/PubSub.ts +6 -18
- package/src/authorize.ts +5 -22
- package/src/createIO.ts +10 -49
- package/src/index.ts +9 -13
- package/src/types.ts +49 -101
- package/src/version.generated.ts +1 -1
package/src/IORoom.ts
CHANGED
|
@@ -4,7 +4,6 @@ import type {
|
|
|
4
4
|
BaseIOAuthorize,
|
|
5
5
|
BaseIOEventRecord,
|
|
6
6
|
EventMessage,
|
|
7
|
-
EventRecord,
|
|
8
7
|
IOAuthorize,
|
|
9
8
|
IOLike,
|
|
10
9
|
Id,
|
|
@@ -24,62 +23,44 @@ import type {
|
|
|
24
23
|
InferPlatformWebSocketType,
|
|
25
24
|
} from "./AbstractPlatform";
|
|
26
25
|
import { AbstractMessageEvent } from "./AbstractWebSocket";
|
|
26
|
+
import type { PluvRouter, PluvRouterEventConfig } from "./PluvRouter";
|
|
27
27
|
import { authorize } from "./authorize";
|
|
28
|
-
import type {
|
|
29
|
-
EventResolver,
|
|
30
|
-
EventResolverContext,
|
|
31
|
-
EventResolverObject,
|
|
32
|
-
InferEventConfig,
|
|
33
|
-
SendMessageOptions,
|
|
34
|
-
WebSocketSession,
|
|
35
|
-
} from "./types";
|
|
28
|
+
import type { EventResolverContext, IORoomListenerEvent, SendMessageOptions, WebSocketSession } from "./types";
|
|
36
29
|
|
|
37
30
|
const PING_TIMEOUT_MS = 30_000;
|
|
38
31
|
|
|
39
|
-
type BroadcastMessage<TIO extends IORoom<any, any, any, any
|
|
32
|
+
type BroadcastMessage<TIO extends IORoom<any, any, any, any>> =
|
|
40
33
|
| InferEventMessage<InferIOInput<TIO>>
|
|
41
34
|
| InferEventMessage<BaseIOEventRecord<InferIOAuthorize<TIO>>>;
|
|
42
35
|
|
|
43
|
-
interface BroadcastParams<TIO extends IORoom<any, any, any, any
|
|
36
|
+
interface BroadcastParams<TIO extends IORoom<any, any, any, any>> {
|
|
44
37
|
message: BroadcastMessage<TIO>;
|
|
45
38
|
senderId?: string;
|
|
46
39
|
}
|
|
47
40
|
|
|
48
|
-
export type IORoomListenerEvent<TPlatform extends AbstractPlatform> = {
|
|
49
|
-
room: string;
|
|
50
|
-
encodedState: string;
|
|
51
|
-
} & InferPlatformRoomContextType<TPlatform>;
|
|
52
|
-
|
|
53
41
|
interface IORoomListeners<TPlatform extends AbstractPlatform> {
|
|
54
42
|
onDestroy: (event: IORoomListenerEvent<TPlatform>) => void;
|
|
55
43
|
}
|
|
56
44
|
|
|
45
|
+
export type BroadcastProxy<TIO extends IORoom<any, any, any, any>> = (<TEvent extends keyof InferIOInput<TIO>>(
|
|
46
|
+
event: TEvent,
|
|
47
|
+
data: Id<InferIOInput<TIO>[TEvent]>,
|
|
48
|
+
) => Promise<void>) & {
|
|
49
|
+
[event in keyof InferIOInput<TIO>]: (data: Id<InferIOInput<TIO>>[event]) => Promise<void>;
|
|
50
|
+
};
|
|
51
|
+
|
|
57
52
|
export type IORoomConfig<
|
|
58
53
|
TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>,
|
|
59
|
-
TAuthorize extends IOAuthorize<
|
|
60
|
-
any,
|
|
61
|
-
any,
|
|
62
|
-
InferPlatformRoomContextType<TPlatform>
|
|
63
|
-
> = BaseIOAuthorize,
|
|
54
|
+
TAuthorize extends IOAuthorize<any, any, InferPlatformRoomContextType<TPlatform>> = BaseIOAuthorize,
|
|
64
55
|
TContext extends JsonObject = {},
|
|
65
|
-
|
|
66
|
-
TOutputBroadcast extends EventRecord<string, any> = {},
|
|
67
|
-
TOutputSelf extends EventRecord<string, any> = {},
|
|
68
|
-
TOutputSync extends EventRecord<string, any> = {},
|
|
56
|
+
TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext> = {},
|
|
69
57
|
> = Partial<IORoomListeners<TPlatform>> & {
|
|
70
58
|
authorize?: TAuthorize;
|
|
71
59
|
context: TContext & InferPlatformRoomContextType<TPlatform>;
|
|
72
60
|
crdt?: { doc: (value: any) => AbstractCrdtDocFactory<any> };
|
|
73
61
|
debug: boolean;
|
|
74
|
-
events: InferEventConfig<
|
|
75
|
-
TPlatform,
|
|
76
|
-
TContext,
|
|
77
|
-
TInput,
|
|
78
|
-
TOutputBroadcast,
|
|
79
|
-
TOutputSelf,
|
|
80
|
-
TOutputSync
|
|
81
|
-
>;
|
|
82
62
|
platform: TPlatform;
|
|
63
|
+
router: PluvRouter<TPlatform, TAuthorize, TContext, TEvents>;
|
|
83
64
|
};
|
|
84
65
|
|
|
85
66
|
interface SendMessageSender {
|
|
@@ -94,69 +75,55 @@ export type WebsocketRegisterOptions<TPlatform extends AbstractPlatform> = {
|
|
|
94
75
|
|
|
95
76
|
export class IORoom<
|
|
96
77
|
TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>,
|
|
97
|
-
TAuthorize extends IOAuthorize<
|
|
98
|
-
any,
|
|
99
|
-
any,
|
|
100
|
-
InferPlatformRoomContextType<TPlatform>
|
|
101
|
-
> = BaseIOAuthorize,
|
|
78
|
+
TAuthorize extends IOAuthorize<any, any, InferPlatformRoomContextType<TPlatform>> = BaseIOAuthorize,
|
|
102
79
|
TContext extends Record<string, any> = {},
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
TOutputSelf extends EventRecord<string, any> = {},
|
|
106
|
-
TOutputSync extends EventRecord<string, any> = {},
|
|
107
|
-
> implements
|
|
108
|
-
IOLike<TAuthorize, TInput, TOutputBroadcast, TOutputSelf, TOutputSync>
|
|
80
|
+
TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext> = {},
|
|
81
|
+
> implements IOLike<TAuthorize, TEvents>
|
|
109
82
|
{
|
|
110
|
-
private readonly _context: TContext &
|
|
111
|
-
InferPlatformRoomContextType<TPlatform>;
|
|
83
|
+
private readonly _context: TContext & InferPlatformRoomContextType<TPlatform>;
|
|
112
84
|
private readonly _debug: boolean;
|
|
113
85
|
private readonly _docFactory: AbstractCrdtDocFactory<any>;
|
|
114
86
|
private readonly _platform: TPlatform;
|
|
115
87
|
|
|
116
88
|
private _doc: AbstractCrdtDoc<any>;
|
|
117
89
|
private _listeners: IORoomListeners<TPlatform>;
|
|
118
|
-
private _sessions = new Map<string, WebSocketSession
|
|
90
|
+
private _sessions = new Map<string, WebSocketSession<TAuthorize>>();
|
|
119
91
|
private _uninitialize: (() => Promise<void>) | null = null;
|
|
120
92
|
|
|
121
93
|
readonly _authorize: TAuthorize | null = null;
|
|
122
|
-
readonly
|
|
123
|
-
TPlatform,
|
|
124
|
-
TContext,
|
|
125
|
-
TInput,
|
|
126
|
-
TOutputBroadcast,
|
|
127
|
-
TOutputSelf,
|
|
128
|
-
TOutputSync
|
|
129
|
-
>;
|
|
94
|
+
readonly _router: PluvRouter<TPlatform, TAuthorize, TContext, TEvents>;
|
|
130
95
|
|
|
131
96
|
readonly id: string;
|
|
132
97
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
98
|
+
public get _events() {
|
|
99
|
+
return this._router._events;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public get broadcast(): BroadcastProxy<this> {
|
|
103
|
+
const _broadcast = <TEvent extends keyof InferIOInput<this>>(
|
|
104
|
+
event: TEvent,
|
|
105
|
+
data: Id<InferIOInput<this>[TEvent]>,
|
|
106
|
+
): Promise<void> => {
|
|
107
|
+
const message = { type: event, data } as BroadcastMessage<this>;
|
|
108
|
+
|
|
109
|
+
return Promise.resolve(this._broadcast({ message }));
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return new Proxy(_broadcast, {
|
|
113
|
+
get(fn, prop) {
|
|
114
|
+
return (data: any) => fn(prop as any, data);
|
|
115
|
+
},
|
|
116
|
+
}) as BroadcastProxy<this>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
constructor(id: string, config: IORoomConfig<TPlatform, TAuthorize, TContext, TEvents>) {
|
|
120
|
+
const { authorize, context, crdt = noop, debug, onDestroy, platform, router } = config;
|
|
154
121
|
|
|
155
122
|
this._context = context;
|
|
156
123
|
this._debug = debug;
|
|
157
124
|
this._docFactory = crdt.doc(() => ({}));
|
|
158
125
|
this._doc = this._docFactory.getEmpty();
|
|
159
|
-
this.
|
|
126
|
+
this._router = router;
|
|
160
127
|
this._platform = platform;
|
|
161
128
|
|
|
162
129
|
this.id = id;
|
|
@@ -168,15 +135,6 @@ export class IORoom<
|
|
|
168
135
|
if (authorize) this._authorize = authorize;
|
|
169
136
|
}
|
|
170
137
|
|
|
171
|
-
public broadcast<TEvent extends keyof InferIOInput<this>>(
|
|
172
|
-
event: TEvent,
|
|
173
|
-
data: Id<InferIOInput<this>[TEvent]>,
|
|
174
|
-
): Promise<void> {
|
|
175
|
-
const message = { type: event, data } as BroadcastMessage<this>;
|
|
176
|
-
|
|
177
|
-
return Promise.resolve(this._broadcast({ message }));
|
|
178
|
-
}
|
|
179
|
-
|
|
180
138
|
public getSize(): number {
|
|
181
139
|
const currentTime = new Date().getTime();
|
|
182
140
|
|
|
@@ -189,9 +147,7 @@ export class IORoom<
|
|
|
189
147
|
return Array.from(this._sessions.values()).reduce((count, session) => {
|
|
190
148
|
if (session.quit) return count;
|
|
191
149
|
|
|
192
|
-
return currentTime - session.timers.ping > PING_TIMEOUT_MS
|
|
193
|
-
? count
|
|
194
|
-
: count + 1;
|
|
150
|
+
return currentTime - session.timers.ping > PING_TIMEOUT_MS ? count : count + 1;
|
|
195
151
|
}, 0);
|
|
196
152
|
}
|
|
197
153
|
|
|
@@ -200,9 +156,8 @@ export class IORoom<
|
|
|
200
156
|
options: WebsocketRegisterOptions<TPlatform>,
|
|
201
157
|
): Promise<void> {
|
|
202
158
|
const { token, ..._platformEventContext } = options;
|
|
203
|
-
const platformEventContext =
|
|
204
|
-
|
|
205
|
-
InferPlatformEventContextType<TPlatform>;
|
|
159
|
+
const platformEventContext = _platformEventContext as InferPlatformRoomContextType<TPlatform> &
|
|
160
|
+
InferPlatformEventContextType<TPlatform>;
|
|
206
161
|
|
|
207
162
|
if (!this._uninitialize) await this._initialize();
|
|
208
163
|
|
|
@@ -215,11 +170,7 @@ export class IORoom<
|
|
|
215
170
|
userId: user?.id ?? null,
|
|
216
171
|
});
|
|
217
172
|
|
|
218
|
-
this._logDebug(
|
|
219
|
-
`${colors.blue(
|
|
220
|
-
`Registering connection for room ${this.id}:`,
|
|
221
|
-
)} ${sessionId}`,
|
|
222
|
-
);
|
|
173
|
+
this._logDebug(`${colors.blue(`Registering connection for room ${this.id}:`)} ${sessionId}`);
|
|
223
174
|
|
|
224
175
|
const uninitializeWs = await pluvWs.initialize();
|
|
225
176
|
const ioAuthorize = this._getIOAuthorize(options);
|
|
@@ -227,17 +178,10 @@ export class IORoom<
|
|
|
227
178
|
const isUnauthorized = !!ioAuthorize?.required && !user;
|
|
228
179
|
// There is an attempt for multiple of the same identities. Probably malicious.
|
|
229
180
|
const isTokenInUse =
|
|
230
|
-
!!user &&
|
|
231
|
-
Array.from(this._sessions.values()).some(
|
|
232
|
-
(session) => session.user?.id === user.id,
|
|
233
|
-
);
|
|
181
|
+
!!user && Array.from(this._sessions.values()).some((session) => session.user?.id === user.id);
|
|
234
182
|
|
|
235
183
|
if (isUnauthorized || isTokenInUse) {
|
|
236
|
-
this._logDebug(
|
|
237
|
-
`${colors.blue(
|
|
238
|
-
"Authorization failed for connection:",
|
|
239
|
-
)} ${sessionId}`,
|
|
240
|
-
);
|
|
184
|
+
this._logDebug(`${colors.blue("Authorization failed for connection:")} ${sessionId}`);
|
|
241
185
|
|
|
242
186
|
pluvWs.handleError({ error: new Error("Not authorized") });
|
|
243
187
|
pluvWs.close(3000, "WebSocket unauthorized.");
|
|
@@ -246,7 +190,7 @@ export class IORoom<
|
|
|
246
190
|
}
|
|
247
191
|
|
|
248
192
|
const currentTime = new Date().getTime();
|
|
249
|
-
const session: WebSocketSession = {
|
|
193
|
+
const session: WebSocketSession<TAuthorize> = {
|
|
250
194
|
id: sessionId,
|
|
251
195
|
presence: null,
|
|
252
196
|
quit: false,
|
|
@@ -260,16 +204,10 @@ export class IORoom<
|
|
|
260
204
|
|
|
261
205
|
this._sessions.set(session.id, session);
|
|
262
206
|
|
|
263
|
-
await this._platform.persistance.addUser(
|
|
264
|
-
this.id,
|
|
265
|
-
sessionId,
|
|
266
|
-
user ?? {},
|
|
267
|
-
);
|
|
207
|
+
await this._platform.persistance.addUser(this.id, sessionId, user ?? {});
|
|
268
208
|
|
|
269
209
|
const onClose = this._onClose(session, uninitializeWs).bind(this);
|
|
270
|
-
const onMessage = this._onMessage(session, platformEventContext).bind(
|
|
271
|
-
this,
|
|
272
|
-
);
|
|
210
|
+
const onMessage = this._onMessage(session, platformEventContext).bind(this);
|
|
273
211
|
|
|
274
212
|
pluvWs.addEventListener("close", onClose);
|
|
275
213
|
pluvWs.addEventListener("error", onClose);
|
|
@@ -277,11 +215,7 @@ export class IORoom<
|
|
|
277
215
|
|
|
278
216
|
this._emitRegistered(session);
|
|
279
217
|
|
|
280
|
-
this._logDebug(
|
|
281
|
-
`${colors.blue(
|
|
282
|
-
`Registered connection for room ${this.id}:`,
|
|
283
|
-
)} ${sessionId}`,
|
|
284
|
-
);
|
|
218
|
+
this._logDebug(`${colors.blue(`Registered connection for room ${this.id}:`)} ${sessionId}`);
|
|
285
219
|
|
|
286
220
|
const size = this.getSize();
|
|
287
221
|
|
|
@@ -295,9 +229,7 @@ export class IORoom<
|
|
|
295
229
|
const sender = senderId ? this._sessions.get(senderId) : null;
|
|
296
230
|
|
|
297
231
|
const quitters = Array.from(this._sessions.values()).filter(
|
|
298
|
-
(session) =>
|
|
299
|
-
session.quit ||
|
|
300
|
-
currentTime - session.timers.ping > PING_TIMEOUT_MS,
|
|
232
|
+
(session) => session.quit || currentTime - session.timers.ping > PING_TIMEOUT_MS,
|
|
301
233
|
);
|
|
302
234
|
|
|
303
235
|
quitters.forEach((session) => {
|
|
@@ -314,7 +246,7 @@ export class IORoom<
|
|
|
314
246
|
});
|
|
315
247
|
}
|
|
316
248
|
|
|
317
|
-
private _emitQuitters(quitters: readonly WebSocketSession[]): void {
|
|
249
|
+
private _emitQuitters(quitters: readonly WebSocketSession<TAuthorize>[]): void {
|
|
318
250
|
quitters.forEach((quitter) => {
|
|
319
251
|
this._broadcast({
|
|
320
252
|
message: {
|
|
@@ -326,7 +258,7 @@ export class IORoom<
|
|
|
326
258
|
});
|
|
327
259
|
}
|
|
328
260
|
|
|
329
|
-
private _emitRegistered(session: WebSocketSession): void {
|
|
261
|
+
private _emitRegistered(session: WebSocketSession<TAuthorize>): void {
|
|
330
262
|
this._sendSelfMessage(
|
|
331
263
|
{
|
|
332
264
|
type: "$REGISTERED",
|
|
@@ -351,11 +283,11 @@ export class IORoom<
|
|
|
351
283
|
private async _getAuthorizedUser(
|
|
352
284
|
token: Maybe<string>,
|
|
353
285
|
context: InferPlatformRoomContextType<TPlatform>,
|
|
354
|
-
): Promise<InferIOAuthorizeUser<InferIOAuthorize<this
|
|
286
|
+
): Promise<InferIOAuthorizeUser<InferIOAuthorize<this>>> {
|
|
355
287
|
const ioAuthorize = this._getIOAuthorize(context);
|
|
356
288
|
|
|
357
|
-
if (!ioAuthorize) return null
|
|
358
|
-
if (!token) return null
|
|
289
|
+
if (!ioAuthorize) return null as InferIOAuthorizeUser<InferIOAuthorize<this>>;
|
|
290
|
+
if (!token) return null as InferIOAuthorizeUser<InferIOAuthorize<this>>;
|
|
359
291
|
|
|
360
292
|
const payload = await authorize({
|
|
361
293
|
platform: this._platform,
|
|
@@ -366,99 +298,38 @@ export class IORoom<
|
|
|
366
298
|
this._logDebug(colors.blue("Could not decode token:"));
|
|
367
299
|
this._logDebug(token);
|
|
368
300
|
|
|
369
|
-
return null
|
|
301
|
+
return null as InferIOAuthorizeUser<InferIOAuthorize<this>>;
|
|
370
302
|
}
|
|
371
303
|
|
|
372
304
|
if (payload.room !== this.id) {
|
|
373
|
-
this._logDebug(
|
|
374
|
-
colors.blue(`Token is not authorized for room ${this.id}:`),
|
|
375
|
-
);
|
|
305
|
+
this._logDebug(colors.blue(`Token is not authorized for room ${this.id}:`));
|
|
376
306
|
this._logDebug(colors.blue("Received:"), payload.room);
|
|
377
307
|
this._logDebug(token);
|
|
378
308
|
|
|
379
|
-
return null
|
|
309
|
+
return null as InferIOAuthorizeUser<InferIOAuthorize<this>>;
|
|
380
310
|
}
|
|
381
311
|
|
|
382
312
|
try {
|
|
383
313
|
return ioAuthorize.user.parse(payload.user) ?? null;
|
|
384
314
|
} catch {
|
|
385
|
-
this._logDebug(
|
|
386
|
-
`${colors.blue("Token fails validation:")} ${token}`,
|
|
387
|
-
);
|
|
315
|
+
this._logDebug(`${colors.blue("Token fails validation:")} ${token}`);
|
|
388
316
|
|
|
389
|
-
return null
|
|
317
|
+
return null as InferIOAuthorizeUser<InferIOAuthorize<this>>;
|
|
390
318
|
}
|
|
391
319
|
}
|
|
392
320
|
|
|
393
|
-
private
|
|
321
|
+
private _getProcedure(
|
|
394
322
|
message: EventMessage<string, any>,
|
|
395
|
-
):
|
|
396
|
-
|
|
397
|
-
TPlatform,
|
|
398
|
-
TContext,
|
|
399
|
-
TInput,
|
|
400
|
-
TOutputBroadcast,
|
|
401
|
-
TOutputSelf,
|
|
402
|
-
TOutputSync
|
|
403
|
-
>[keyof TInput]
|
|
404
|
-
| null {
|
|
405
|
-
return this._events[message.type as keyof TInput] ?? null;
|
|
323
|
+
): (typeof this._router)["_events"][keyof (typeof this._router)["_events"]] | null {
|
|
324
|
+
return this._router._events[message.type as keyof (typeof this._router)["_events"]] ?? null;
|
|
406
325
|
}
|
|
407
326
|
|
|
408
|
-
private
|
|
409
|
-
|
|
410
|
-
): TInput[string] {
|
|
411
|
-
const eventConfig = this._getEventConfig(message);
|
|
412
|
-
|
|
413
|
-
if (!eventConfig) return message.data;
|
|
327
|
+
private _getProcedureInputs(message: EventMessage<string, any>): InferIOInput<this>[keyof TEvents] {
|
|
328
|
+
const procedure = this._getProcedure(message);
|
|
414
329
|
|
|
415
|
-
|
|
416
|
-
* !HACK
|
|
417
|
-
* @description If the config doesn't specify validation, we
|
|
418
|
-
* assume no input is to be expected, but pass through all values
|
|
419
|
-
* regardless. Leave this up to the implementer of the event.
|
|
420
|
-
* @author leedavidcs
|
|
421
|
-
* @date September 25, 2022
|
|
422
|
-
*/
|
|
423
|
-
return eventConfig.input
|
|
424
|
-
? (eventConfig.input.parse(message.data) as TInput[string])
|
|
425
|
-
: message.data;
|
|
426
|
-
}
|
|
330
|
+
if (!procedure) return message.data;
|
|
427
331
|
|
|
428
|
-
|
|
429
|
-
message: EventMessage<string, any>,
|
|
430
|
-
): EventResolverObject<
|
|
431
|
-
TPlatform,
|
|
432
|
-
TContext,
|
|
433
|
-
TInput[string],
|
|
434
|
-
TOutputBroadcast,
|
|
435
|
-
TOutputSelf,
|
|
436
|
-
TOutputSync
|
|
437
|
-
> {
|
|
438
|
-
const eventConfig = this._getEventConfig(message);
|
|
439
|
-
|
|
440
|
-
if (!eventConfig) return {};
|
|
441
|
-
|
|
442
|
-
const resolver:
|
|
443
|
-
| EventResolver<
|
|
444
|
-
TContext &
|
|
445
|
-
InferPlatformRoomContextType<TPlatform> &
|
|
446
|
-
InferPlatformEventContextType<TPlatform>,
|
|
447
|
-
TInput[string],
|
|
448
|
-
TOutputBroadcast
|
|
449
|
-
>
|
|
450
|
-
| EventResolverObject<
|
|
451
|
-
TPlatform,
|
|
452
|
-
TContext,
|
|
453
|
-
TInput[string],
|
|
454
|
-
TOutputBroadcast,
|
|
455
|
-
TOutputSelf,
|
|
456
|
-
TOutputSync
|
|
457
|
-
> = eventConfig.resolver;
|
|
458
|
-
|
|
459
|
-
return typeof resolver === "function"
|
|
460
|
-
? { broadcast: resolver }
|
|
461
|
-
: { ...resolver };
|
|
332
|
+
return procedure.config.input ? procedure.config.input.parse(message.data) : message.data;
|
|
462
333
|
}
|
|
463
334
|
|
|
464
335
|
private async _initialize(): Promise<void> {
|
|
@@ -470,17 +341,14 @@ export class IORoom<
|
|
|
470
341
|
this._uninitialize = null;
|
|
471
342
|
}
|
|
472
343
|
|
|
473
|
-
const pubSubId = await this._platform.pubSub.subscribe(
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
user: message.user,
|
|
479
|
-
};
|
|
344
|
+
const pubSubId = await this._platform.pubSub.subscribe(this.id, ({ options = {}, ...message }) => {
|
|
345
|
+
const session = {
|
|
346
|
+
id: message.connectionId,
|
|
347
|
+
user: message.user,
|
|
348
|
+
};
|
|
480
349
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
);
|
|
350
|
+
this._sendMessage(message, session, options);
|
|
351
|
+
});
|
|
484
352
|
|
|
485
353
|
this._uninitialize = async () => {
|
|
486
354
|
this._platform.pubSub.unsubscribe(pubSubId);
|
|
@@ -504,59 +372,44 @@ export class IORoom<
|
|
|
504
372
|
this._debug && console.log(...data);
|
|
505
373
|
}
|
|
506
374
|
|
|
507
|
-
private _onClose(
|
|
508
|
-
session: WebSocketSession,
|
|
509
|
-
callback?: () => void,
|
|
510
|
-
): () => void {
|
|
375
|
+
private _onClose(session: WebSocketSession<TAuthorize>, callback?: () => void): () => void {
|
|
511
376
|
return (): void => {
|
|
512
377
|
if (!this._uninitialize) return;
|
|
513
378
|
|
|
514
379
|
session.quit = true;
|
|
515
380
|
|
|
516
|
-
this._logDebug(
|
|
517
|
-
`${colors.blue(
|
|
518
|
-
`(Unregistering connection for room ${this.id}:`,
|
|
519
|
-
)} ${session.id}`,
|
|
520
|
-
);
|
|
381
|
+
this._logDebug(`${colors.blue(`(Unregistering connection for room ${this.id}:`)} ${session.id}`);
|
|
521
382
|
this._sessions.delete(session.id);
|
|
522
383
|
|
|
523
|
-
this._platform.persistance
|
|
524
|
-
.
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
senderId: session.id,
|
|
532
|
-
});
|
|
384
|
+
this._platform.persistance.deleteUser(this.id, session.id).finally(() => {
|
|
385
|
+
this._broadcast({
|
|
386
|
+
message: {
|
|
387
|
+
type: "$EXIT",
|
|
388
|
+
data: { sessionId: session.id },
|
|
389
|
+
},
|
|
390
|
+
senderId: session.id,
|
|
391
|
+
});
|
|
533
392
|
|
|
534
|
-
|
|
393
|
+
callback?.();
|
|
535
394
|
|
|
536
|
-
|
|
395
|
+
const size = this.getSize();
|
|
537
396
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
`Unregistered connection for room ${this.id}:`,
|
|
541
|
-
)} ${session.id}`,
|
|
542
|
-
);
|
|
543
|
-
this._logDebug(
|
|
544
|
-
`${colors.blue(`Room ${this.id} size:`)} ${size}`,
|
|
545
|
-
);
|
|
397
|
+
this._logDebug(`${colors.blue(`Unregistered connection for room ${this.id}:`)} ${session.id}`);
|
|
398
|
+
this._logDebug(`${colors.blue(`Room ${this.id} size:`)} ${size}`);
|
|
546
399
|
|
|
547
|
-
|
|
400
|
+
if (size) return;
|
|
548
401
|
|
|
549
|
-
|
|
550
|
-
|
|
402
|
+
this._uninitialize?.();
|
|
403
|
+
});
|
|
551
404
|
};
|
|
552
405
|
}
|
|
553
406
|
|
|
554
407
|
private _onMessage(
|
|
555
|
-
session: WebSocketSession
|
|
408
|
+
session: WebSocketSession<TAuthorize>,
|
|
556
409
|
platformEventContext: InferPlatformEventContextType<TPlatform>,
|
|
557
410
|
): (event: AbstractMessageEvent) => void {
|
|
558
411
|
return (event: AbstractMessageEvent): void => {
|
|
559
|
-
const baseContext: EventResolverContext<TContext> = {
|
|
412
|
+
const baseContext: EventResolverContext<TPlatform, TAuthorize, TContext> = {
|
|
560
413
|
context: this._context,
|
|
561
414
|
doc: this._doc,
|
|
562
415
|
room: this.id,
|
|
@@ -576,12 +429,14 @@ export class IORoom<
|
|
|
576
429
|
|
|
577
430
|
if (!message) return;
|
|
578
431
|
|
|
579
|
-
const
|
|
432
|
+
const procedure = this._getProcedure(message);
|
|
433
|
+
|
|
434
|
+
if (!procedure) return;
|
|
580
435
|
|
|
581
|
-
let inputs:
|
|
436
|
+
let inputs: InferIOInput<this>[keyof TEvents];
|
|
582
437
|
|
|
583
438
|
try {
|
|
584
|
-
inputs = this.
|
|
439
|
+
inputs = this._getProcedureInputs(message);
|
|
585
440
|
} catch (error) {
|
|
586
441
|
session?.webSocket.handleError({
|
|
587
442
|
error,
|
|
@@ -593,18 +448,18 @@ export class IORoom<
|
|
|
593
448
|
}
|
|
594
449
|
|
|
595
450
|
const extendedContext: EventResolverContext<
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
451
|
+
TPlatform,
|
|
452
|
+
TAuthorize,
|
|
453
|
+
TContext & InferPlatformRoomContextType<TPlatform> & InferPlatformEventContextType<TPlatform>
|
|
599
454
|
> = {
|
|
600
455
|
...baseContext,
|
|
601
456
|
context: { ...this._context, ...platformEventContext },
|
|
602
457
|
};
|
|
603
458
|
|
|
604
459
|
Promise.all([
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
460
|
+
procedure.config.broadcast?.(inputs, extendedContext),
|
|
461
|
+
procedure.config.self?.(inputs, extendedContext),
|
|
462
|
+
procedure.config.sync?.(inputs, baseContext),
|
|
608
463
|
]).then(([broadcast, self, sync]) => {
|
|
609
464
|
if (broadcast) {
|
|
610
465
|
Object.entries(broadcast).forEach(([type, data]: any) => {
|
|
@@ -638,9 +493,7 @@ export class IORoom<
|
|
|
638
493
|
};
|
|
639
494
|
}
|
|
640
495
|
|
|
641
|
-
private _parseMessage(message: {
|
|
642
|
-
data: string | ArrayBuffer;
|
|
643
|
-
}): EventMessage<string, any> | null {
|
|
496
|
+
private _parseMessage(message: { data: string | ArrayBuffer }): EventMessage<string, any> | null {
|
|
644
497
|
try {
|
|
645
498
|
const parsed = this._platform.parseData(message.data);
|
|
646
499
|
|
|
@@ -691,7 +544,7 @@ export class IORoom<
|
|
|
691
544
|
const _session = this._sessions.get(id);
|
|
692
545
|
|
|
693
546
|
return _session ? dict.set(id, _session) : dict;
|
|
694
|
-
}, new Map<string, WebSocketSession
|
|
547
|
+
}, new Map<string, WebSocketSession<TAuthorize>>()) ?? this._sessions;
|
|
695
548
|
|
|
696
549
|
Array.from(sessions.values()).forEach((_session) => {
|
|
697
550
|
_session.webSocket.sendMessage({
|
|
@@ -703,10 +556,7 @@ export class IORoom<
|
|
|
703
556
|
});
|
|
704
557
|
}
|
|
705
558
|
|
|
706
|
-
private _sendSelfMessage(
|
|
707
|
-
message: EventMessage<string, any>,
|
|
708
|
-
sender: SendMessageSender | null,
|
|
709
|
-
): void {
|
|
559
|
+
private _sendSelfMessage(message: EventMessage<string, any>, sender: SendMessageSender | null): void {
|
|
710
560
|
const senderId = sender?.id;
|
|
711
561
|
|
|
712
562
|
if (!senderId) return;
|
|
@@ -723,15 +573,12 @@ export class IORoom<
|
|
|
723
573
|
});
|
|
724
574
|
}
|
|
725
575
|
|
|
726
|
-
private _sendSyncMessage(
|
|
727
|
-
message: EventMessage<string, any>,
|
|
728
|
-
sender: SendMessageSender | null,
|
|
729
|
-
): void {
|
|
576
|
+
private _sendSyncMessage(message: EventMessage<string, any>, sender: SendMessageSender | null): void {
|
|
730
577
|
const senderId = sender?.id;
|
|
731
578
|
|
|
732
579
|
if (!senderId) return;
|
|
733
580
|
|
|
734
|
-
const context: EventResolverContext<TContext> = {
|
|
581
|
+
const context: EventResolverContext<TPlatform, TAuthorize, TContext> = {
|
|
735
582
|
context: this._context,
|
|
736
583
|
doc: this._doc,
|
|
737
584
|
room: this.id,
|
|
@@ -739,14 +586,14 @@ export class IORoom<
|
|
|
739
586
|
sessions: this._sessions,
|
|
740
587
|
};
|
|
741
588
|
|
|
742
|
-
const resolver = this.
|
|
589
|
+
const resolver = this._getProcedure(message)?.config.sync;
|
|
743
590
|
|
|
744
591
|
if (!resolver) return;
|
|
745
592
|
|
|
746
|
-
let inputs:
|
|
593
|
+
let inputs: InferIOInput<this>[keyof TEvents];
|
|
747
594
|
|
|
748
595
|
try {
|
|
749
|
-
inputs = this.
|
|
596
|
+
inputs = this._getProcedureInputs(message);
|
|
750
597
|
} catch {
|
|
751
598
|
return;
|
|
752
599
|
}
|
package/src/Persistance.ts
CHANGED
|
@@ -5,13 +5,8 @@ export class Persistance extends AbstractPersistance {
|
|
|
5
5
|
private _storages = new Map<string, string>();
|
|
6
6
|
private _users = new Map<string, Map<string, JsonObject | null>>();
|
|
7
7
|
|
|
8
|
-
public addUser(
|
|
9
|
-
room
|
|
10
|
-
connectionId: string,
|
|
11
|
-
user: JsonObject | null,
|
|
12
|
-
): Promise<void> {
|
|
13
|
-
const users =
|
|
14
|
-
this._users.get(room) ?? new Map<string, JsonObject | null>();
|
|
8
|
+
public addUser(room: string, connectionId: string, user: JsonObject | null): Promise<void> {
|
|
9
|
+
const users = this._users.get(room) ?? new Map<string, JsonObject | null>();
|
|
15
10
|
|
|
16
11
|
this._users.set(room, users);
|
|
17
12
|
users.set(connectionId, user);
|
|
@@ -56,10 +51,7 @@ export class Persistance extends AbstractPersistance {
|
|
|
56
51
|
return Promise.resolve(storage);
|
|
57
52
|
}
|
|
58
53
|
|
|
59
|
-
public getUser(
|
|
60
|
-
room: string,
|
|
61
|
-
connectionId: string,
|
|
62
|
-
): Promise<JsonObject | null> {
|
|
54
|
+
public getUser(room: string, connectionId: string): Promise<JsonObject | null> {
|
|
63
55
|
const user = this._users.get(room)?.get(connectionId);
|
|
64
56
|
|
|
65
57
|
return Promise.resolve(user ?? null);
|