@pluv/io 2.0.2 → 2.2.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/src/PluvServer.ts CHANGED
@@ -50,12 +50,12 @@ export type PluvServerConfig<
50
50
  > = Partial<PluvIOListeners<TPlatform, TAuthorize, TContext, TEvents>> & {
51
51
  authorize?: TAuthorize;
52
52
  context?: PluvContext<TPlatform, TContext>;
53
- crdt?: { doc: (value: any) => AbstractCrdtDocFactory<any> };
53
+ crdt?: { doc: (value: any) => AbstractCrdtDocFactory<any, any> };
54
54
  debug?: boolean;
55
55
  getInitialStorage?: GetInitialStorageFn<TContext>;
56
56
  limits: PluvIOLimits;
57
57
  io: PluvIO<TPlatform, TAuthorize, TContext>;
58
- platform: TPlatform;
58
+ platform: () => TPlatform;
59
59
  router?: PluvRouter<TPlatform, TAuthorize, TContext, TEvents>;
60
60
  };
61
61
 
@@ -96,211 +96,27 @@ export class PluvServer<
96
96
  public readonly version: string = __PLUV_VERSION as any;
97
97
 
98
98
  private readonly _authorize: TAuthorize = null as TAuthorize;
99
- private readonly _baseRouter: PluvRouter<TPlatform, TAuthorize, TContext, {}> = new PluvRouter({
100
- $getOthers: this._procedure.sync((data, { room, session, sessions }) => {
101
- const currentTime = Date.now();
102
-
103
- const others = sessions
104
- .filter((wsSession) => {
105
- if (wsSession.id === session?.id) return false;
106
- if (wsSession.quit) return false;
107
- if (currentTime - wsSession.timers.ping > PING_TIMEOUT_MS) return false;
108
-
109
- return true;
110
- })
111
- .reduce<{
112
- [connectionId: string]: {
113
- connectionId: string;
114
- room: string | null;
115
- user: JsonObject | null;
116
- };
117
- }>(
118
- (acc, { id, presence, timers, user }) => ({
119
- ...acc,
120
- [id]: {
121
- connectionId: id,
122
- presence,
123
- room,
124
- timers: { presence: timers.presence },
125
- user,
126
- },
127
- }),
128
- {},
129
- );
130
-
131
- return { $othersReceived: { others } };
132
- }),
133
- $initializeSession: this._procedure
134
- .broadcast((data, { session }) => {
135
- const presence = (data as any)?.presence;
136
- const timers = session.timers;
137
-
138
- if (!session) return {};
139
-
140
- session.presence = presence;
141
-
142
- return {
143
- $userJoined: {
144
- connectionId: session.id,
145
- user: session.user,
146
- presence,
147
- timers: { presence: timers.presence },
148
- },
149
- };
150
- })
151
- .self(async (data, { context, doc, room, session }) => {
152
- const oldState = await this._platform.persistence.getStorageState(room);
153
- /**
154
- * !HACK
155
- * @description This is the frontend's initialStorage. We only want to
156
- * apply this if the server's storage is empty (i.e. no initial storage
157
- * has been applied)
158
- */
159
- const update = (data as any)?.update as Maybe<string>;
160
-
161
- if (!oldState) {
162
- const loadedState = await this._getInitialStorage?.({ context, room });
163
-
164
- if (!!loadedState) {
165
- doc.applyEncodedState({ update: loadedState }).getEncodedState();
166
- }
167
- }
168
-
169
- /**
170
- * @description Storage was already initialized. Don't overwrite the current
171
- * storage state with the incoming initial storage. Return what the current state
172
- * is without changes.
173
- * @date May 7, 2025
174
- */
175
- if (!doc.isEmpty()) {
176
- const encodedState = doc.getEncodedState();
177
-
178
- return { $storageReceived: { changeKind: "unchanged", state: encodedState } };
179
- }
180
-
181
- /**
182
- * @description Storage was never initialized, and there is an incoming
183
- * initialStorage from the client. Apply the initialStorage and persist the state
184
- * as an update.
185
- * @date May 7, 2025
186
- */
187
- if (!!update) {
188
- const encodedState = doc.applyEncodedState({ update }).getEncodedState();
189
- const storageSize = new TextEncoder().encode(encodedState).length;
190
-
191
- if (
192
- !!this._limits.storageMaxSize &&
193
- storageSize > this._limits.storageMaxSize
194
- ) {
195
- throw new Error("Storage has exceeded the size limit");
196
- }
197
-
198
- await this._platform.persistence
199
- .setStorageState(room, encodedState)
200
- .catch((error) => {
201
- this._logDebug(error);
202
- });
203
-
204
- this._getListeners().onStorageUpdated({
205
- context,
206
- encodedState,
207
- room,
208
- user: session?.user,
209
- webSocket: session?.webSocket.webSocket,
210
- });
211
-
212
- return { $storageReceived: { changeKind: "initialized", state: encodedState } };
213
- }
214
-
215
- const encodedState = doc.getEncodedState();
216
-
217
- return { $storageReceived: { changeKind: "empty", state: encodedState } };
218
- }),
219
- $ping: this._procedure.self((data, { session }) => {
220
- if (!session) return {};
221
-
222
- const currentTime = new Date().getTime();
223
- const prevState = session.webSocket.state;
224
-
225
- this._platform.setSerializedState(session.webSocket, {
226
- ...prevState,
227
- timers: {
228
- ...prevState.timers,
229
- ping: currentTime,
230
- },
231
- });
232
-
233
- return { $pong: {} };
234
- }),
235
- $updatePresence: this._procedure.broadcast((data, context) => {
236
- const presence = (data as any)?.presence;
237
- const { session } = context;
238
-
239
- if (!session) return {};
240
-
241
- const cleanedPatch = pickBy(presence ?? {}, (value) => typeof value !== "undefined");
242
- const updated = Object.assign(Object.create(null), session.presence, cleanedPatch);
243
- const bytes = new TextEncoder().encode(JSON.stringify(updated)).length;
244
-
245
- if (!!this._limits.presenceMaxSize && bytes > this._limits.presenceMaxSize) {
246
- throw new Error(
247
- `Large presence. Presence must be at most 512 bytes. Current size: ${bytes.toLocaleString()}`,
248
- );
249
- }
250
-
251
- context.presence = updated;
252
-
253
- return {
254
- $presenceUpdated: {
255
- presence: updated,
256
- timers: { presence: session.timers.presence },
257
- },
258
- };
259
- }),
260
- $updateStorage: this._procedure.broadcast((data, { context, doc, room }) => {
261
- const origin = (data as any)?.origin as Maybe<string>;
262
- const update: string | null = (data as any)?.update ?? null;
263
-
264
- if (origin === "$initialized" && Object.keys(doc.toJson()).length) return {};
265
-
266
- const updated = update === null ? doc : doc.applyEncodedState({ update });
267
- const encodedState = updated.getEncodedState();
268
- const storageSize = new TextEncoder().encode(encodedState).length;
269
-
270
- if (!!this._limits.storageMaxSize && storageSize > this._limits.storageMaxSize) {
271
- throw new Error("Storage has exceeded the size limit");
272
- }
273
-
274
- this._platform.persistence.setStorageState(room, encodedState).then(() => {
275
- this._getListeners().onStorageUpdated({
276
- context,
277
- encodedState,
278
- room,
279
- });
280
- });
281
-
282
- return { $storageUpdated: { state: encodedState } };
283
- }),
284
- });
285
99
  private readonly _context: PluvContext<TPlatform, TContext> = {} as PluvContext<
