@pluv/io 0.22.0 → 0.24.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 +196 -0
- package/dist/index.d.mts +101 -101
- package/dist/index.d.ts +101 -101
- package/dist/index.js +217 -202
- package/dist/index.mjs +217 -202
- package/package.json +5 -5
- package/src/AbstractPlatform.ts +5 -5
- package/src/IORoom.ts +182 -115
- package/src/PluvIO.ts +16 -185
- package/src/PluvRouter.ts +1 -3
- package/src/PluvServer.ts +194 -76
- package/src/createIO.ts +3 -7
- package/src/index.ts +1 -1
- package/src/types.ts +31 -6
- package/src/version.generated.ts +1 -1
package/src/PluvIO.ts
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import type { AbstractCrdtDocFactory } from "@pluv/crdt";
|
|
2
2
|
import { noop } from "@pluv/crdt";
|
|
3
|
-
import type {
|
|
4
|
-
BaseIOAuthorize,
|
|
5
|
-
IOAuthorize,
|
|
6
|
-
IOLike,
|
|
7
|
-
InferIOAuthorize,
|
|
8
|
-
InferIOAuthorizeUser,
|
|
9
|
-
JsonObject,
|
|
10
|
-
Maybe,
|
|
11
|
-
} from "@pluv/types";
|
|
3
|
+
import type { BaseIOAuthorize, IOAuthorize, InferIOAuthorizeUser, JsonObject } from "@pluv/types";
|
|
12
4
|
import type { AbstractPlatform, InferPlatformContextType, WebSocketRegistrationMode } from "./AbstractPlatform";
|
|
13
5
|
import { PluvProcedure } from "./PluvProcedure";
|
|
14
6
|
import type { MergedRouter, PluvRouterEventConfig } from "./PluvRouter";
|
|
@@ -16,7 +8,6 @@ import { PluvRouter } from "./PluvRouter";
|
|
|
16
8
|
import { PluvServer } from "./PluvServer";
|
|
17
9
|
import type { JWTEncodeParams } from "./authorize";
|
|
18
10
|
import { authorize } from "./authorize";
|
|
19
|
-
import { PING_TIMEOUT_MS } from "./constants";
|
|
20
11
|
import type { GetInitialStorageFn, PluvIOListeners } from "./types";
|
|
21
12
|
import { __PLUV_VERSION } from "./version";
|
|
22
13
|
|
|
@@ -24,187 +15,43 @@ export type PluvIOConfig<
|
|
|
24
15
|
TPlatform extends AbstractPlatform<any>,
|
|
25
16
|
TAuthorize extends IOAuthorize<any, any, InferPlatformContextType<TPlatform>>,
|
|
26
17
|
TContext extends JsonObject,
|
|
27
|
-
> =
|
|
18
|
+
> = {
|
|
28
19
|
authorize?: TAuthorize;
|
|
29
20
|
context?: TContext;
|
|
30
21
|
crdt?: { doc: (value: any) => AbstractCrdtDocFactory<any> };
|
|
31
22
|
debug?: boolean;
|
|
32
|
-
getInitialStorage?: GetInitialStorageFn<TPlatform>;
|
|
33
23
|
platform: TPlatform;
|
|
34
24
|
};
|
|
35
25
|
|
|
36
|
-
export
|
|
26
|
+
export type ServerConfig<
|
|
37
27
|
TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>,
|
|
38
28
|
TAuthorize extends IOAuthorize<any, any, InferPlatformContextType<TPlatform>> = BaseIOAuthorize,
|
|
39
29
|
TContext extends JsonObject = {},
|
|
40
30
|
TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext> = {},
|
|
41
|
-
> {
|
|
31
|
+
> = Partial<PluvIOListeners<TPlatform, TAuthorize, TContext, TEvents>> & {
|
|
32
|
+
getInitialStorage?: GetInitialStorageFn<TPlatform>;
|
|
42
33
|
router?: PluvRouter<TPlatform, TAuthorize, TContext, TEvents>;
|
|
43
|
-
}
|
|
34
|
+
};
|
|
44
35
|
|
|
45
36
|
export class PluvIO<
|
|
46
37
|
TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>,
|
|
47
38
|
TAuthorize extends IOAuthorize<any, any, InferPlatformContextType<TPlatform>> = BaseIOAuthorize,
|
|
48
39
|
TContext extends JsonObject = {},
|
|
49
|
-
>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
$GET_OTHERS: this.procedure.sync((data, { room, session, sessions }) => {
|
|
53
|
-
const currentTime = new Date().getTime();
|
|
54
|
-
|
|
55
|
-
const others = sessions
|
|
56
|
-
.filter((wsSession) => {
|
|
57
|
-
if (wsSession.id === session?.id) return false;
|
|
58
|
-
if (wsSession.quit) return false;
|
|
59
|
-
if (currentTime - wsSession.timers.ping > PING_TIMEOUT_MS) return false;
|
|
60
|
-
|
|
61
|
-
return true;
|
|
62
|
-
})
|
|
63
|
-
.reduce<{
|
|
64
|
-
[connectionId: string]: {
|
|
65
|
-
connectionId: string;
|
|
66
|
-
room: string | null;
|
|
67
|
-
user: JsonObject;
|
|
68
|
-
};
|
|
69
|
-
}>(
|
|
70
|
-
(acc, { id, presence, user }) => ({
|
|
71
|
-
...acc,
|
|
72
|
-
[id]: {
|
|
73
|
-
connectionId: id,
|
|
74
|
-
presence,
|
|
75
|
-
room,
|
|
76
|
-
user,
|
|
77
|
-
},
|
|
78
|
-
}),
|
|
79
|
-
{},
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
return { $OTHERS_RECEIVED: { others } };
|
|
83
|
-
}),
|
|
84
|
-
$INITIALIZE_SESSION: this.procedure
|
|
85
|
-
.broadcast((data, { session }) => {
|
|
86
|
-
const presence = (data as any)?.presence;
|
|
87
|
-
|
|
88
|
-
if (!session) return {};
|
|
89
|
-
|
|
90
|
-
session.presence = presence;
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
$USER_JOINED: {
|
|
94
|
-
connectionId: session.id,
|
|
95
|
-
user: session.user,
|
|
96
|
-
presence,
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
})
|
|
100
|
-
.self(async (data, { context, doc, room }) => {
|
|
101
|
-
const oldState = await this._platform.persistance.getStorageState(room);
|
|
102
|
-
const update = (data as any)?.update as Maybe<string>;
|
|
103
|
-
|
|
104
|
-
const updates: readonly Maybe<string>[] = [
|
|
105
|
-
oldState
|
|
106
|
-
? null
|
|
107
|
-
: await this._getInitialStorage?.({
|
|
108
|
-
...context,
|
|
109
|
-
room,
|
|
110
|
-
}),
|
|
111
|
-
update,
|
|
112
|
-
];
|
|
113
|
-
|
|
114
|
-
if (updates.some((_update) => !!_update)) {
|
|
115
|
-
const encodedState = doc.batchApplyEncodedState(updates).getEncodedState();
|
|
116
|
-
|
|
117
|
-
await this._platform.persistance.setStorageState(room, encodedState);
|
|
118
|
-
|
|
119
|
-
this._listeners.onStorageUpdated({ ...context, encodedState, room });
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const state = (await this._platform.persistance.getStorageState(room)) ?? doc.getEncodedState();
|
|
123
|
-
|
|
124
|
-
return { $STORAGE_RECEIVED: { state } };
|
|
125
|
-
}),
|
|
126
|
-
$PING: this.procedure.self((data, { session }) => {
|
|
127
|
-
if (!session) return {};
|
|
128
|
-
|
|
129
|
-
const currentTime = new Date().getTime();
|
|
130
|
-
const prevState = session.webSocket.state;
|
|
131
|
-
|
|
132
|
-
session.webSocket.state = {
|
|
133
|
-
...prevState,
|
|
134
|
-
timers: {
|
|
135
|
-
...prevState.timers,
|
|
136
|
-
ping: currentTime,
|
|
137
|
-
},
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
return { $PONG: {} };
|
|
141
|
-
}),
|
|
142
|
-
$UPDATE_PRESENCE: this.procedure.broadcast((data, { session }) => {
|
|
143
|
-
const presence = (data as any)?.presence;
|
|
144
|
-
|
|
145
|
-
if (!session) return {};
|
|
146
|
-
|
|
147
|
-
const updated = Object.assign(Object.create(null), session.presence, presence);
|
|
148
|
-
|
|
149
|
-
session.webSocket.presence = updated;
|
|
150
|
-
|
|
151
|
-
return { $PRESENCE_UPDATED: { presence: updated } };
|
|
152
|
-
}),
|
|
153
|
-
$UPDATE_STORAGE: this.procedure.broadcast((data, { context, doc, room }) => {
|
|
154
|
-
const origin = (data as any)?.origin as Maybe<string>;
|
|
155
|
-
const update: string | null = (data as any)?.update ?? null;
|
|
156
|
-
|
|
157
|
-
if (origin === "$INITIALIZED" && Object.keys(doc.toJson()).length) {
|
|
158
|
-
return {};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const updated = update === null ? doc : doc.applyEncodedState({ update });
|
|
162
|
-
const encodedState = updated.getEncodedState();
|
|
163
|
-
|
|
164
|
-
this._platform.persistance.setStorageState(room, encodedState).then(() => {
|
|
165
|
-
this._listeners.onStorageUpdated({
|
|
166
|
-
...context,
|
|
167
|
-
encodedState,
|
|
168
|
-
room,
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
return { $STORAGE_UPDATED: { state: encodedState } };
|
|
173
|
-
}),
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
readonly _authorize: TAuthorize | null = null;
|
|
177
|
-
readonly _context: TContext = {} as TContext;
|
|
178
|
-
readonly _crdt: { doc: (value: any) => AbstractCrdtDocFactory<any> };
|
|
179
|
-
readonly _debug: boolean;
|
|
180
|
-
readonly _getInitialStorage: GetInitialStorageFn<TPlatform> | null = null;
|
|
181
|
-
readonly _listeners: PluvIOListeners<TPlatform>;
|
|
182
|
-
readonly _platform: TPlatform;
|
|
183
|
-
readonly _version: string = __PLUV_VERSION as any;
|
|
184
|
-
|
|
185
|
-
public get _events() {
|
|
186
|
-
return this._router._events;
|
|
187
|
-
}
|
|
40
|
+
> {
|
|
41
|
+
public readonly version: string = __PLUV_VERSION as any;
|
|
42
|
+
public readonly _authorize: TAuthorize | null = null;
|
|
188
43
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
44
|
+
private readonly _context: TContext = {} as TContext;
|
|
45
|
+
private readonly _crdt: { doc: (value: any) => AbstractCrdtDocFactory<any> };
|
|
46
|
+
private readonly _debug: boolean;
|
|
47
|
+
private readonly _platform: TPlatform;
|
|
192
48
|
|
|
193
49
|
public get procedure(): PluvProcedure<TPlatform, TAuthorize, TContext, {}, {}> {
|
|
194
50
|
return new PluvProcedure();
|
|
195
51
|
}
|
|
196
52
|
|
|
197
53
|
constructor(options: PluvIOConfig<TPlatform, TAuthorize, TContext>) {
|
|
198
|
-
const {
|
|
199
|
-
authorize,
|
|
200
|
-
context,
|
|
201
|
-
crdt = noop,
|
|
202
|
-
debug = false,
|
|
203
|
-
getInitialStorage,
|
|
204
|
-
onRoomDeleted,
|
|
205
|
-
onStorageUpdated,
|
|
206
|
-
platform,
|
|
207
|
-
} = options;
|
|
54
|
+
const { authorize, context, crdt = noop, debug = false, platform } = options;
|
|
208
55
|
|
|
209
56
|
this._crdt = crdt;
|
|
210
57
|
this._debug = debug;
|
|
@@ -212,17 +59,9 @@ export class PluvIO<
|
|
|
212
59
|
|
|
213
60
|
if (authorize) this._authorize = authorize;
|
|
214
61
|
if (context) this._context = context;
|
|
215
|
-
if (getInitialStorage) this._getInitialStorage = getInitialStorage;
|
|
216
|
-
|
|
217
|
-
this._listeners = {
|
|
218
|
-
onRoomDeleted: (event) => onRoomDeleted?.(event),
|
|
219
|
-
onStorageUpdated: (event) => onStorageUpdated?.(event),
|
|
220
|
-
};
|
|
221
62
|
}
|
|
222
63
|
|
|
223
|
-
public async createToken(
|
|
224
|
-
params: JWTEncodeParams<InferIOAuthorizeUser<InferIOAuthorize<this>>, TPlatform>,
|
|
225
|
-
): Promise<string> {
|
|
64
|
+
public async createToken(params: JWTEncodeParams<InferIOAuthorizeUser<TAuthorize>, TPlatform>): Promise<string> {
|
|
226
65
|
if (!this._authorize) throw new Error("IO does not specify authorize during initialization.");
|
|
227
66
|
|
|
228
67
|
const ioAuthorize =
|
|
@@ -251,21 +90,13 @@ export class PluvIO<
|
|
|
251
90
|
public server<TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>>(
|
|
252
91
|
config: ServerConfig<TPlatform, TAuthorize, TContext, TEvents> = {},
|
|
253
92
|
): PluvServer<TPlatform, TAuthorize, TContext, TEvents> {
|
|
254
|
-
const router = (config.router ? this.mergeRouters(this._router, config.router) : this._router) as PluvRouter<
|
|
255
|
-
TPlatform,
|
|
256
|
-
TAuthorize,
|
|
257
|
-
TContext,
|
|
258
|
-
TEvents
|
|
259
|
-
>;
|
|
260
|
-
|
|
261
93
|
return new PluvServer<TPlatform, TAuthorize, TContext, TEvents>({
|
|
262
|
-
...
|
|
94
|
+
...config,
|
|
263
95
|
authorize: this._authorize ?? undefined,
|
|
264
96
|
context: this._context,
|
|
265
97
|
crdt: this._crdt,
|
|
266
98
|
debug: this._debug,
|
|
267
99
|
platform: this._platform,
|
|
268
|
-
router,
|
|
269
100
|
});
|
|
270
101
|
}
|
|
271
102
|
}
|
package/src/PluvRouter.ts
CHANGED
|
@@ -6,9 +6,7 @@ export type PluvRouterEventConfig<
|
|
|
6
6
|
TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>,
|
|
7
7
|
TAuthorize extends IOAuthorize<any, any, InferRoomContextType<TPlatform>> = BaseIOAuthorize,
|
|
8
8
|
TContext extends JsonObject = {},
|
|
9
|
-
> = {
|
|
10
|
-
[P: string]: Pick<PluvProcedure<TPlatform, TAuthorize, TContext, any, any>, "config">;
|
|
11
|
-
};
|
|
9
|
+
> = { [P: string]: Pick<PluvProcedure<TPlatform, TAuthorize, TContext, any, any>, "config"> };
|
|
12
10
|
|
|
13
11
|
export type MergedRouter<
|
|
14
12
|
TRouters extends PluvRouter<TPlatform, TAuthorize, TContext, any>[] = [],
|
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 {
|
|
@@ -16,12 +17,15 @@ import type {
|
|
|
16
17
|
InferRoomContextType,
|
|
17
18
|
WebSocketRegistrationMode,
|
|
18
19
|
} from "./AbstractPlatform";
|
|
20
|
+
import type { IORoomListeners } from "./IORoom";
|
|
19
21
|
import { IORoom } from "./IORoom";
|
|
22
|
+
import { PluvProcedure } from "./PluvProcedure";
|
|
20
23
|
import type { PluvRouterEventConfig } from "./PluvRouter";
|
|
21
24
|
import { PluvRouter } from "./PluvRouter";
|
|
22
25
|
import type { JWTEncodeParams } from "./authorize";
|
|
23
26
|
import { authorize } from "./authorize";
|
|
24
|
-
import
|
|
27
|
+
import { PING_TIMEOUT_MS } from "./constants";
|
|
28
|
+
import type { GetInitialStorageFn, PluvIOListeners } from "./types";
|
|
25
29
|
import { __PLUV_VERSION } from "./version";
|
|
26
30
|
|
|
27
31
|
export type InferIORoom<TServer extends PluvServer<any, any, any, any>> =
|
|
@@ -34,18 +38,33 @@ export type PluvServerConfig<
|
|
|
34
38
|
TAuthorize extends IOAuthorize<any, any, InferPlatformContextType<TPlatform>> = BaseIOAuthorize,
|
|
35
39
|
TContext extends JsonObject = {},
|
|
36
40
|
TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext> = {},
|
|
37
|
-
> = Partial<PluvIOListeners<TPlatform>> & {
|
|
41
|
+
> = Partial<PluvIOListeners<TPlatform, TAuthorize, TContext, TEvents>> & {
|
|
38
42
|
authorize?: TAuthorize;
|
|
39
43
|
context?: TContext;
|
|
40
44
|
crdt?: { doc: (value: any) => AbstractCrdtDocFactory<any> };
|
|
41
45
|
debug?: boolean;
|
|
46
|
+
getInitialStorage?: GetInitialStorageFn<TPlatform>;
|
|
42
47
|
platform: TPlatform;
|
|
43
48
|
router?: PluvRouter<TPlatform, TAuthorize, TContext, TEvents>;
|
|
44
49
|
};
|
|
45
50
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
type BaseCreateRoomOptions<
|
|
52
|
+
TPlatform extends AbstractPlatform<any>,
|
|
53
|
+
TAuthorize extends IOAuthorize<any, any, InferPlatformContextType<TPlatform>>,
|
|
54
|
+
TContext extends JsonObject,
|
|
55
|
+
TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>,
|
|
56
|
+
> = Partial<IORoomListeners<TPlatform, TAuthorize, TContext, TEvents>> & {
|
|
57
|
+
debug?: boolean;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type CreateRoomOptions<
|
|
61
|
+
TPlatform extends AbstractPlatform<any>,
|
|
62
|
+
TAuthorize extends IOAuthorize<any, any, InferPlatformContextType<TPlatform>>,
|
|
63
|
+
TContext extends JsonObject,
|
|
64
|
+
TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>,
|
|
65
|
+
> = keyof InferRoomContextType<TPlatform> extends never
|
|
66
|
+
? [BaseCreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents>] | []
|
|
67
|
+
: [Id<BaseCreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents> & InferRoomContextType<TPlatform>>];
|
|
49
68
|
|
|
50
69
|
export class PluvServer<
|
|
51
70
|
TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>,
|
|
@@ -54,16 +73,134 @@ export class PluvServer<
|
|
|
54
73
|
TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext> = {},
|
|
55
74
|
> implements IORouterLike<TEvents>
|
|
56
75
|
{
|
|
57
|
-
|
|
76
|
+
public _listeners: PluvIOListeners<TPlatform, TAuthorize, TContext, TEvents>;
|
|
77
|
+
|
|
78
|
+
public readonly version: string = __PLUV_VERSION as any;
|
|
79
|
+
public readonly _authorize: TAuthorize | null = null;
|
|
80
|
+
|
|
81
|
+
private readonly _baseRouter: PluvRouter<TPlatform, TAuthorize, TContext, {}> = new PluvRouter({
|
|
82
|
+
$GET_OTHERS: this._procedure.sync((data, { room, session, sessions }) => {
|
|
83
|
+
const currentTime = new Date().getTime();
|
|
84
|
+
|
|
85
|
+
const others = sessions
|
|
86
|
+
.filter((wsSession) => {
|
|
87
|
+
if (wsSession.id === session?.id) return false;
|
|
88
|
+
if (wsSession.quit) return false;
|
|
89
|
+
if (currentTime - wsSession.timers.ping > PING_TIMEOUT_MS) return false;
|
|
90
|
+
|
|
91
|
+
return true;
|
|
92
|
+
})
|
|
93
|
+
.reduce<{
|
|
94
|
+
[connectionId: string]: {
|
|
95
|
+
connectionId: string;
|
|
96
|
+
room: string | null;
|
|
97
|
+
user: JsonObject;
|
|
98
|
+
};
|
|
99
|
+
}>(
|
|
100
|
+
(acc, { id, presence, user }) => ({
|
|
101
|
+
...acc,
|
|
102
|
+
[id]: {
|
|
103
|
+
connectionId: id,
|
|
104
|
+
presence,
|
|
105
|
+
room,
|
|
106
|
+
user,
|
|
107
|
+
},
|
|
108
|
+
}),
|
|
109
|
+
{},
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
return { $OTHERS_RECEIVED: { others } };
|
|
113
|
+
}),
|
|
114
|
+
$INITIALIZE_SESSION: this._procedure
|
|
115
|
+
.broadcast((data, { session }) => {
|
|
116
|
+
const presence = (data as any)?.presence;
|
|
117
|
+
|
|
118
|
+
if (!session) return {};
|
|
119
|
+
|
|
120
|
+
session.presence = presence;
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
$USER_JOINED: {
|
|
124
|
+
connectionId: session.id,
|
|
125
|
+
user: session.user,
|
|
126
|
+
presence,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
})
|
|
130
|
+
.self(async (data, { context, doc, room }) => {
|
|
131
|
+
const oldState = await this._platform.persistance.getStorageState(room);
|
|
132
|
+
const update = (data as any)?.update as Maybe<string>;
|
|
133
|
+
|
|
134
|
+
const updates: readonly Maybe<string>[] = [
|
|
135
|
+
oldState ? null : await this._getInitialStorage?.({ ...context, room }),
|
|
136
|
+
update,
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
if (updates.some((_update) => !!_update)) {
|
|
140
|
+
const encodedState = doc.batchApplyEncodedState(updates).getEncodedState();
|
|
141
|
+
|
|
142
|
+
await this._platform.persistance.setStorageState(room, encodedState);
|
|
143
|
+
|
|
144
|
+
this._listeners.onStorageUpdated({ context, encodedState, room });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const state = (await this._platform.persistance.getStorageState(room)) ?? doc.getEncodedState();
|
|
148
|
+
|
|
149
|
+
return { $STORAGE_RECEIVED: { state } };
|
|
150
|
+
}),
|
|
151
|
+
$PING: this._procedure.self((data, { session }) => {
|
|
152
|
+
if (!session) return {};
|
|
153
|
+
|
|
154
|
+
const currentTime = new Date().getTime();
|
|
155
|
+
const prevState = session.webSocket.state;
|
|
156
|
+
|
|
157
|
+
session.webSocket.state = {
|
|
158
|
+
...prevState,
|
|
159
|
+
timers: {
|
|
160
|
+
...prevState.timers,
|
|
161
|
+
ping: currentTime,
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return { $PONG: {} };
|
|
166
|
+
}),
|
|
167
|
+
$UPDATE_PRESENCE: this._procedure.broadcast((data, { session }) => {
|
|
168
|
+
const presence = (data as any)?.presence;
|
|
58
169
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
170
|
+
if (!session) return {};
|
|
171
|
+
|
|
172
|
+
const updated = Object.assign(Object.create(null), session.presence, presence);
|
|
173
|
+
|
|
174
|
+
session.webSocket.presence = updated;
|
|
175
|
+
|
|
176
|
+
return { $PRESENCE_UPDATED: { presence: updated } };
|
|
177
|
+
}),
|
|
178
|
+
$UPDATE_STORAGE: this._procedure.broadcast((data, { context, doc, room }) => {
|
|
179
|
+
const origin = (data as any)?.origin as Maybe<string>;
|
|
180
|
+
const update: string | null = (data as any)?.update ?? null;
|
|
181
|
+
|
|
182
|
+
if (origin === "$INITIALIZED" && Object.keys(doc.toJson()).length) return {};
|
|
183
|
+
|
|
184
|
+
const updated = update === null ? doc : doc.applyEncodedState({ update });
|
|
185
|
+
const encodedState = updated.getEncodedState();
|
|
186
|
+
|
|
187
|
+
this._platform.persistance.setStorageState(room, encodedState).then(() => {
|
|
188
|
+
this._listeners.onStorageUpdated({
|
|
189
|
+
context,
|
|
190
|
+
encodedState,
|
|
191
|
+
room,
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
return { $STORAGE_UPDATED: { state: encodedState } };
|
|
196
|
+
}),
|
|
197
|
+
});
|
|
198
|
+
private readonly _context: TContext = {} as TContext;
|
|
199
|
+
private readonly _crdt: { doc: (value: any) => AbstractCrdtDocFactory<any> };
|
|
200
|
+
private readonly _debug: boolean;
|
|
201
|
+
private readonly _getInitialStorage: GetInitialStorageFn<TPlatform> | null = null;
|
|
202
|
+
private readonly _platform: TPlatform;
|
|
203
|
+
private readonly _router: PluvRouter<TPlatform, TAuthorize, TContext, TEvents>;
|
|
67
204
|
|
|
68
205
|
public get _events() {
|
|
69
206
|
return this._router._events;
|
|
@@ -73,13 +210,19 @@ export class PluvServer<
|
|
|
73
210
|
return this._platform._registrationMode;
|
|
74
211
|
}
|
|
75
212
|
|
|
213
|
+
private get _procedure(): PluvProcedure<TPlatform, TAuthorize, TContext, {}, {}> {
|
|
214
|
+
return new PluvProcedure();
|
|
215
|
+
}
|
|
216
|
+
|
|
76
217
|
constructor(options: PluvServerConfig<TPlatform, TAuthorize, TContext, TEvents>) {
|
|
77
218
|
const {
|
|
78
219
|
authorize,
|
|
79
220
|
context,
|
|
80
221
|
crdt = noop,
|
|
81
222
|
debug = false,
|
|
223
|
+
getInitialStorage,
|
|
82
224
|
onRoomDeleted,
|
|
225
|
+
onRoomMessage,
|
|
83
226
|
onStorageUpdated,
|
|
84
227
|
platform,
|
|
85
228
|
router = new PluvRouter<TPlatform, TAuthorize, TContext, TEvents>({} as TEvents),
|
|
@@ -88,52 +231,34 @@ export class PluvServer<
|
|
|
88
231
|
this._crdt = crdt;
|
|
89
232
|
this._debug = debug;
|
|
90
233
|
this._platform = platform;
|
|
91
|
-
this._router = router
|
|
234
|
+
this._router = (router ? this._baseRouter : PluvRouter.merge(this._baseRouter, router)) as PluvRouter<
|
|
235
|
+
TPlatform,
|
|
236
|
+
TAuthorize,
|
|
237
|
+
TContext,
|
|
238
|
+
TEvents
|
|
239
|
+
>;
|
|
92
240
|
|
|
93
241
|
if (authorize) this._authorize = authorize;
|
|
94
242
|
if (context) this._context = context;
|
|
243
|
+
if (getInitialStorage) this._getInitialStorage = getInitialStorage;
|
|
95
244
|
|
|
96
245
|
this._listeners = {
|
|
97
246
|
onRoomDeleted: (event) => onRoomDeleted?.(event),
|
|
247
|
+
onRoomMessage: (event) => onRoomMessage?.(event),
|
|
98
248
|
onStorageUpdated: (event) => onStorageUpdated?.(event),
|
|
99
249
|
};
|
|
100
250
|
}
|
|
101
251
|
|
|
102
|
-
public
|
|
103
|
-
params: JWTEncodeParams<InferIOAuthorizeUser<InferIOAuthorize<this>>, TPlatform>,
|
|
104
|
-
): Promise<string> {
|
|
105
|
-
if (!this._authorize) {
|
|
106
|
-
throw new Error("IO does not specify authorize during initialization.");
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const ioAuthorize =
|
|
110
|
-
typeof this._authorize === "function" ? this._authorize(params) : (this._authorize as { secret: string });
|
|
111
|
-
|
|
112
|
-
const secret = ioAuthorize.secret;
|
|
113
|
-
|
|
114
|
-
return await authorize({
|
|
115
|
-
platform: this._platform,
|
|
116
|
-
secret,
|
|
117
|
-
}).encode(params);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
public getRoom(
|
|
252
|
+
public createRoom(
|
|
121
253
|
room: string,
|
|
122
|
-
...options:
|
|
254
|
+
...options: CreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents>
|
|
123
255
|
): IORoom<TPlatform, TAuthorize, TContext, TEvents> {
|
|
124
|
-
const { debug, ...platformRoomContext } = options[0] ?? {};
|
|
125
|
-
|
|
126
|
-
this._purgeEmptyRooms();
|
|
256
|
+
const { debug, onDestroy, onMessage, ...platformRoomContext } = options[0] ?? {};
|
|
127
257
|
|
|
128
258
|
if (!/^[a-z0-9]+[a-z0-9\-_]+[a-z0-9]+$/i.test(room)) {
|
|
129
259
|
throw new Error("Unsupported room name");
|
|
130
260
|
}
|
|
131
261
|
|
|
132
|
-
const platform = this._platform;
|
|
133
|
-
const oldRoom = this._rooms.get(room);
|
|
134
|
-
|
|
135
|
-
if (oldRoom) return oldRoom;
|
|
136
|
-
|
|
137
262
|
const roomContext = {
|
|
138
263
|
...this._context,
|
|
139
264
|
...platformRoomContext,
|
|
@@ -144,54 +269,47 @@ export class PluvServer<
|
|
|
144
269
|
context: roomContext,
|
|
145
270
|
crdt: this._crdt,
|
|
146
271
|
debug: debug ?? this._debug,
|
|
147
|
-
onDestroy: async (
|
|
272
|
+
onDestroy: async (event) => {
|
|
148
273
|
this._logDebug(`${colors.blue("Deleting empty room:")} ${room}`);
|
|
149
274
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
encodedState,
|
|
153
|
-
...platformRoomContext,
|
|
154
|
-
} as IORoomListenerEvent<TPlatform> & InferRoomContextType<TPlatform>;
|
|
155
|
-
|
|
156
|
-
this._rooms.delete(room);
|
|
157
|
-
|
|
158
|
-
await Promise.resolve(this._listeners.onRoomDeleted(roomContext));
|
|
275
|
+
await Promise.resolve(onDestroy?.(event));
|
|
276
|
+
await Promise.resolve(this._listeners.onRoomDeleted(event));
|
|
159
277
|
await this._platform.persistance.deleteStorageState(room);
|
|
160
278
|
|
|
161
|
-
|
|
162
|
-
const rooms = Array.from(this._rooms.keys());
|
|
163
|
-
|
|
164
|
-
this._logDebug(`${colors.blue("Deleted room:")} ${room}`);
|
|
165
|
-
this._logDebug(`${colors.blue("Rooms available:")} ${rooms.join(", ")}`);
|
|
166
|
-
}
|
|
279
|
+
this._logDebug(`${colors.blue("Deleted room:")} ${room}`);
|
|
167
280
|
},
|
|
168
|
-
|
|
281
|
+
onMessage: async (event) => {
|
|
282
|
+
await Promise.resolve(onMessage?.(event));
|
|
283
|
+
await Promise.resolve(this._listeners.onRoomMessage(event));
|
|
284
|
+
},
|
|
285
|
+
platform: this._platform,
|
|
169
286
|
router: this._router,
|
|
170
287
|
});
|
|
171
288
|
|
|
172
|
-
this.
|
|
289
|
+
this._logDebug(`${colors.blue("Created room:")} ${room}`);
|
|
173
290
|
|
|
174
|
-
|
|
175
|
-
|
|
291
|
+
return newRoom;
|
|
292
|
+
}
|
|
176
293
|
|
|
177
|
-
|
|
178
|
-
|
|
294
|
+
public async createToken(
|
|
295
|
+
params: JWTEncodeParams<InferIOAuthorizeUser<InferIOAuthorize<this>>, TPlatform>,
|
|
296
|
+
): Promise<string> {
|
|
297
|
+
if (!this._authorize) {
|
|
298
|
+
throw new Error("IO does not specify authorize during initialization.");
|
|
179
299
|
}
|
|
180
300
|
|
|
181
|
-
|
|
301
|
+
const ioAuthorize =
|
|
302
|
+
typeof this._authorize === "function" ? this._authorize(params) : (this._authorize as { secret: string });
|
|
303
|
+
|
|
304
|
+
const secret = ioAuthorize.secret;
|
|
305
|
+
|
|
306
|
+
return await authorize({
|
|
307
|
+
platform: this._platform,
|
|
308
|
+
secret,
|
|
309
|
+
}).encode(params);
|
|
182
310
|
}
|
|
183
311
|
|
|
184
312
|
private _logDebug(...data: any[]): void {
|
|
185
313
|
this._debug && console.log(...data);
|
|
186
314
|
}
|
|
187
|
-
|
|
188
|
-
private _purgeEmptyRooms(): void {
|
|
189
|
-
const rooms = Array.from(this._rooms.entries());
|
|
190
|
-
|
|
191
|
-
rooms.forEach(([id, room]) => {
|
|
192
|
-
if (!!room?.getSize()) return;
|
|
193
|
-
|
|
194
|
-
this._rooms.delete(id);
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
315
|
}
|
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
|
};
|