@pluv/io 0.22.0 → 0.23.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
@@ -8,6 +8,7 @@ import type {
8
8
  InferIOAuthorize,
9
9
  InferIOAuthorizeUser,
10
10
  JsonObject,
11
+ Maybe,
11
12
  } from "@pluv/types";
12
13
  import colors from "kleur";
13
14
  import type {
@@ -17,11 +18,13 @@ import type {
17
18
  WebSocketRegistrationMode,
18
19
  } from "./AbstractPlatform";
19
20
  import { IORoom } from "./IORoom";
21
+ import { PluvProcedure } from "./PluvProcedure";
20
22
  import type { PluvRouterEventConfig } from "./PluvRouter";
21
23
  import { PluvRouter } from "./PluvRouter";
22
24
  import type { JWTEncodeParams } from "./authorize";
23
25
  import { authorize } from "./authorize";
24
- import type { IORoomListenerEvent, PluvIOListeners } from "./types";
26
+ import { PING_TIMEOUT_MS } from "./constants";
27
+ import type { GetInitialStorageFn, PluvIOListeners } from "./types";
25
28
  import { __PLUV_VERSION } from "./version";
26
29
 
27
30
  export type InferIORoom<TServer extends PluvServer<any, any, any, any>> =
@@ -34,11 +37,12 @@ export type PluvServerConfig<
34
37
  TAuthorize extends IOAuthorize<any, any, InferPlatformContextType<TPlatform>> = BaseIOAuthorize,
35
38
  TContext extends JsonObject = {},
36
39
  TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext> = {},
37
- > = Partial<PluvIOListeners<TPlatform>> & {
40
+ > = Partial<PluvIOListeners<TPlatform, TAuthorize, TContext, TEvents>> & {
38
41
  authorize?: TAuthorize;
39
42
  context?: TContext;
40
43
  crdt?: { doc: (value: any) => AbstractCrdtDocFactory<any> };
41
44
  debug?: boolean;
45
+ getInitialStorage?: GetInitialStorageFn<TPlatform>;
42
46
  platform: TPlatform;
43
47
  router?: PluvRouter<TPlatform, TAuthorize, TContext, TEvents>;
44
48
  };
@@ -54,16 +58,142 @@ export class PluvServer<
54
58
  TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext> = {},
55
59
  > implements IORouterLike<TEvents>
56
60
  {
57
- private _rooms = new Map<string, IORoom<TPlatform, TAuthorize, TContext, TEvents>>();
61
+ public _listeners: PluvIOListeners<TPlatform, TAuthorize, TContext, TEvents>;
62
+
63
+ public readonly version: string = __PLUV_VERSION as any;
64
+ public readonly _authorize: TAuthorize | null = null;
65
+
66
+ private readonly _baseRouter: PluvRouter<TPlatform, TAuthorize, TContext, {}> = new PluvRouter({
67
+ $GET_OTHERS: this._procedure.sync((data, { room, session, sessions }) => {
68
+ const currentTime = new Date().getTime();
69
+
70
+ const others = sessions
71
+ .filter((wsSession) => {
72
+ if (wsSession.id === session?.id) return false;
73
+ if (wsSession.quit) return false;
74
+ if (currentTime - wsSession.timers.ping > PING_TIMEOUT_MS) return false;
75
+
76
+ return true;
77
+ })
78
+ .reduce<{
79
+ [connectionId: string]: {
80
+ connectionId: string;
81
+ room: string | null;
82
+ user: JsonObject;
83
+ };
84
+ }>(
85
+ (acc, { id, presence, user }) => ({
86
+ ...acc,
87
+ [id]: {
88
+ connectionId: id,
89
+ presence,
90
+ room,
91
+ user,
92
+ },
93
+ }),
94
+ {},
95
+ );
96
+
97
+ return { $OTHERS_RECEIVED: { others } };
98
+ }),
99
+ $INITIALIZE_SESSION: this._procedure
100
+ .broadcast((data, { session }) => {
101
+ const presence = (data as any)?.presence;
102
+
103
+ if (!session) return {};
104
+
105
+ session.presence = presence;
106
+
107
+ return {
108
+ $USER_JOINED: {
109
+ connectionId: session.id,
110
+ user: session.user,
111
+ presence,
112
+ },
113
+ };
114
+ })
115
+ .self(async (data, { context, doc, room }) => {
116
+ const oldState = await this._platform.persistance.getStorageState(room);
117
+ const update = (data as any)?.update as Maybe<string>;
118
+
119
+ const updates: readonly Maybe<string>[] = [
120
+ oldState
121
+ ? null
122
+ : await this._getInitialStorage?.({
123
+ ...context,
124
+ room,
125
+ }),
126
+ update,
127
+ ];
128
+
129
+ if (updates.some((_update) => !!_update)) {
130
+ const encodedState = doc.batchApplyEncodedState(updates).getEncodedState();
131
+
132
+ await this._platform.persistance.setStorageState(room, encodedState);
133
+
134
+ this._listeners.onStorageUpdated({ context, encodedState, room });
135
+ }
136
+
137
+ const state = (await this._platform.persistance.getStorageState(room)) ?? doc.getEncodedState();
138
+
139
+ return { $STORAGE_RECEIVED: { state } };
140
+ }),
141
+ $PING: this._procedure.self((data, { session }) => {
142
+ if (!session) return {};
143
+
144
+ const currentTime = new Date().getTime();
145
+ const prevState = session.webSocket.state;
146
+
147
+ session.webSocket.state = {
148
+ ...prevState,
149
+ timers: {
150
+ ...prevState.timers,
151
+ ping: currentTime,
152
+ },
153
+ };
154
+
155
+ return { $PONG: {} };
156
+ }),
157
+ $UPDATE_PRESENCE: this._procedure.broadcast((data, { session }) => {
158
+ const presence = (data as any)?.presence;
159
+
160
+ if (!session) return {};
161
+
162
+ const updated = Object.assign(Object.create(null), session.presence, presence);
163
+
164
+ session.webSocket.presence = updated;
165
+
166
+ return { $PRESENCE_UPDATED: { presence: updated } };
167
+ }),
168
+ $UPDATE_STORAGE: this._procedure.broadcast((data, { context, doc, room }) => {
169
+ const origin = (data as any)?.origin as Maybe<string>;
170
+ const update: string | null = (data as any)?.update ?? null;
171
+
172
+ if (origin === "$INITIALIZED" && Object.keys(doc.toJson()).length) {
173
+ return {};
174
+ }
58
175
 
59
- readonly _authorize: TAuthorize | null = null;
60
- readonly _context: TContext = {} as TContext;
61
- readonly _crdt: { doc: (value: any) => AbstractCrdtDocFactory<any> };
62
- readonly _debug: boolean;
63
- readonly _listeners: PluvIOListeners<TPlatform>;
64
- readonly _platform: TPlatform;
65
- readonly _router: PluvRouter<TPlatform, TAuthorize, TContext, TEvents>;
66
- readonly _version: string = __PLUV_VERSION as any;
176
+ const updated = update === null ? doc : doc.applyEncodedState({ update });
177
+ const encodedState = updated.getEncodedState();
178
+
179
+ this._platform.persistance.setStorageState(room, encodedState).then(() => {
180
+ this._listeners.onStorageUpdated({
181
+ context,
182
+ encodedState,
183
+ room,
184
+ });
185
+ });
186
+
187
+ return { $STORAGE_UPDATED: { state: encodedState } };
188
+ }),
189
+ });
190
+ private readonly _context: TContext = {} as TContext;
191
+ private readonly _crdt: { doc: (value: any) => AbstractCrdtDocFactory<any> };
192
+ private readonly _debug: boolean;
193
+ private readonly _getInitialStorage: GetInitialStorageFn<TPlatform> | null = null;
194
+ private readonly _platform: TPlatform;
195
+ private readonly _rooms = new Map<string, IORoom<TPlatform, TAuthorize, TContext, TEvents>>();
196
+ private readonly _router: PluvRouter<TPlatform, TAuthorize, TContext, TEvents>;
67
197
 
68
198
  public get _events() {
69
199
  return this._router._events;
@@ -73,13 +203,19 @@ export class PluvServer<
73
203
  return this._platform._registrationMode;
74
204
  }
75
205
 
206
+ private get _procedure(): PluvProcedure<TPlatform, TAuthorize, TContext, {}, {}> {
207
+ return new PluvProcedure();
208
+ }
209
+
76
210
  constructor(options: PluvServerConfig<TPlatform, TAuthorize, TContext, TEvents>) {
77
211
  const {
78
212
  authorize,
79
213
  context,
80
214
  crdt = noop,
81
215
  debug = false,
216
+ getInitialStorage,
82
217
  onRoomDeleted,
218
+ onRoomMessage,
83
219
  onStorageUpdated,
84
220
  platform,
85
221
  router = new PluvRouter<TPlatform, TAuthorize, TContext, TEvents>({} as TEvents),
@@ -88,13 +224,20 @@ export class PluvServer<
88
224
  this._crdt = crdt;
89
225
  this._debug = debug;
90
226
  this._platform = platform;
91
- this._router = router ?? null;
227
+ this._router = (router ? this._baseRouter : PluvRouter.merge(this._baseRouter, router)) as PluvRouter<
228
+ TPlatform,
229
+ TAuthorize,
230
+ TContext,
231
+ TEvents
232
+ >;
92
233
 
93
234
  if (authorize) this._authorize = authorize;
94
235
  if (context) this._context = context;
236
+ if (getInitialStorage) this._getInitialStorage = getInitialStorage;
95
237
 
96
238
  this._listeners = {
97
239
  onRoomDeleted: (event) => onRoomDeleted?.(event),
240
+ onRoomMessage: (event) => onRoomMessage?.(event),
98
241
  onStorageUpdated: (event) => onStorageUpdated?.(event),
99
242
  };
100
243
  }
@@ -144,18 +287,11 @@ export class PluvServer<
144
287
  context: roomContext,
145
288
  crdt: this._crdt,
146
289
  debug: debug ?? this._debug,
147
- onDestroy: async ({ encodedState, room }) => {
290
+ onDestroy: async (event) => {
148
291
  this._logDebug(`${colors.blue("Deleting empty room:")} ${room}`);
149
-
150
- const roomContext = {
151
- room,
152
- encodedState,
153
- ...platformRoomContext,
154
- } as IORoomListenerEvent<TPlatform> & InferRoomContextType<TPlatform>;
155
-
156
292
  this._rooms.delete(room);
157
293
 
158
- await Promise.resolve(this._listeners.onRoomDeleted(roomContext));
294
+ await Promise.resolve(this._listeners.onRoomDeleted(event));
159
295
  await this._platform.persistance.deleteStorageState(room);
160
296
 
161
297
  if (this._debug) {
@@ -165,6 +301,9 @@ export class PluvServer<
165
301
  this._logDebug(`${colors.blue("Rooms available:")} ${rooms.join(", ")}`);
166
302
  }
167
303
  },
304
+ onMessage: async (event) => {
305
+ await Promise.resolve(this._listeners.onRoomMessage(event));
306
+ },
168
307
  platform,
169
308
  router: this._router,
170
309
  });
package/src/createIO.ts CHANGED
@@ -2,19 +2,18 @@ import type { AbstractCrdtDocFactory } from "@pluv/crdt";
2
2
  import type { BaseUser, IOAuthorize, JsonObject } from "@pluv/types";
3
3
  import type { AbstractPlatform, InferPlatformContextType } from "./AbstractPlatform";
4
4
  import { PluvIO } from "./PluvIO";
5
- import type { GetInitialStorageFn, PluvIOListeners } from "./types";
5
+ import type { GetInitialStorageFn } from "./types";
6
6
 
7
7
  export type CreateIOParams<
8
8
  TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>,
9
9
  TContext extends JsonObject = {},
10
10
  TAuthorizeUser extends BaseUser = BaseUser,
11
11
  TAuthorizeRequired extends boolean = false,
12
- > = Partial<PluvIOListeners<TPlatform>> & {
12
+ > = {
13
13
  authorize?: IOAuthorize<TAuthorizeUser, TAuthorizeRequired, InferPlatformContextType<TPlatform>>;
14
14
  context?: TContext;
15
15
  crdt?: { doc: (value: any) => AbstractCrdtDocFactory<any> };
16
16
  debug?: boolean;
17
- getInitialStorage?: GetInitialStorageFn<TPlatform>;
18
17
  platform: TPlatform;
19
18
  };
20
19
 
@@ -30,7 +29,7 @@ export const createIO = <
30
29
  IOAuthorize<TAuthorizeUser, TAuthorizeRequired, InferPlatformContextType<TPlatform>>,
31
30
  TContext
32
31
  > => {
33
- const { authorize, context, crdt, debug, getInitialStorage, onRoomDeleted, onStorageUpdated, platform } = params;
32
+ const { authorize, context, crdt, debug, platform } = params;
34
33
 
35
34
  return new PluvIO<
36
35
  TPlatform,
@@ -41,9 +40,6 @@ export const createIO = <
41
40
  context,
42
41
  crdt,
43
42
  debug,
44
- getInitialStorage,
45
- onRoomDeleted,
46
- onStorageUpdated,
47
43
  platform,
48
44
  });
49
45
  };
package/src/types.ts CHANGED
@@ -3,8 +3,12 @@ import type {
3
3
  BaseIOAuthorize,
4
4
  EventRecord,
5
5
  IOAuthorize,
6
+ IORouterLike,
6
7
  Id,
8
+ InferEventMessage,
9
+ InferEventsOutput,
7
10
  InferIOAuthorizeUser,
11
+ InferIOOutput,
8
12
  JsonObject,
9
13
  Maybe,
10
14
  MaybePromise,
@@ -16,6 +20,7 @@ import type {
16
20
  InferRoomContextType,
17
21
  } from "./AbstractPlatform";
18
22
  import type { AbstractWebSocket } from "./AbstractWebSocket";
23
+ import type { PluvRouterEventConfig } from "./PluvRouter";
19
24
 
20
25
  declare global {
21
26
  var console: {
@@ -101,15 +106,37 @@ export type GetInitialStorageFn<TPlatform extends AbstractPlatform> = (
101
106
  event: GetInitialStorageEvent<TPlatform>,
102
107
  ) => MaybePromise<Maybe<string>>;
103
108
 
104
- export interface PluvIOListeners<TPlatform extends AbstractPlatform> {
105
- onRoomDeleted: (event: IORoomListenerEvent<TPlatform>) => void;
106
- onStorageUpdated: (event: IORoomListenerEvent<TPlatform>) => void;
109
+ export interface PluvIOListeners<
110
+ TPlatform extends AbstractPlatform<any>,
111
+ TAuthorize extends IOAuthorize<any, any, InferRoomContextType<TPlatform>>,
112
+ TContext extends JsonObject,
113
+ TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>,
114
+ > {
115
+ onRoomDeleted: (event: IORoomListenerEvent<TPlatform, TAuthorize, TContext, TEvents>) => void;
116
+ onRoomMessage: (event: IORoomMessageEvent<TPlatform, TAuthorize, TContext, TEvents>) => void;
117
+ onStorageUpdated: (event: IORoomListenerEvent<TPlatform, TAuthorize, TContext, TEvents>) => void;
107
118
  }
108
119
 
109
- export type IORoomListenerEvent<TPlatform extends AbstractPlatform> = {
110
- room: string;
120
+ export type IORoomListenerEvent<
121
+ TPlatform extends AbstractPlatform<any>,
122
+ TAuthorize extends IOAuthorize<any, any, InferRoomContextType<TPlatform>>,
123
+ TContext extends JsonObject,
124
+ TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>,
125
+ > = {
126
+ context: TContext & InferRoomContextType<TPlatform>;
111
127
  encodedState: string | null;
112
- } & InferRoomContextType<TPlatform>;
128
+ room: string;
129
+ };
130
+
131
+ export type IORoomMessageEvent<
132
+ TPlatform extends AbstractPlatform<any>,
133
+ TAuthorize extends IOAuthorize<any, any, InferRoomContextType<TPlatform>>,
134
+ TContext extends JsonObject,
135
+ TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>,
136
+ > = IORoomListenerEvent<TPlatform, TAuthorize, TContext, TEvents> & {
137
+ message: InferEventMessage<InferEventsOutput<TEvents>, keyof InferEventsOutput<TEvents>>;
138
+ user?: InferIOAuthorizeUser<TAuthorize>;
139
+ };
113
140
 
114
141
  export type WebSocketType<TPlatform extends AbstractPlatform> =
115
142
  | InferPlatformWebSocketType<TPlatform>
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '0.22.0'
2
+ export const version = '0.23.0'