286
100
  TPlatform,
287
101
  TContext
288
102
  >;
289
- private readonly _crdt: { doc: (value: any) => AbstractCrdtDocFactory<any> };
103
+ private readonly _crdt: { doc: (value: any) => AbstractCrdtDocFactory<any, any> };
290
104
  private readonly _debug: boolean;
291
105
  private readonly _getInitialStorage: GetInitialStorageFn<TContext> | null = null;
292
106
  private readonly _io: PluvIO<TPlatform, TAuthorize, TContext>;
293
107
  private readonly _limits: PluvIOLimits;
294
- private readonly _platform: TPlatform;
295
- private readonly _router: PluvRouter<TPlatform, TAuthorize, TContext, TEvents>;
108
+ private readonly _platform: () => TPlatform;
109
+ private readonly _inputRouter: PluvRouter<TPlatform, TAuthorize, TContext, TEvents>;
296
110
 
297
111
  public get fetch(): (...args: any[]) => Promise<any> {
112
+ const platform = this._platform();
113
+
298
114
  return ((...args: any[]): Promise<any> => {
299
- if (!this._platform._fetch)
300
- throw new Error(`\`${this._platform._name}\` does not support \`fetch\``);
115
+ if (!platform._fetch)
116
+ throw new Error(`\`${platform._name}\` does not support \`fetch\``);
301
117
 
302
- return this._platform._fetch(...args);
303
- }) as NonNullable<typeof this._platform._fetch>;
118
+ return platform._fetch(...args);
119
+ }) as NonNullable<typeof platform._fetch>;
304
120
  }
