@colyseus/core 0.17.39 → 0.17.41
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/README.md +15 -1
- package/build/MatchMaker.cjs +40 -0
- package/build/MatchMaker.cjs.map +2 -2
- package/build/MatchMaker.d.ts +13 -0
- package/build/MatchMaker.mjs +39 -0
- package/build/MatchMaker.mjs.map +2 -2
- package/build/Room.cjs +47 -17
- package/build/Room.cjs.map +2 -2
- package/build/Room.d.ts +1 -0
- package/build/Room.mjs +27 -7
- package/build/Room.mjs.map +2 -2
- package/build/Server.cjs +35 -6
- package/build/Server.cjs.map +2 -2
- package/build/Server.d.ts +3 -0
- package/build/Server.mjs +32 -5
- package/build/Server.mjs.map +2 -2
- package/build/errors/ServerError.cjs +2 -2
- package/build/errors/ServerError.cjs.map +2 -2
- package/build/errors/ServerError.d.ts +1 -1
- package/build/errors/ServerError.mjs +2 -2
- package/build/errors/ServerError.mjs.map +2 -2
- package/build/index.cjs +9 -0
- package/build/index.cjs.map +2 -2
- package/build/index.d.ts +3 -2
- package/build/index.mjs +7 -2
- package/build/index.mjs.map +2 -2
- package/build/router/index.cjs +1 -1
- package/build/router/index.cjs.map +2 -2
- package/build/router/index.mjs +1 -1
- package/build/router/index.mjs.map +2 -2
- package/build/router/node.cjs +98 -0
- package/build/router/node.cjs.map +7 -0
- package/build/router/node.d.ts +11 -0
- package/build/router/node.mjs +63 -0
- package/build/router/node.mjs.map +7 -0
- package/build/utils/DevMode.cjs +14 -2
- package/build/utils/DevMode.cjs.map +2 -2
- package/build/utils/DevMode.mjs +15 -3
- package/build/utils/DevMode.mjs.map +2 -2
- package/build/utils/Utils.cjs +4 -1
- package/build/utils/Utils.cjs.map +2 -2
- package/build/utils/Utils.mjs +4 -1
- package/build/utils/Utils.mjs.map +2 -2
- package/package.json +8 -8
- package/src/MatchMaker.ts +67 -0
- package/src/Room.ts +34 -11
- package/src/Server.ts +47 -6
- package/src/errors/ServerError.ts +2 -2
- package/src/index.ts +3 -2
- package/src/router/index.ts +1 -1
- package/src/router/node.ts +88 -0
- package/src/utils/DevMode.ts +29 -6
- package/src/utils/Utils.ts +2 -2
package/src/Room.ts
CHANGED
|
@@ -23,7 +23,7 @@ import { ClientState, type AuthContext, type Client, type ClientPrivate, ClientA
|
|
|
23
23
|
import { type RoomMethodName, OnAuthException, OnCreateException, OnDisposeException, OnDropException, OnJoinException, OnLeaveException, OnMessageException, OnReconnectException, type RoomException, SimulationIntervalException, TimedEventException } from './errors/RoomExceptions.ts';
|
|
24
24
|
|
|
25
25
|
import { standardValidate, type StandardSchemaV1 } from './utils/StandardSchema.ts';
|
|
26
|
-
import
|
|
26
|
+
import * as matchMaker from './MatchMaker.ts';
|
|
27
27
|
|
|
28
28
|
import {
|
|
29
29
|
CloseCode,
|
|
@@ -551,7 +551,7 @@ export class Room<T extends RoomOptions = RoomOptions> {
|
|
|
551
551
|
(isDevMode)
|
|
552
552
|
? CloseCode.MAY_TRY_RECONNECT
|
|
553
553
|
: CloseCode.SERVER_SHUTDOWN
|
|
554
|
-
);
|
|
554
|
+
).catch(() => {});
|
|
555
555
|
}
|
|
556
556
|
|
|
557
557
|
/**
|
|
@@ -1066,9 +1066,7 @@ export class Room<T extends RoomOptions = RoomOptions> {
|
|
|
1066
1066
|
this._events.once('disconnect', () => resolve()));
|
|
1067
1067
|
|
|
1068
1068
|
// reject pending reconnections
|
|
1069
|
-
|
|
1070
|
-
reconnection.reject(new Error("disconnecting"));
|
|
1071
|
-
}
|
|
1069
|
+
this._rejectPendingReconnections("disconnecting");
|
|
1072
1070
|
|
|
1073
1071
|
let numClients = this.clients.length;
|
|
1074
1072
|
if (numClients > 0) {
|
|
@@ -1085,6 +1083,15 @@ export class Room<T extends RoomOptions = RoomOptions> {
|
|
|
1085
1083
|
return delayedDisconnection;
|
|
1086
1084
|
}
|
|
1087
1085
|
|
|
1086
|
+
private _rejectPendingReconnections(message: string) {
|
|
1087
|
+
for (const [_, reconnection] of Object.values(this._reconnections)) {
|
|
1088
|
+
reconnection.reject(new ServerError(CloseCode.NORMAL_CLOSURE, message));
|
|
1089
|
+
// Suppress unhandled rejection — expected during shutdown/devMode
|
|
1090
|
+
// restart, handled downstream by _onLeave's .catch() handler.
|
|
1091
|
+
reconnection.catch(() => {});
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1088
1095
|
private async _onJoin(
|
|
1089
1096
|
client: ExtractRoomClient<T> & ClientPrivate,
|
|
1090
1097
|
authContext: AuthContext,
|
|
@@ -1323,7 +1330,7 @@ export class Room<T extends RoomOptions = RoomOptions> {
|
|
|
1323
1330
|
//
|
|
1324
1331
|
if ((previousClient as unknown as ClientPrivate)._enqueuedMessages !== undefined) {
|
|
1325
1332
|
// @ts-ignore
|
|
1326
|
-
return Promise.reject(new
|
|
1333
|
+
return Promise.reject(new ServerError("not joined"));
|
|
1327
1334
|
}
|
|
1328
1335
|
|
|
1329
1336
|
if (seconds === undefined) { // TODO: remove this check
|
|
@@ -1497,13 +1504,26 @@ export class Room<T extends RoomOptions = RoomOptions> {
|
|
|
1497
1504
|
this.resetAutoDisposeTimeout(seconds);
|
|
1498
1505
|
}
|
|
1499
1506
|
|
|
1500
|
-
//
|
|
1501
|
-
// TODO: isDevMode workaround to allow players to reconnect on devMode
|
|
1502
|
-
//
|
|
1503
1507
|
if (devModeReconnectionToken) {
|
|
1504
|
-
// Set up reconnection token mapping
|
|
1505
1508
|
const reconnection = new Deferred<Client & ClientPrivate>();
|
|
1506
1509
|
this._reconnections[devModeReconnectionToken] = [sessionId, reconnection];
|
|
1510
|
+
|
|
1511
|
+
// If the client doesn't reconnect within the timeout, call onLeave
|
|
1512
|
+
// so the room can clean up stale state (e.g. delete player data).
|
|
1513
|
+
clearTimeout(this._reservedSeatTimeouts[sessionId]);
|
|
1514
|
+
this._reservedSeatTimeouts[sessionId] = setTimeout(async () => {
|
|
1515
|
+
if (!this._reconnections[devModeReconnectionToken]) { return; }
|
|
1516
|
+
|
|
1517
|
+
delete this._reconnections[devModeReconnectionToken];
|
|
1518
|
+
delete this._reservedSeats[sessionId];
|
|
1519
|
+
delete this._reservedSeatTimeouts[sessionId];
|
|
1520
|
+
|
|
1521
|
+
if (!allowReconnection) {
|
|
1522
|
+
await this.#_decrementClientCount();
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
this.onLeave?.({ sessionId } as any, CloseCode.MAY_TRY_RECONNECT);
|
|
1526
|
+
}, seconds * 1000);
|
|
1507
1527
|
}
|
|
1508
1528
|
|
|
1509
1529
|
return true;
|
|
@@ -1717,7 +1737,10 @@ export class Room<T extends RoomOptions = RoomOptions> {
|
|
|
1717
1737
|
await method.call(this, client, code);
|
|
1718
1738
|
|
|
1719
1739
|
} catch (e: any) {
|
|
1720
|
-
|
|
1740
|
+
const serverError = (!(e instanceof ServerError))
|
|
1741
|
+
? new ServerError(CloseCode.WITH_ERROR, `${method.name} error`, { cause: e })
|
|
1742
|
+
: e;
|
|
1743
|
+
debugAndPrintError(serverError);
|
|
1721
1744
|
|
|
1722
1745
|
} finally {
|
|
1723
1746
|
this.#_onLeaveConcurrent--;
|
package/src/Server.ts
CHANGED
|
@@ -370,6 +370,40 @@ export class Server<
|
|
|
370
370
|
() => Promise.resolve()
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
+
export type RoomDefinitions = Record<string, RegisteredHandler | Type<Room>>;
|
|
374
|
+
|
|
375
|
+
function isRegisteredHandler(value: RegisteredHandler | Type<Room>): value is RegisteredHandler {
|
|
376
|
+
return value instanceof RegisteredHandler || (
|
|
377
|
+
typeof(value) === "object" &&
|
|
378
|
+
value !== null &&
|
|
379
|
+
'klass' in (value as object)
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function registerRoomDefinitions<T extends RoomDefinitions>(rooms: T): string[] {
|
|
384
|
+
const roomNames: string[] = [];
|
|
385
|
+
|
|
386
|
+
for (const [name, value] of Object.entries(rooms)) {
|
|
387
|
+
if (isRegisteredHandler(value)) {
|
|
388
|
+
value.name = name;
|
|
389
|
+
matchMaker.addRoomType(value);
|
|
390
|
+
|
|
391
|
+
} else {
|
|
392
|
+
matchMaker.defineRoomType(name, value);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
roomNames.push(name);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return roomNames;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function unregisterRoomDefinitions(roomNames: Iterable<string>) {
|
|
402
|
+
for (const roomName of roomNames) {
|
|
403
|
+
matchMaker.removeRoomType(roomName);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
373
407
|
export type DefineServerOptions<
|
|
374
408
|
T extends Record<string, RegisteredHandler>,
|
|
375
409
|
R extends Router
|
|
@@ -385,14 +419,21 @@ export function defineServer<
|
|
|
385
419
|
options: DefineServerOptions<T, R>,
|
|
386
420
|
): Server<T, R> {
|
|
387
421
|
const { rooms, routes, ...serverOptions } = options;
|
|
388
|
-
const server = new Server<T, R>(serverOptions);
|
|
389
422
|
|
|
423
|
+
if (isDevMode) {
|
|
424
|
+
// In dev mode, the Vite plugin manages Server/matchMaker lifecycle.
|
|
425
|
+
// Return a config-only object — no Server instance, no matchMaker.setup().
|
|
426
|
+
return {
|
|
427
|
+
options: serverOptions,
|
|
428
|
+
router: routes,
|
|
429
|
+
'~rooms': rooms,
|
|
430
|
+
} as unknown as Server<T, R>;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const server = new Server<T, R>(serverOptions);
|
|
390
434
|
server.router = routes;
|
|
391
435
|
|
|
392
|
-
|
|
393
|
-
handler.name = name;
|
|
394
|
-
matchMaker.addRoomType(handler);
|
|
395
|
-
}
|
|
436
|
+
registerRoomDefinitions(rooms);
|
|
396
437
|
|
|
397
438
|
return server;
|
|
398
439
|
}
|
|
@@ -402,4 +443,4 @@ export function defineRoom<T extends Type<Room>>(
|
|
|
402
443
|
defaultOptions?: Parameters<NonNullable<InstanceType<T>['onCreate']>>[0],
|
|
403
444
|
): RegisteredHandler<InstanceType<T>> {
|
|
404
445
|
return new RegisteredHandler(roomKlass, defaultOptions) as unknown as RegisteredHandler<InstanceType<T>>;
|
|
405
|
-
}
|
|
446
|
+
}
|
|
@@ -3,8 +3,8 @@ import { ErrorCode } from '@colyseus/shared-types';
|
|
|
3
3
|
export class ServerError extends Error {
|
|
4
4
|
public code: number;
|
|
5
5
|
|
|
6
|
-
constructor(code: number = ErrorCode.MATCHMAKE_UNHANDLED, message?: string) {
|
|
7
|
-
super(message);
|
|
6
|
+
constructor(code: number = ErrorCode.MATCHMAKE_UNHANDLED, message?: string, options?: ErrorOptions) {
|
|
7
|
+
super(message, options);
|
|
8
8
|
|
|
9
9
|
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
10
10
|
if (Error.captureStackTrace) {
|
package/src/index.ts
CHANGED
|
@@ -11,7 +11,7 @@ export {
|
|
|
11
11
|
} from '@colyseus/shared-types';
|
|
12
12
|
|
|
13
13
|
// Core classes
|
|
14
|
-
export { Server, defineRoom, defineServer, type ServerOptions, type SDKTypes } from './Server.ts';
|
|
14
|
+
export { Server, defineRoom, defineServer, registerRoomDefinitions, unregisterRoomDefinitions, type RoomDefinitions, type ServerOptions, type SDKTypes } from './Server.ts';
|
|
15
15
|
export { Room, room, RoomInternalState, validate, type RoomOptions, type MessageHandlerWithFormat, type Messages, type ExtractRoomState, type ExtractRoomMetadata, type ExtractRoomClient } from './Room.ts';
|
|
16
16
|
export { getMessageBytes } from './Protocol.ts';
|
|
17
17
|
export { RegisteredHandler } from './matchmaker/RegisteredHandler.ts';
|
|
@@ -34,6 +34,7 @@ export {
|
|
|
34
34
|
import * as matchMaker from './MatchMaker.ts';
|
|
35
35
|
export { matchMaker };
|
|
36
36
|
export { updateLobby, subscribeLobby } from './matchmaker/Lobby.ts';
|
|
37
|
+
export { createNodeMatchmakingMiddleware } from './router/node.ts';
|
|
37
38
|
|
|
38
39
|
// Driver
|
|
39
40
|
export * from './matchmaker/LocalDriver/LocalDriver.ts';
|
|
@@ -53,7 +54,7 @@ export { SchemaSerializer } from './serializer/SchemaSerializer.ts';
|
|
|
53
54
|
// Utilities
|
|
54
55
|
export { Clock, Delayed };
|
|
55
56
|
export { generateId, Deferred, spliceOne, getBearerToken, dynamicImport } from './utils/Utils.ts';
|
|
56
|
-
export { isDevMode } from './utils/DevMode.ts';
|
|
57
|
+
export { isDevMode, setDevMode } from './utils/DevMode.ts';
|
|
57
58
|
|
|
58
59
|
// IPC
|
|
59
60
|
export { subscribeIPC, requestFromIPC } from './IPC.ts';
|
package/src/router/index.ts
CHANGED
|
@@ -68,7 +68,7 @@ export function bindRouterToTransport(transport: Transport, router: Router, useE
|
|
|
68
68
|
next = async (req: IncomingMessage, res: ServerResponse) => {
|
|
69
69
|
// check if the route is defined in the router
|
|
70
70
|
// if so, use the router handler, otherwise fallback to express
|
|
71
|
-
if (router.findRoute(req.method, req.url) !== undefined) {
|
|
71
|
+
if (router.findRoute(req.method, req.url.split('?')[0]) !== undefined) {
|
|
72
72
|
const protocol = req.headers["x-forwarded-proto"] || ((req.socket as any).encrypted ? "https" : "http");
|
|
73
73
|
const base = `${protocol}://${req.headers[":authority"] || req.headers.host}`;
|
|
74
74
|
const response = await router.handler(getRequest({ base, request: req }));
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raw Node.js adapter for Colyseus matchmaking routes used by `colyseus/vite`.
|
|
3
|
+
*
|
|
4
|
+
* This file exists specifically so the Vite plugin can share Vite's dev HTTP
|
|
5
|
+
* server while still exposing the Colyseus `/matchmake/*` endpoints.
|
|
6
|
+
*
|
|
7
|
+
* Keep the matchmaking behavior itself in `router/default_routes.ts` and use
|
|
8
|
+
* this file only as the thin raw Node/Express adapter around it.
|
|
9
|
+
*/
|
|
10
|
+
import type http from 'http';
|
|
11
|
+
import { URL } from 'url';
|
|
12
|
+
import * as matchMaker from '../MatchMaker.ts';
|
|
13
|
+
import { setResponse } from '@colyseus/better-call/node';
|
|
14
|
+
import { postMatchmakeMethod } from './default_routes.ts';
|
|
15
|
+
|
|
16
|
+
function readBody(req: http.IncomingMessage): Promise<any> {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
let data = '';
|
|
19
|
+
|
|
20
|
+
req.on('data', (chunk: Buffer | string) => {
|
|
21
|
+
data += chunk.toString();
|
|
22
|
+
});
|
|
23
|
+
req.on('end', () => resolve(data ? JSON.parse(data) : {}));
|
|
24
|
+
req.on('error', reject);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getCorsHeaders(req: http.IncomingMessage, headers?: Headers): Record<string, string> {
|
|
29
|
+
return {
|
|
30
|
+
...matchMaker.controller.DEFAULT_CORS_HEADERS,
|
|
31
|
+
...matchMaker.controller.getCorsHeaders(headers),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function createNodeMatchmakingMiddleware() {
|
|
36
|
+
return async (
|
|
37
|
+
req: http.IncomingMessage,
|
|
38
|
+
res: http.ServerResponse,
|
|
39
|
+
next: () => void,
|
|
40
|
+
) => {
|
|
41
|
+
const url = new URL(req.url || '/', 'http://localhost');
|
|
42
|
+
const isMatchmakeRoute = url.pathname.startsWith(`/${matchMaker.controller.matchmakeRoute}/`);
|
|
43
|
+
|
|
44
|
+
if (!isMatchmakeRoute) {
|
|
45
|
+
next();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const headers = new Headers(req.headers as Record<string, string>);
|
|
50
|
+
const corsHeaders = getCorsHeaders(req, headers);
|
|
51
|
+
|
|
52
|
+
if (req.method === 'OPTIONS') {
|
|
53
|
+
res.writeHead(204, corsHeaders);
|
|
54
|
+
res.end();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (req.method !== 'POST') {
|
|
59
|
+
next();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const match = url.pathname.match(/^\/matchmake\/(\w+)\/(.+)/);
|
|
64
|
+
if (!match) {
|
|
65
|
+
next();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const [, method, roomName] = match;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const response = await postMatchmakeMethod({
|
|
73
|
+
params: { method, roomName },
|
|
74
|
+
body: await readBody(req),
|
|
75
|
+
headers: req.headers as Record<string, string>,
|
|
76
|
+
request: { headers } as any,
|
|
77
|
+
asResponse: true,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await setResponse(res, response);
|
|
81
|
+
|
|
82
|
+
} catch {
|
|
83
|
+
// Endpoint-level failures are returned as Response when `asResponse` is true.
|
|
84
|
+
// Any thrown error here is unexpected, so let the next middleware decide.
|
|
85
|
+
next();
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
package/src/utils/DevMode.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { type Schema, MapSchema, ArraySchema, SetSchema, CollectionSchema, $childType } from '@colyseus/schema';
|
|
3
|
+
import { type Schema, MapSchema, ArraySchema, SetSchema, CollectionSchema, $childType, $changes } from '@colyseus/schema';
|
|
4
4
|
import { logger } from '../Logger.ts';
|
|
5
5
|
import { debugAndPrintError, debugDevMode } from '../Debug.ts';
|
|
6
6
|
import { getLocalRoomById, handleCreateRoom, presence, remoteRoomCall } from '../MatchMaker.ts';
|
|
@@ -44,19 +44,33 @@ export async function reloadFromCache() {
|
|
|
44
44
|
logger.debug(`📋 room '${roomId}' state =>`, rawState);
|
|
45
45
|
|
|
46
46
|
(recreatedRoom.state as Schema).restore(rawState);
|
|
47
|
+
|
|
48
|
+
// Restore the encoder's nextUniqueId so refIds increase
|
|
49
|
+
// monotonically across HMR cycles. Without this, restore()
|
|
50
|
+
// always produces the same refIds (0,1,2,3...) and onJoin()
|
|
51
|
+
// always assigns the same next refIds (4,5...), causing the
|
|
52
|
+
// client decoder to reuse stale instances on the 2nd+ cycle.
|
|
53
|
+
if (roomHistory.nextRefId !== undefined) {
|
|
54
|
+
const encoderRoot = recreatedRoom.state[$changes]?.root;
|
|
55
|
+
if (encoderRoot && roomHistory.nextRefId > encoderRoot['nextUniqueId']) {
|
|
56
|
+
encoderRoot['nextUniqueId'] = roomHistory.nextRefId;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
47
59
|
} catch (e: any) {
|
|
48
60
|
debugAndPrintError(`❌ couldn't restore room '${roomId}' state:\n${e.stack}`);
|
|
49
61
|
}
|
|
50
62
|
}
|
|
51
63
|
|
|
52
|
-
// Reserve seats for clients from cached history
|
|
64
|
+
// Reserve seats for clients from cached history.
|
|
65
|
+
// Skip entries without a reconnectionToken — these are stale
|
|
66
|
+
// seats from allowReconnection() where the client already left
|
|
67
|
+
// (e.g. page refresh). Restoring them would block room disposal.
|
|
53
68
|
if (roomHistory.clients) {
|
|
54
69
|
for (const clientData of roomHistory.clients) {
|
|
55
|
-
// TODO: need to restore each client's StateView as well
|
|
56
|
-
// reserve seat for 20 seconds
|
|
57
70
|
const { sessionId, reconnectionToken } = clientData;
|
|
58
|
-
|
|
59
|
-
|
|
71
|
+
if (!reconnectionToken) { continue; }
|
|
72
|
+
// TODO: need to restore each client's StateView as well
|
|
73
|
+
await remoteRoomCall(recreatedRoomListing.roomId, '_reserveSeat', [sessionId, {}, {}, recreatedRoom.seatReservationTimeout, false, reconnectionToken]);
|
|
60
74
|
}
|
|
61
75
|
}
|
|
62
76
|
|
|
@@ -86,6 +100,15 @@ export async function cacheRoomHistory(rooms: { [roomId: string]: Room }) {
|
|
|
86
100
|
|
|
87
101
|
if (room.state) {
|
|
88
102
|
roomHistory["state"] = JSON.stringify(room.state);
|
|
103
|
+
|
|
104
|
+
// Cache the encoder's nextUniqueId so it can be restored.
|
|
105
|
+
// This ensures refIds increase monotonically across HMR cycles,
|
|
106
|
+
// preventing the client decoder from reusing stale refs that
|
|
107
|
+
// happen to have the same refId as newly created instances.
|
|
108
|
+
const encoderRoot = room.state[$changes]?.root;
|
|
109
|
+
if (encoderRoot) {
|
|
110
|
+
roomHistory["nextRefId"] = encoderRoot['nextUniqueId'];
|
|
111
|
+
}
|
|
89
112
|
}
|
|
90
113
|
|
|
91
114
|
// cache active clients with their reconnection tokens
|
package/src/utils/Utils.ts
CHANGED
|
@@ -190,8 +190,8 @@ export function dynamicImport<T = any>(moduleName: string): Promise<T> {
|
|
|
190
190
|
}
|
|
191
191
|
} else {
|
|
192
192
|
// ESM context - use import()
|
|
193
|
-
const promise = import(moduleName);
|
|
193
|
+
const promise = import(/* @vite-ignore */ moduleName);
|
|
194
194
|
promise.catch(() => {}); // prevent unhandled rejection warnings
|
|
195
195
|
return promise;
|
|
196
196
|
}
|
|
197
|
-
}
|
|
197
|
+
}
|