@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/.turbo/turbo-build.log +9 -9
- package/CHANGELOG.md +68 -0
- package/dist/index.d.mts +91 -90
- package/dist/index.d.ts +91 -90
- package/dist/index.js +144 -150
- package/dist/index.mjs +144 -150
- package/package.json +5 -5
- package/src/AbstractPlatform.ts +5 -5
- package/src/IORoom.ts +60 -49
- package/src/PluvIO.ts +16 -185
- package/src/PluvRouter.ts +1 -3
- package/src/PluvServer.ts +160 -21
- package/src/createIO.ts +3 -7
- package/src/types.ts +33 -6
- package/src/version.generated.ts +1 -1
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
|
|
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
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
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 (
|
|
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(
|
|
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
|
|
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
|
-
> =
|
|
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,
|
|
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<
|
|
105
|
-
|
|
106
|
-
|
|
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<
|
|
110
|
-
|
|
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
|
-
|
|
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>
|
package/src/version.generated.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by genversion.
|
|
2
|
-
export const version = '0.
|
|
2
|
+
export const version = '0.23.0'
|