305
121
 
306
122
  /**
@@ -313,7 +129,7 @@ export class PluvServer<
313
129
  authorize: this._authorize,
314
130
  context: this._context,
315
131
  events: this._router._defs.events,
316
- platform: this._platform,
132
+ platform: this._platform(),
317
133
  } as {
318
134
  authorize: TAuthorize;
319
135
  context: TContext;
@@ -322,10 +138,216 @@ export class PluvServer<
322
138
  };
323
139
  }
324
140
 
141
+ private get _baseRouter(): PluvRouter<TPlatform, TAuthorize, TContext, {}> {
142
+ return new PluvRouter({
143
+ $getOthers: this._procedure.sync((data, { room, session, sessions }) => {
144
+ const currentTime = Date.now();
145
+
146
+ const others = sessions
147
+ .filter((wsSession) => {
148
+ if (wsSession.id === session?.id) return false;
149
+ if (wsSession.quit) return false;
150
+ if (currentTime - wsSession.timers.ping > PING_TIMEOUT_MS) return false;
151
+
152
+ return true;
153
+ })
154
+ .reduce<{
155
+ [connectionId: string]: {
156
+ connectionId: string;
157
+ room: string | null;
158
+ user: JsonObject | null;
159
+ };
160
+ }>(
161
+ (acc, { id, presence, timers, user }) => ({
162
+ ...acc,
163
+ [id]: {
164
+ connectionId: id,
165
+ presence,
166
+ room,
167
+ timers: { presence: timers.presence },
168
+ user,
169
+ },
170
+ }),
171
+ {},
172
+ );
173
+
174
+ return { $othersReceived: { others } };
175
+ }),
176
+ $initializeSession: this._procedure
177
+ .broadcast((data, { session }) => {
178
+ const presence = (data as any)?.presence;
179
+ const timers = session.timers;
180
+
181
+ if (!session) return {};
182
+
183
+ session.presence = presence;
184
+
185
+ return {
186
+ $userJoined: {
187
+ connectionId: session.id,
188
+ user: session.user,
189
+ presence,
190
+ timers: { presence: timers.presence },
191
+ },
192
+ };
193
+ })
194
+ .self(async (data, { context, doc, platform, room, session }) => {
195
+ const oldState = await platform.persistence.getStorageState(room);
196
+ /**
197
+ * !HACK
198
+ * @description This is the frontend's initialStorage. We only want to
199
+ * apply this if the server's storage is empty (i.e. no initial storage
200
+ * has been applied)
201
+ */
202
+ const update = (data as any)?.update as Maybe<string>;
203
+
204
+ if (!oldState) {
205
+ const loadedState = await this._getInitialStorage?.({ context, room });
206
+
207
+ if (!!loadedState) {
208
+ doc.applyEncodedState({ update: loadedState }).getEncodedState();
209
+ }
210
+ }
211
+
212
+ /**
213
+ * @description Storage was already initialized. Don't overwrite the current
214
+ * storage state with the incoming initial storage. Return what the current state
215
+ * is without changes.
216
+ * @date May 7, 2025
217
+ */
218
+ if (!doc.isEmpty()) {
219
+ const encodedState = doc.getEncodedState();
220
+
221
+ return {
222
+ $storageReceived: { changeKind: "unchanged", state: encodedState },
223
+ };
224
+ }
225
+
226
+ /**
227
+ * @description Storage was never initialized, and there is an incoming
228
+ * initialStorage from the client. Apply the initialStorage and persist the state
229
+ * as an update.
230
+ * @date May 7, 2025
231
+ */
232
+ if (!!update) {
233
+ const encodedState = doc.applyEncodedState({ update }).getEncodedState();
234
+ const storageSize = new TextEncoder().encode(encodedState).length;
235
+
236
+ if (
237
+ !!this._limits.storageMaxSize &&
238
+ storageSize > this._limits.storageMaxSize
239
+ ) {
240
+ throw new Error("Storage has exceeded the size limit");
241
+ }
242
+
243
+ await platform.persistence
244
+ .setStorageState(room, encodedState)
245
+ .catch((error) => {
246
+ this._logDebug(error);
247
+ });
248
+
249
+ this._getListeners().onStorageUpdated({
250
+ context,
251
+ encodedState,
252
+ platform,
253
+ room,
254
+ user: session?.user,
255
+ webSocket: session?.webSocket.webSocket,
256
+ });
257
+
258
+ return {
259
+ $storageReceived: { changeKind: "initialized", state: encodedState },
260
+ };
261
+ }
262
+
263
+ const encodedState = doc.getEncodedState();
264
+
265
+ return { $storageReceived: { changeKind: "empty", state: encodedState } };
266
+ }),
267
+ $ping: this._procedure.self((data, { platform, session }) => {
268
+ if (!session) return {};
269
+
270
+ const currentTime = new Date().getTime();
271
+ const prevState = session.webSocket.state;
272
+
273
+ platform.setSerializedState(session.webSocket, {
274
+ ...prevState,
275
+ timers: {
276
+ ...prevState.timers,
277
+ ping: currentTime,
278
+ },
279
+ });
280
+
281
+ return { $pong: {} };
282
+ }),
283
+ $updatePresence: this._procedure.broadcast((data, context) => {
284
+ const presence = (data as any)?.presence;
285
+ const { session } = context;
286
+
287
+ if (!session) return {};
288
+
289
+ const cleanedPatch = pickBy(
290
+ presence ?? {},
291
+ (value) => typeof value !== "undefined",
292
+ );
293
+ const updated = Object.assign(Object.create(null), session.presence, cleanedPatch);
294
+ const bytes = new TextEncoder().encode(JSON.stringify(updated)).length;
295
+
296
+ if (!!this._limits.presenceMaxSize && bytes > this._limits.presenceMaxSize) {
297
+ throw new Error(
298
+ `Large presence. Presence must be at most 512 bytes. Current size: ${bytes.toLocaleString()}`,
299
+ );
300
+ }
301
+
302
+ context.presence = updated;
303
+
304
+ return {
305
+ $presenceUpdated: {
306
+ presence: updated,
307
+ timers: { presence: session.timers.presence },
308
+ },
309
+ };
310
+ }),
311
+ $updateStorage: this._procedure.broadcast((data, { context, doc, platform, room }) => {
312
+ const origin = (data as any)?.origin as Maybe<string>;
313
+ const update: string | null = (data as any)?.update ?? null;
314
+
315
+ if (origin === "$initialized" && Object.keys(doc.toJson()).length) return {};
316
+
317
+ const updated = update === null ? doc : doc.applyEncodedState({ update });
318
+ const encodedState = updated.getEncodedState();
319
+ const storageSize = new TextEncoder().encode(encodedState).length;
320
+
321
+ if (!!this._limits.storageMaxSize && storageSize > this._limits.storageMaxSize) {
322
+ throw new Error("Storage has exceeded the size limit");
323
+ }
324
+
325
+ platform.persistence.setStorageState(room, encodedState).then(() => {
326
+ this._getListeners().onStorageUpdated({
327
+ context,
328
+ encodedState,
329
+ platform,
330
+ room,
331
+ });
332
+ });
333
+
334
+ return { $storageUpdated: { state: encodedState } };
335
+ }),
336
+ });
337
+ }
338
+
325
339
  private get _procedure(): PluvProcedure<TPlatform, TAuthorize, TContext, {}, {}> {
326
340
  return new PluvProcedure();
327
341
  }
