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