328
342
 
343
+ private get _router(): PluvRouter<TPlatform, TAuthorize, TContext, TEvents> {
344
+ return (
345
+ this._inputRouter
346
+ ? PluvRouter.merge(this._baseRouter, this._inputRouter)
347
+ : this._baseRouter
348
+ ) as PluvRouter<TPlatform, TAuthorize, TContext, TEvents>;
349
+ }
350
+
329
351
  constructor(options: PluvServerConfig<TPlatform, TAuthorize, TContext, TEvents>) {
330
352
  const {
331
353
  authorize,
@@ -347,16 +369,14 @@ export class PluvServer<
347
369
  onUserDisconnected,
348
370
  } = options as Partial<BasePluvIOListeners<TPlatform, TAuthorize, TContext, TEvents>>;
349
371
 
350
- platform.validateConfig(options);
372
+ platform().validateConfig(options);
351
373
 
352
374
  this._crdt = crdt;
353
375
  this._debug = debug;
376
+ this._inputRouter = router;
354
377
  this._io = io;
355
378
  this._limits = limits;
356
379
  this._platform = platform;
357
- this._router = (
358
- router ? PluvRouter.merge(this._baseRouter, router) : this._baseRouter
359
- ) as PluvRouter<TPlatform, TAuthorize, TContext, TEvents>;
360
380
 
361
381
  if (authorize) this._authorize = authorize;
362
382
  if (context) this._context = context;
@@ -378,8 +398,10 @@ export class PluvServer<
378
398
  const { _meta, debug, onDestroy, onMessage, ...platformRoomContext } = (options[0] ??
379
399
  {}) as CreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents>[0] & { _meta?: any };
380
400
 
381
- if (this._platform._config.handleMode !== "io") {
382
- throw new Error(`\`createRoom\` is unsupported for \`${this._platform._name}\``);
401
+ const platform = this._platform();
402
+
403
+ if (platform._config.handleMode !== "io") {
404
+ throw new Error(`\`createRoom\` is unsupported for \`${platform._name}\``);
383
405
  }
384
406
 
385
407
  if (!/^[a-z0-9](?:[a-z0-9-_]*[a-z0-9])?$/i.test(room))
@@ -388,6 +410,8 @@ export class PluvServer<
388
410
  const roomContext = platformRoomContext as InferRoomContextType<TPlatform>;
389
411
  const context: TContext =
390
412
  typeof this._context === "function" ? this._context(roomContext) : this._context;
413
+ const listeners = this._getListeners();
414
+ const logDebug = this._logDebug.bind(this);
391
415
 
392
416
  const newRoom = new IORoom<TPlatform, TAuthorize, TContext, TEvents>(room, {
393
417
  ...(!!_meta ? { _meta } : {}),
@@ -395,26 +419,26 @@ export class PluvServer<
395
419
  context,
396
420
  crdt: this._crdt,
397
421
  debug: debug ?? this._debug,
398
- onDestroy: async (event) => {
399
- this._logDebug(`${colors.blue("Deleting empty room:")} ${room}`);
422
+ async onDestroy(event) {
423
+ logDebug(`${colors.blue("Deleting empty room:")} ${room}`);
400
424
 
401
425
  await Promise.resolve(onDestroy?.(event));
402
- await Promise.resolve(this._getListeners().onRoomDeleted(event));
403
- await this._platform.persistence.deleteStorageState(room);
426
+ await Promise.resolve(listeners.onRoomDeleted(event));
427
+ await event.platform.persistence.deleteStorageState(room);
404
428
 
405
- this._logDebug(`${colors.blue("Deleted room:")} ${room}`);
429
+ logDebug(`${colors.blue("Deleted room:")} ${room}`);
406
430
  },
407
- onMessage: async (event) => {
431
+ async onMessage(event) {
408
432
  await Promise.resolve(onMessage?.(event));
409
- await Promise.resolve(this._getListeners().onRoomMessage(event));
433
+ await Promise.resolve(listeners.onRoomMessage(event));
410
434
  },
411
- onUserConnected: async (event) => {
412
- await Promise.resolve(this._getListeners().onUserConnected(event));
435
+ async onUserConnected(event) {
436
+ await Promise.resolve(listeners.onUserConnected(event));
413
437
  },
414
- onUserDisconnected: async (event) => {
415
- await Promise.resolve(this._getListeners().onUserDisconnected(event));
438
+ async onUserDisconnected(event) {
439
+ await Promise.resolve(listeners.onUserDisconnected(event));
416
440
  },
417
- platform: this._platform,
441
+ platform,
418
442
  roomContext,
419
443
  router: this._router,
420
444
  });
package/src/createIO.ts CHANGED
@@ -13,7 +13,7 @@ export type CreateIOParams<
13
13
  crdt?: CrdtLibraryType;
14
14
  debug?: boolean;
15
15
  limits?: PluvIOLimits;
16
- platform: TPlatform;
16
+ platform: () => TPlatform;
17
17
  };
18
18
 
19
19
  export const createIO = <
package/src/types.ts CHANGED
@@ -29,7 +29,7 @@ export type PluvContext<TPlatform extends AbstractPlatform, TContext extends Rec
29
29
  | ((params: InferRoomContextType<TPlatform>) => TContext);
30
30
 
31
31
  export interface CrdtLibraryType {
32
- doc: (value: any) => AbstractCrdtDocFactory<any>;
32
+ doc: (value: any) => AbstractCrdtDocFactory<any, any>;
33
33
  kind: "loro" | "yjs";
34
34
  }
35
35
 
@@ -54,8 +54,9 @@ export interface EventResolverContext<
54
54
  TContext extends Record<string, any> = {},
55
55
  > {
56
56
  context: TContext;
57
- doc: CrdtDocLike<any>;
57
+ doc: CrdtDocLike<any, any>;
58
58
  garbageCollect: () => Promise<void>;
59
+ platform: TPlatform;
59
60
  presence: JsonObject | null;
60
61
  room: string;
61
62
  session: TKind extends "sync"
@@ -176,7 +177,7 @@ export type BasePluvIOListeners<
176
177
  TContext extends Record<string, any>,
177
178
  TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>,
178
179
  > = {
179
- onRoomDeleted: (event: IORoomListenerEvent<TContext>) => void;
180
+ onRoomDeleted: (event: IORoomListenerEvent<TPlatform, TContext>) => void;
180
181
  onRoomMessage: (event: IORoomMessageEvent<TPlatform, TAuthorize, TContext, TEvents>) => void;
181
182
  onStorageUpdated: (event: IOStorageUpdatedEvent<TPlatform, TAuthorize, TContext>) => void;
182
183
  onUserConnected: (event: IOUserConnectedEvent<TPlatform, TAuthorize, TContext>) => void;
@@ -230,9 +231,13 @@ export type InferPlatformListeners<TPlatform extends AbstractPlatform<any, any,
230
231
  export type InferPlatformRouter<TPlatform extends AbstractPlatform<any, any, any, any>> =
231
232
  InferPlatformConfig<TPlatform>["router"];
232
233
 
233
- export type IORoomListenerEvent<TContext extends Record<string, any>> = {
234
+ export type IORoomListenerEvent<
235
+ TPlatform extends AbstractPlatform<any, any, any, any>,
236
+ TContext extends Record<string, any>,
237
+ > = {
234
238
  context: TContext;
235
239
  encodedState: string | null;
240
+ platform: TPlatform;
236
241
  room: string;
237
242
  };
238
243
 
@@ -241,7 +246,7 @@ export type IORoomMessageEvent<
241
246
  TAuthorize extends PluvIOAuthorize<TPlatform, any, InferInitContextType<TPlatform>> | null,
242
247
  TContext extends Record<string, any>,
243
248
  TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>,
244
- > = IORoomListenerEvent<TContext> & {
249
+ > = IORoomListenerEvent<TPlatform, TContext> & {
245
250
  message: InferEventMessage<InferEventsOutput<TEvents>, keyof InferEventsOutput<TEvents>>;
246
251
  user?: InferIOAuthorizeUser<TAuthorize>;
247
252
  webSocket?: InferPlatformWebSocketSource<TPlatform>;
@@ -251,7 +256,7 @@ export type IOStorageUpdatedEvent<
251
256
  TPlatform extends AbstractPlatform<any>,
252
257
  TAuthorize extends IOAuthorize<any, InferInitContextType<TPlatform>> | null,
253
258
  TContext extends Record<string, any>,
254
- > = IORoomListenerEvent<TContext> & {
259
+ > = IORoomListenerEvent<TPlatform, TContext> & {
255
260
  user?: InferIOAuthorizeUser<TAuthorize>;
256
261
  webSocket?: InferPlatformWebSocketSource<TPlatform>;
257
262
  };
@@ -260,7 +265,7 @@ export type IOUserConnectedEvent<
260
265
  TPlatform extends AbstractPlatform<any>,
261
266
  TAuthorize extends IOAuthorize<any, InferInitContextType<TPlatform>> | null,
262
267
  TContext extends Record<string, any>,
263
- > = IORoomListenerEvent<TContext> & {
268
+ > = IORoomListenerEvent<TPlatform, TContext> & {
264
269
  user?: InferIOAuthorizeUser<TAuthorize>;
265
270
  webSocket?: InferPlatformWebSocketSource<TPlatform>;
266
271
  };
@@ -269,7 +274,7 @@ export type IOUserDisconnectedEvent<
269
274
  TPlatform extends AbstractPlatform<any>,
270
275
  TAuthorize extends IOAuthorize<any, InferInitContextType<TPlatform>> | null,
271
276
  TContext extends Record<string, any>,
272
- > = IORoomListenerEvent<TContext> & {
277
+ > = IORoomListenerEvent<TPlatform, TContext> & {
273
278
  user?: InferIOAuthorizeUser<TAuthorize>;
274
279
  };
275
280
 
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '2.0.2'
2
+ export const version = '2.2.0'