@pluv/io 1.0.0 → 1.0.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 +8 -8
- package/CHANGELOG.md +8 -0
- package/README.md +5 -0
- package/dist/index.d.mts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +65 -31
- package/dist/index.mjs +65 -31
- package/package.json +5 -5
- package/src/AbstractPersistence.ts +8 -2
- package/src/AbstractPlatform.ts +6 -2
- package/src/AbstractWebSocket.ts +11 -3
- package/src/IORoom.ts +113 -37
- package/src/PluvIO.ts +38 -11
- package/src/PluvProcedure.ts +57 -10
- package/src/PluvRouter.ts +14 -3
- package/src/PluvServer.ts +98 -36
- package/src/PubSub.ts +4 -1
- package/src/authorize.ts +4 -1
- package/src/createIO.ts +10 -2
- package/src/types.ts +25 -11
- package/src/utils/pickBy.ts +4 -1
- package/src/version.generated.ts +1 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
|
-
> @pluv/io@1.0.
|
|
2
|
+
> @pluv/io@1.0.1 build /home/runner/work/pluv/pluv/packages/io
|
|
3
3
|
> pnpm run version && tsup src/index.ts --format esm,cjs --dts
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
> @pluv/io@1.0.
|
|
6
|
+
> @pluv/io@1.0.1 version /home/runner/work/pluv/pluv/packages/io
|
|
7
7
|
> genversion ./src/version.generated.ts --esm
|
|
8
8
|
|
|
9
9
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
[34mCLI[39m Target: es6
|
|
13
13
|
[34mESM[39m Build start
|
|
14
14
|
[34mCJS[39m Build start
|
|
15
|
-
[
|
|
15
|
+
[32mCJS[39m [1mdist/index.js [22m[32m53.34 KB[39m
|
|
16
|
+
[32mCJS[39m ⚡️ Build success in 119ms
|
|
17
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m51.16 KB[39m
|
|
16
18
|
[32mESM[39m ⚡️ Build success in 132ms
|
|
17
|
-
[32mCJS[39m [1mdist/index.js [22m[32m52.90 KB[39m
|
|
18
|
-
[32mCJS[39m ⚡️ Build success in 133ms
|
|
19
19
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
21
|
-
[32mDTS[39m [1mdist/index.d.mts [22m[32m32.
|
|
22
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m32.
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 3284ms
|
|
21
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m32.20 KB[39m
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m32.20 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @pluv/io
|
|
2
2
|
|
|
3
|
+
## 1.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c583be9: Fixed storage race condition that occurred when 2 users joined a room near simultaneously that would cause users to have mismatched storage states from the server (and thereby preventing updates from properly registering) until the user reconnected.
|
|
8
|
+
- @pluv/crdt@1.0.1
|
|
9
|
+
- @pluv/types@1.0.1
|
|
10
|
+
|
|
3
11
|
## 1.0.0
|
|
4
12
|
|
|
5
13
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -19,6 +19,11 @@
|
|
|
19
19
|
</a>
|
|
20
20
|
<img src="https://badgen.net/badge/-/TypeScript?icon=typescript&label&labelColor=blue&color=555555" alt="TypeScript" />
|
|
21
21
|
</p>
|
|
22
|
+
|
|
23
|
+
<div align="center">
|
|
24
|
+
<a href="https://www.producthunt.com/posts/pluv-io?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-pluv-io" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=961147&theme=light&t=1746462368894" alt="pluv.io - Open source multiplayer APIs, powered by TypeScript | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
22
27
|
<h4 align="center">💕 Inspired by <a href="https://trpc.io">trpc</a> 💕 <a href="https://docs.yjs.dev/">yjs</a> 💕 <br />and <a href="https://workers.cloudflare.com/">Cloudflare Workers</a> 💕</h4>
|
|
23
28
|
|
|
24
29
|
## Intro
|
package/dist/index.d.mts
CHANGED
|
@@ -445,7 +445,9 @@ type PluvServerConfig<TPlatform extends AbstractPlatform<any, any> = AbstractPla
|
|
|
445
445
|
type BaseCreateRoomOptions<TPlatform extends AbstractPlatform<any, any>, TAuthorize extends PluvIOAuthorize<TPlatform, any, InferInitContextType<TPlatform>> | null, TContext extends Record<string, any>, TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>> = Partial<IORoomListeners<TPlatform, TAuthorize, TContext, TEvents>> & {
|
|
446
446
|
debug?: boolean;
|
|
447
447
|
};
|
|
448
|
-
type CreateRoomOptions<TPlatform extends AbstractPlatform<any, any>, TAuthorize extends PluvIOAuthorize<TPlatform, any, InferInitContextType<TPlatform>> | null, TContext extends Record<string, any>, TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>> = keyof InferRoomContextType<TPlatform> extends never ? [BaseCreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents>] | [] : [
|
|
448
|
+
type CreateRoomOptions<TPlatform extends AbstractPlatform<any, any>, TAuthorize extends PluvIOAuthorize<TPlatform, any, InferInitContextType<TPlatform>> | null, TContext extends Record<string, any>, TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>> = keyof InferRoomContextType<TPlatform> extends never ? [BaseCreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents>] | [] : [
|
|
449
|
+
Id<BaseCreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents> & InferRoomContextType<TPlatform>>
|
|
450
|
+
];
|
|
449
451
|
declare class PluvServer<TPlatform extends AbstractPlatform<any, any> = AbstractPlatform<any, any>, TAuthorize extends PluvIOAuthorize<TPlatform, any, InferInitContextType<TPlatform>> | null = any, TContext extends Record<string, any> = {}, TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext> = {}> implements IOLike<TAuthorize, TEvents> {
|
|
450
452
|
readonly version: string;
|
|
451
453
|
private readonly _authorize;
|
package/dist/index.d.ts
CHANGED
|
@@ -445,7 +445,9 @@ type PluvServerConfig<TPlatform extends AbstractPlatform<any, any> = AbstractPla
|
|
|
445
445
|
type BaseCreateRoomOptions<TPlatform extends AbstractPlatform<any, any>, TAuthorize extends PluvIOAuthorize<TPlatform, any, InferInitContextType<TPlatform>> | null, TContext extends Record<string, any>, TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>> = Partial<IORoomListeners<TPlatform, TAuthorize, TContext, TEvents>> & {
|
|
446
446
|
debug?: boolean;
|
|
447
447
|
};
|
|
448
|
-
type CreateRoomOptions<TPlatform extends AbstractPlatform<any, any>, TAuthorize extends PluvIOAuthorize<TPlatform, any, InferInitContextType<TPlatform>> | null, TContext extends Record<string, any>, TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>> = keyof InferRoomContextType<TPlatform> extends never ? [BaseCreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents>] | [] : [
|
|
448
|
+
type CreateRoomOptions<TPlatform extends AbstractPlatform<any, any>, TAuthorize extends PluvIOAuthorize<TPlatform, any, InferInitContextType<TPlatform>> | null, TContext extends Record<string, any>, TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>> = keyof InferRoomContextType<TPlatform> extends never ? [BaseCreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents>] | [] : [
|
|
449
|
+
Id<BaseCreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents> & InferRoomContextType<TPlatform>>
|
|
450
|
+
];
|
|
449
451
|
declare class PluvServer<TPlatform extends AbstractPlatform<any, any> = AbstractPlatform<any, any>, TAuthorize extends PluvIOAuthorize<TPlatform, any, InferInitContextType<TPlatform>> | null = any, TContext extends Record<string, any> = {}, TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext> = {}> implements IOLike<TAuthorize, TEvents> {
|
|
450
452
|
readonly version: string;
|
|
451
453
|
private readonly _authorize;
|
package/dist/index.js
CHANGED
|
@@ -343,7 +343,9 @@ var PluvProcedure = class _PluvProcedure {
|
|
|
343
343
|
}));
|
|
344
344
|
}
|
|
345
345
|
input(input) {
|
|
346
|
-
return new _PluvProcedure({
|
|
346
|
+
return new _PluvProcedure({
|
|
347
|
+
input
|
|
348
|
+
});
|
|
347
349
|
}
|
|
348
350
|
self(resolver) {
|
|
349
351
|
const { self } = this.config;
|
|
@@ -380,7 +382,10 @@ var PluvRouter = class _PluvRouter {
|
|
|
380
382
|
this._defs = { events };
|
|
381
383
|
}
|
|
382
384
|
static merge(...routers) {
|
|
383
|
-
const events = Object.assign(
|
|
385
|
+
const events = Object.assign(
|
|
386
|
+
/* @__PURE__ */ Object.create(null),
|
|
387
|
+
...routers.map((router) => router._defs.events)
|
|
388
|
+
);
|
|
384
389
|
return new _PluvRouter(events);
|
|
385
390
|
}
|
|
386
391
|
_isValidEventName(name) {
|
|
@@ -583,7 +588,9 @@ var IORoom = class {
|
|
|
583
588
|
}));
|
|
584
589
|
this._addUserSession(user.id, pluvWs.sessionId);
|
|
585
590
|
}
|
|
586
|
-
this._logDebug(
|
|
591
|
+
this._logDebug(
|
|
592
|
+
`${import_kleur.default.blue(`Registering connection for room ${this.id}:`)} ${pluvWs.sessionId}`
|
|
593
|
+
);
|
|
587
594
|
yield this._platform.acceptWebSocket(pluvWs);
|
|
588
595
|
this._sessions.set(pluvWs.sessionId, pluvWs);
|
|
589
596
|
yield this._platform.persistence.addUser(this.id, pluvWs.sessionId, user != null ? user : {});
|
|
@@ -596,7 +603,9 @@ var IORoom = class {
|
|
|
596
603
|
}
|
|
597
604
|
yield this._emitRegistered(pluvWs);
|
|
598
605
|
const size = this.getSize();
|
|
599
|
-
this._logDebug(
|
|
606
|
+
this._logDebug(
|
|
607
|
+
`${import_kleur.default.blue(`Registered connection for room ${this.id}:`)} ${pluvWs.sessionId}`
|
|
608
|
+
);
|
|
600
609
|
this._logDebug(`${import_kleur.default.blue(`Room ${this.id} size:`)} ${size}`);
|
|
601
610
|
});
|
|
602
611
|
}
|
|
@@ -627,7 +636,9 @@ var IORoom = class {
|
|
|
627
636
|
const closeWebSocket = (webSocket) => __async(this, null, function* () {
|
|
628
637
|
webSocket.state = __spreadProps(__spreadValues({}, webSocket.state), { quit: true });
|
|
629
638
|
const sessionId = webSocket.sessionId;
|
|
630
|
-
this._logDebug(
|
|
639
|
+
this._logDebug(
|
|
640
|
+
`${import_kleur.default.blue(`Unregistering connection for room ${this.id}:`)} ${sessionId}`
|
|
641
|
+
);
|
|
631
642
|
this._sessions.delete(sessionId);
|
|
632
643
|
yield this._platform.persistence.deleteUser(this.id, sessionId).catch(() => null);
|
|
633
644
|
yield this._broadcast({
|
|
@@ -651,7 +662,9 @@ var IORoom = class {
|
|
|
651
662
|
} catch (error) {
|
|
652
663
|
console.error(error);
|
|
653
664
|
}
|
|
654
|
-
this._logDebug(
|
|
665
|
+
this._logDebug(
|
|
666
|
+
`${import_kleur.default.blue(`Unregistered connection for room ${this.id}:`)} ${webSocket.sessionId}`
|
|
667
|
+
);
|
|
655
668
|
});
|
|
656
669
|
const promises = webSockets.map((webSocket) => __async(this, null, function* () {
|
|
657
670
|
yield closeWebSocket(webSocket).catch(() => null);
|
|
@@ -668,9 +681,9 @@ var IORoom = class {
|
|
|
668
681
|
_emitQuitters() {
|
|
669
682
|
return __async(this, null, function* () {
|
|
670
683
|
const currentTime = (/* @__PURE__ */ new Date()).getTime();
|
|
671
|
-
const quitters = Array.from(this._sessions.values()).filter(
|
|
672
|
-
|
|
673
|
-
);
|
|
684
|
+
const quitters = Array.from(this._sessions.values()).filter((pluvWs) => {
|
|
685
|
+
return pluvWs.state.quit || currentTime - pluvWs.state.timers.ping > PING_TIMEOUT_MS;
|
|
686
|
+
});
|
|
674
687
|
this._closeWebSockets(quitters);
|
|
675
688
|
});
|
|
676
689
|
}
|
|
@@ -680,20 +693,21 @@ var IORoom = class {
|
|
|
680
693
|
const sessionId = session.id;
|
|
681
694
|
const presence = session.presence;
|
|
682
695
|
const user = session.user;
|
|
696
|
+
const doc = yield this._doc;
|
|
697
|
+
const encodedState = doc.getEncodedState();
|
|
683
698
|
yield this._sendSelfMessage(
|
|
684
699
|
{
|
|
685
700
|
type: "$registered",
|
|
686
701
|
data: {
|
|
687
702
|
presence,
|
|
688
703
|
sessionId,
|
|
704
|
+
state: encodedState,
|
|
689
705
|
timers: { presence: session.timers.presence }
|
|
690
706
|
}
|
|
691
707
|
},
|
|
692
708
|
{ sessionId, user }
|
|
693
709
|
);
|
|
694
710
|
try {
|
|
695
|
-
const doc = yield this._doc;
|
|
696
|
-
const encodedState = doc.getEncodedState();
|
|
697
711
|
yield Promise.resolve(
|
|
698
712
|
this._listeners.onUserConnected({
|
|
699
713
|
context: this._context,
|
|
@@ -740,7 +754,8 @@ var IORoom = class {
|
|
|
740
754
|
const ioAuthorize = this._getIOAuthorize(options);
|
|
741
755
|
if (!ioAuthorize) return null;
|
|
742
756
|
if (!token) return null;
|
|
743
|
-
if (!ioAuthorize.secret)
|
|
757
|
+
if (!ioAuthorize.secret)
|
|
758
|
+
throw new Error("`authorize` was specified without a valid secret");
|
|
744
759
|
const payload = yield authorize({
|
|
745
760
|
platform: this._platform,
|
|
746
761
|
secret: ioAuthorize.secret
|
|
@@ -1141,7 +1156,7 @@ var pickBy = (obj, by) => {
|
|
|
1141
1156
|
};
|
|
1142
1157
|
|
|
1143
1158
|
// src/version.generated.ts
|
|
1144
|
-
var version = "1.0.
|
|
1159
|
+
var version = "1.0.1";
|
|
1145
1160
|
|
|
1146
1161
|
// src/PluvServer.ts
|
|
1147
1162
|
var PluvServer = class {
|
|
@@ -1184,16 +1199,28 @@ var PluvServer = class {
|
|
|
1184
1199
|
}
|
|
1185
1200
|
};
|
|
1186
1201
|
}).self((_0, _1) => __async(this, [_0, _1], function* (data, { context, doc, room, session }) {
|
|
1187
|
-
var _a
|
|
1202
|
+
var _a;
|
|
1188
1203
|
const oldState = yield this._platform.persistence.getStorageState(room);
|
|
1189
1204
|
const update = data == null ? void 0 : data.update;
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1205
|
+
if (!oldState) {
|
|
1206
|
+
const loadedState = yield (_a = this._getInitialStorage) == null ? void 0 : _a.call(this, { context, room });
|
|
1207
|
+
if (!!loadedState) {
|
|
1208
|
+
doc.applyEncodedState({ update: loadedState }).getEncodedState();
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
if (!doc.isEmpty()) {
|
|
1212
|
+
const encodedState2 = doc.getEncodedState();
|
|
1213
|
+
return { $storageReceived: { state: encodedState2 } };
|
|
1214
|
+
}
|
|
1215
|
+
if (!!update) {
|
|
1216
|
+
const encodedState2 = doc.applyEncodedState({ update }).getEncodedState();
|
|
1217
|
+
const storageSize = new TextEncoder().encode(encodedState2).length;
|
|
1218
|
+
if (!!this._limits.storageMaxSize && storageSize > this._limits.storageMaxSize) {
|
|
1219
|
+
throw new Error("Storage has exceeded the size limit");
|
|
1220
|
+
}
|
|
1221
|
+
yield this._platform.persistence.setStorageState(room, encodedState2).catch((error) => {
|
|
1222
|
+
this._logDebug(error);
|
|
1223
|
+
});
|
|
1197
1224
|
this._getListeners().onStorageUpdated({
|
|
1198
1225
|
context,
|
|
1199
1226
|
encodedState: encodedState2,
|
|
@@ -1202,11 +1229,7 @@ var PluvServer = class {
|
|
|
1202
1229
|
webSocket: session == null ? void 0 : session.webSocket.webSocket
|
|
1203
1230
|
});
|
|
1204
1231
|
}
|
|
1205
|
-
const encodedState =
|
|
1206
|
-
const storageSize = new TextEncoder().encode(encodedState).length;
|
|
1207
|
-
if (!!this._limits.storageMaxSize && storageSize > this._limits.storageMaxSize) {
|
|
1208
|
-
throw new Error("Storage has exceeded the size limit");
|
|
1209
|
-
}
|
|
1232
|
+
const encodedState = doc.getEncodedState();
|
|
1210
1233
|
return { $storageReceived: { state: encodedState } };
|
|
1211
1234
|
})),
|
|
1212
1235
|
$ping: this._procedure.self((data, { session }) => {
|
|
@@ -1274,7 +1297,13 @@ var PluvServer = class {
|
|
|
1274
1297
|
platform,
|
|
1275
1298
|
router = new PluvRouter({})
|
|
1276
1299
|
} = options;
|
|
1277
|
-
const {
|
|
1300
|
+
const {
|
|
1301
|
+
onRoomDeleted,
|
|
1302
|
+
onRoomMessage,
|
|
1303
|
+
onStorageUpdated,
|
|
1304
|
+
onUserConnected,
|
|
1305
|
+
onUserDisconnected
|
|
1306
|
+
} = options;
|
|
1278
1307
|
platform.validateConfig(options);
|
|
1279
1308
|
this._crdt = crdt;
|
|
1280
1309
|
this._debug = debug;
|
|
@@ -1295,7 +1324,8 @@ var PluvServer = class {
|
|
|
1295
1324
|
}
|
|
1296
1325
|
get fetch() {
|
|
1297
1326
|
return (...args) => {
|
|
1298
|
-
if (!this._platform._fetch)
|
|
1327
|
+
if (!this._platform._fetch)
|
|
1328
|
+
throw new Error(`\`${this._platform._name}\` does not support \`fetch\``);
|
|
1299
1329
|
return this._platform._fetch(...args);
|
|
1300
1330
|
};
|
|
1301
1331
|
}
|
|
@@ -1321,7 +1351,8 @@ var PluvServer = class {
|
|
|
1321
1351
|
if (this._platform._config.handleMode !== "io") {
|
|
1322
1352
|
throw new Error(`\`createRoom\` is unsupported for \`${this._platform._name}\``);
|
|
1323
1353
|
}
|
|
1324
|
-
if (!/^[a-z0-9](?:[a-z0-9-_]*[a-z0-9])?$/i.test(room))
|
|
1354
|
+
if (!/^[a-z0-9](?:[a-z0-9-_]*[a-z0-9])?$/i.test(room))
|
|
1355
|
+
throw new Error("Unsupported room name");
|
|
1325
1356
|
const roomContext = platformRoomContext;
|
|
1326
1357
|
const context = typeof this._context === "function" ? this._context(roomContext) : this._context;
|
|
1327
1358
|
const newRoom = new IORoom(room, __spreadProps(__spreadValues({}, !!_meta ? { _meta } : {}), {
|
|
@@ -1391,7 +1422,8 @@ var PluvIO = class {
|
|
|
1391
1422
|
createToken(params) {
|
|
1392
1423
|
return __async(this, null, function* () {
|
|
1393
1424
|
var _a;
|
|
1394
|
-
if (!this._authorize)
|
|
1425
|
+
if (!this._authorize)
|
|
1426
|
+
throw new Error("IO does not specify authorize during initialization.");
|
|
1395
1427
|
const ioAuthorize = this._getIOAuthorize(params);
|
|
1396
1428
|
const user = params.user;
|
|
1397
1429
|
const parsed = !!ioAuthorize ? ioAuthorize.user.parse(user) : user;
|
|
@@ -1409,7 +1441,9 @@ var PluvIO = class {
|
|
|
1409
1441
|
if (this._platform._createToken) return yield this._platform._createToken(params);
|
|
1410
1442
|
const secret = (_a = ioAuthorize == null ? void 0 : ioAuthorize.secret) != null ? _a : null;
|
|
1411
1443
|
if (!secret) throw new Error("`authorize` was specified without a valid secret");
|
|
1412
|
-
return yield authorize({ platform: this._platform, secret }).encode(
|
|
1444
|
+
return yield authorize({ platform: this._platform, secret }).encode(
|
|
1445
|
+
params
|
|
1446
|
+
);
|
|
1413
1447
|
});
|
|
1414
1448
|
}
|
|
1415
1449
|
mergeRouters(...routers) {
|
package/dist/index.mjs
CHANGED
|
@@ -301,7 +301,9 @@ var PluvProcedure = class _PluvProcedure {
|
|
|
301
301
|
}));
|
|
302
302
|
}
|
|
303
303
|
input(input) {
|
|
304
|
-
return new _PluvProcedure({
|
|
304
|
+
return new _PluvProcedure({
|
|
305
|
+
input
|
|
306
|
+
});
|
|
305
307
|
}
|
|
306
308
|
self(resolver) {
|
|
307
309
|
const { self } = this.config;
|
|
@@ -338,7 +340,10 @@ var PluvRouter = class _PluvRouter {
|
|
|
338
340
|
this._defs = { events };
|
|
339
341
|
}
|
|
340
342
|
static merge(...routers) {
|
|
341
|
-
const events = Object.assign(
|
|
343
|
+
const events = Object.assign(
|
|
344
|
+
/* @__PURE__ */ Object.create(null),
|
|
345
|
+
...routers.map((router) => router._defs.events)
|
|
346
|
+
);
|
|
342
347
|
return new _PluvRouter(events);
|
|
343
348
|
}
|
|
344
349
|
_isValidEventName(name) {
|
|
@@ -541,7 +546,9 @@ var IORoom = class {
|
|
|
541
546
|
}));
|
|
542
547
|
this._addUserSession(user.id, pluvWs.sessionId);
|
|
543
548
|
}
|
|
544
|
-
this._logDebug(
|
|
549
|
+
this._logDebug(
|
|
550
|
+
`${colors.blue(`Registering connection for room ${this.id}:`)} ${pluvWs.sessionId}`
|
|
551
|
+
);
|
|
545
552
|
yield this._platform.acceptWebSocket(pluvWs);
|
|
546
553
|
this._sessions.set(pluvWs.sessionId, pluvWs);
|
|
547
554
|
yield this._platform.persistence.addUser(this.id, pluvWs.sessionId, user != null ? user : {});
|
|
@@ -554,7 +561,9 @@ var IORoom = class {
|
|
|
554
561
|
}
|
|
555
562
|
yield this._emitRegistered(pluvWs);
|
|
556
563
|
const size = this.getSize();
|
|
557
|
-
this._logDebug(
|
|
564
|
+
this._logDebug(
|
|
565
|
+
`${colors.blue(`Registered connection for room ${this.id}:`)} ${pluvWs.sessionId}`
|
|
566
|
+
);
|
|
558
567
|
this._logDebug(`${colors.blue(`Room ${this.id} size:`)} ${size}`);
|
|
559
568
|
});
|
|
560
569
|
}
|
|
@@ -585,7 +594,9 @@ var IORoom = class {
|
|
|
585
594
|
const closeWebSocket = (webSocket) => __async(this, null, function* () {
|
|
586
595
|
webSocket.state = __spreadProps(__spreadValues({}, webSocket.state), { quit: true });
|
|
587
596
|
const sessionId = webSocket.sessionId;
|
|
588
|
-
this._logDebug(
|
|
597
|
+
this._logDebug(
|
|
598
|
+
`${colors.blue(`Unregistering connection for room ${this.id}:`)} ${sessionId}`
|
|
599
|
+
);
|
|
589
600
|
this._sessions.delete(sessionId);
|
|
590
601
|
yield this._platform.persistence.deleteUser(this.id, sessionId).catch(() => null);
|
|
591
602
|
yield this._broadcast({
|
|
@@ -609,7 +620,9 @@ var IORoom = class {
|
|
|
609
620
|
} catch (error) {
|
|
610
621
|
console.error(error);
|
|
611
622
|
}
|
|
612
|
-
this._logDebug(
|
|
623
|
+
this._logDebug(
|
|
624
|
+
`${colors.blue(`Unregistered connection for room ${this.id}:`)} ${webSocket.sessionId}`
|
|
625
|
+
);
|
|
613
626
|
});
|
|
614
627
|
const promises = webSockets.map((webSocket) => __async(this, null, function* () {
|
|
615
628
|
yield closeWebSocket(webSocket).catch(() => null);
|
|
@@ -626,9 +639,9 @@ var IORoom = class {
|
|
|
626
639
|
_emitQuitters() {
|
|
627
640
|
return __async(this, null, function* () {
|
|
628
641
|
const currentTime = (/* @__PURE__ */ new Date()).getTime();
|
|
629
|
-
const quitters = Array.from(this._sessions.values()).filter(
|
|
630
|
-
|
|
631
|
-
);
|
|
642
|
+
const quitters = Array.from(this._sessions.values()).filter((pluvWs) => {
|
|
643
|
+
return pluvWs.state.quit || currentTime - pluvWs.state.timers.ping > PING_TIMEOUT_MS;
|
|
644
|
+
});
|
|
632
645
|
this._closeWebSockets(quitters);
|
|
633
646
|
});
|
|
634
647
|
}
|
|
@@ -638,20 +651,21 @@ var IORoom = class {
|
|
|
638
651
|
const sessionId = session.id;
|
|
639
652
|
const presence = session.presence;
|
|
640
653
|
const user = session.user;
|
|
654
|
+
const doc = yield this._doc;
|
|
655
|
+
const encodedState = doc.getEncodedState();
|
|
641
656
|
yield this._sendSelfMessage(
|
|
642
657
|
{
|
|
643
658
|
type: "$registered",
|
|
644
659
|
data: {
|
|
645
660
|
presence,
|
|
646
661
|
sessionId,
|
|
662
|
+
state: encodedState,
|
|
647
663
|
timers: { presence: session.timers.presence }
|
|
648
664
|
}
|
|
649
665
|
},
|
|
650
666
|
{ sessionId, user }
|
|
651
667
|
);
|
|
652
668
|
try {
|
|
653
|
-
const doc = yield this._doc;
|
|
654
|
-
const encodedState = doc.getEncodedState();
|
|
655
669
|
yield Promise.resolve(
|
|
656
670
|
this._listeners.onUserConnected({
|
|
657
671
|
context: this._context,
|
|
@@ -698,7 +712,8 @@ var IORoom = class {
|
|
|
698
712
|
const ioAuthorize = this._getIOAuthorize(options);
|
|
699
713
|
if (!ioAuthorize) return null;
|
|
700
714
|
if (!token) return null;
|
|
701
|
-
if (!ioAuthorize.secret)
|
|
715
|
+
if (!ioAuthorize.secret)
|
|
716
|
+
throw new Error("`authorize` was specified without a valid secret");
|
|
702
717
|
const payload = yield authorize({
|
|
703
718
|
platform: this._platform,
|
|
704
719
|
secret: ioAuthorize.secret
|
|
@@ -1099,7 +1114,7 @@ var pickBy = (obj, by) => {
|
|
|
1099
1114
|
};
|
|
1100
1115
|
|
|
1101
1116
|
// src/version.generated.ts
|
|
1102
|
-
var version = "1.0.
|
|
1117
|
+
var version = "1.0.1";
|
|
1103
1118
|
|
|
1104
1119
|
// src/PluvServer.ts
|
|
1105
1120
|
var PluvServer = class {
|
|
@@ -1142,16 +1157,28 @@ var PluvServer = class {
|
|
|
1142
1157
|
}
|
|
1143
1158
|
};
|
|
1144
1159
|
}).self((_0, _1) => __async(this, [_0, _1], function* (data, { context, doc, room, session }) {
|
|
1145
|
-
var _a
|
|
1160
|
+
var _a;
|
|
1146
1161
|
const oldState = yield this._platform.persistence.getStorageState(room);
|
|
1147
1162
|
const update = data == null ? void 0 : data.update;
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1163
|
+
if (!oldState) {
|
|
1164
|
+
const loadedState = yield (_a = this._getInitialStorage) == null ? void 0 : _a.call(this, { context, room });
|
|
1165
|
+
if (!!loadedState) {
|
|
1166
|
+
doc.applyEncodedState({ update: loadedState }).getEncodedState();
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
if (!doc.isEmpty()) {
|
|
1170
|
+
const encodedState2 = doc.getEncodedState();
|
|
1171
|
+
return { $storageReceived: { state: encodedState2 } };
|
|
1172
|
+
}
|
|
1173
|
+
if (!!update) {
|
|
1174
|
+
const encodedState2 = doc.applyEncodedState({ update }).getEncodedState();
|
|
1175
|
+
const storageSize = new TextEncoder().encode(encodedState2).length;
|
|
1176
|
+
if (!!this._limits.storageMaxSize && storageSize > this._limits.storageMaxSize) {
|
|
1177
|
+
throw new Error("Storage has exceeded the size limit");
|
|
1178
|
+
}
|
|
1179
|
+
yield this._platform.persistence.setStorageState(room, encodedState2).catch((error) => {
|
|
1180
|
+
this._logDebug(error);
|
|
1181
|
+
});
|
|
1155
1182
|
this._getListeners().onStorageUpdated({
|
|
1156
1183
|
context,
|
|
1157
1184
|
encodedState: encodedState2,
|
|
@@ -1160,11 +1187,7 @@ var PluvServer = class {
|
|
|
1160
1187
|
webSocket: session == null ? void 0 : session.webSocket.webSocket
|
|
1161
1188
|
});
|
|
1162
1189
|
}
|
|
1163
|
-
const encodedState =
|
|
1164
|
-
const storageSize = new TextEncoder().encode(encodedState).length;
|
|
1165
|
-
if (!!this._limits.storageMaxSize && storageSize > this._limits.storageMaxSize) {
|
|
1166
|
-
throw new Error("Storage has exceeded the size limit");
|
|
1167
|
-
}
|
|
1190
|
+
const encodedState = doc.getEncodedState();
|
|
1168
1191
|
return { $storageReceived: { state: encodedState } };
|
|
1169
1192
|
})),
|
|
1170
1193
|
$ping: this._procedure.self((data, { session }) => {
|
|
@@ -1232,7 +1255,13 @@ var PluvServer = class {
|
|
|
1232
1255
|
platform,
|
|
1233
1256
|
router = new PluvRouter({})
|
|
1234
1257
|
} = options;
|
|
1235
|
-
const {
|
|
1258
|
+
const {
|
|
1259
|
+
onRoomDeleted,
|
|
1260
|
+
onRoomMessage,
|
|
1261
|
+
onStorageUpdated,
|
|
1262
|
+
onUserConnected,
|
|
1263
|
+
onUserDisconnected
|
|
1264
|
+
} = options;
|
|
1236
1265
|
platform.validateConfig(options);
|
|
1237
1266
|
this._crdt = crdt;
|
|
1238
1267
|
this._debug = debug;
|
|
@@ -1253,7 +1282,8 @@ var PluvServer = class {
|
|
|
1253
1282
|
}
|
|
1254
1283
|
get fetch() {
|
|
1255
1284
|
return (...args) => {
|
|
1256
|
-
if (!this._platform._fetch)
|
|
1285
|
+
if (!this._platform._fetch)
|
|
1286
|
+
throw new Error(`\`${this._platform._name}\` does not support \`fetch\``);
|
|
1257
1287
|
return this._platform._fetch(...args);
|
|
1258
1288
|
};
|
|
1259
1289
|
}
|
|
@@ -1279,7 +1309,8 @@ var PluvServer = class {
|
|
|
1279
1309
|
if (this._platform._config.handleMode !== "io") {
|
|
1280
1310
|
throw new Error(`\`createRoom\` is unsupported for \`${this._platform._name}\``);
|
|
1281
1311
|
}
|
|
1282
|
-
if (!/^[a-z0-9](?:[a-z0-9-_]*[a-z0-9])?$/i.test(room))
|
|
1312
|
+
if (!/^[a-z0-9](?:[a-z0-9-_]*[a-z0-9])?$/i.test(room))
|
|
1313
|
+
throw new Error("Unsupported room name");
|
|
1283
1314
|
const roomContext = platformRoomContext;
|
|
1284
1315
|
const context = typeof this._context === "function" ? this._context(roomContext) : this._context;
|
|
1285
1316
|
const newRoom = new IORoom(room, __spreadProps(__spreadValues({}, !!_meta ? { _meta } : {}), {
|
|
@@ -1349,7 +1380,8 @@ var PluvIO = class {
|
|
|
1349
1380
|
createToken(params) {
|
|
1350
1381
|
return __async(this, null, function* () {
|
|
1351
1382
|
var _a;
|
|
1352
|
-
if (!this._authorize)
|
|
1383
|
+
if (!this._authorize)
|
|
1384
|
+
throw new Error("IO does not specify authorize during initialization.");
|
|
1353
1385
|
const ioAuthorize = this._getIOAuthorize(params);
|
|
1354
1386
|
const user = params.user;
|
|
1355
1387
|
const parsed = !!ioAuthorize ? ioAuthorize.user.parse(user) : user;
|
|
@@ -1367,7 +1399,9 @@ var PluvIO = class {
|
|
|
1367
1399
|
if (this._platform._createToken) return yield this._platform._createToken(params);
|
|
1368
1400
|
const secret = (_a = ioAuthorize == null ? void 0 : ioAuthorize.secret) != null ? _a : null;
|
|
1369
1401
|
if (!secret) throw new Error("`authorize` was specified without a valid secret");
|
|
1370
|
-
return yield authorize({ platform: this._platform, secret }).encode(
|
|
1402
|
+
return yield authorize({ platform: this._platform, secret }).encode(
|
|
1403
|
+
params
|
|
1404
|
+
);
|
|
1371
1405
|
});
|
|
1372
1406
|
}
|
|
1373
1407
|
mergeRouters(...routers) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pluv/io",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "@pluv/io server-side websocket manager",
|
|
5
5
|
"author": "leedavidcs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,16 +21,16 @@
|
|
|
21
21
|
"jose": "^5.10.0",
|
|
22
22
|
"kleur": "^4.1.5",
|
|
23
23
|
"wonka": "^6.3.5",
|
|
24
|
-
"@pluv/crdt": "^1.0.
|
|
25
|
-
"@pluv/types": "^1.0.
|
|
24
|
+
"@pluv/crdt": "^1.0.1",
|
|
25
|
+
"@pluv/types": "^1.0.1"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"eslint": "^9.25.1",
|
|
29
29
|
"genversion": "^3.2.0",
|
|
30
30
|
"tsup": "^8.4.0",
|
|
31
31
|
"typescript": "^5.8.3",
|
|
32
|
-
"@pluv/tsconfig": "^1.0.
|
|
33
|
-
"eslint-config-pluv": "^1.0.
|
|
32
|
+
"@pluv/tsconfig": "^1.0.1",
|
|
33
|
+
"eslint-config-pluv": "^1.0.1"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
36
|
"build": "pnpm run version && tsup src/index.ts --format esm,cjs --dts",
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { JsonObject } from "@pluv/types";
|
|
2
2
|
|
|
3
3
|
export abstract class AbstractPersistence {
|
|
4
|
-
public abstract addUser(
|
|
4
|
+
public abstract addUser(
|
|
5
|
+
room: string,
|
|
6
|
+
connectionId: string,
|
|
7
|
+
user: JsonObject | null,
|
|
8
|
+
): Promise<void>;
|
|
5
9
|
|
|
6
10
|
public abstract deleteStorageState(room: string): Promise<void>;
|
|
7
11
|
|
|
@@ -13,7 +17,9 @@ export abstract class AbstractPersistence {
|
|
|
13
17
|
|
|
14
18
|
public abstract getUser(room: string, connectionId: string): Promise<JsonObject | null>;
|
|
15
19
|
|
|
16
|
-
public abstract getUsers(
|
|
20
|
+
public abstract getUsers(
|
|
21
|
+
room: string,
|
|
22
|
+
): Promise<Map<[connectionId: string][0], JsonObject | null>>;
|
|
17
23
|
|
|
18
24
|
public abstract getUsersSize(room: string): Promise<number>;
|
|
19
25
|
|
package/src/AbstractPlatform.ts
CHANGED
|
@@ -10,7 +10,9 @@ export type InferPlatformWebSocketType<TPlatform extends AbstractPlatform> =
|
|
|
10
10
|
TPlatform extends AbstractPlatform<infer IAbstractWebSocket> ? IAbstractWebSocket : never;
|
|
11
11
|
|
|
12
12
|
export type InferPlatformWebSocketSource<TPlatform> =
|
|
13
|
-
TPlatform extends AbstractPlatform<infer IAbstractWebSocket>
|
|
13
|
+
TPlatform extends AbstractPlatform<infer IAbstractWebSocket>
|
|
14
|
+
? InferWebSocketSource<IAbstractWebSocket>
|
|
15
|
+
: never;
|
|
14
16
|
|
|
15
17
|
export type InferInitContextType<TPlatform extends AbstractPlatform> =
|
|
16
18
|
TPlatform extends AbstractPlatform<any, infer IInitContext> ? IInitContext : never;
|
|
@@ -66,7 +68,9 @@ export abstract class AbstractPlatform<
|
|
|
66
68
|
|
|
67
69
|
public abstract getLastPing(webSocket: AbstractWebSocket<any, any>): number | null;
|
|
68
70
|
|
|
69
|
-
public abstract getSerializedState(
|
|
71
|
+
public abstract getSerializedState(
|
|
72
|
+
webSocket: InferWebSocketSource<TWebSocket>,
|
|
73
|
+
): WebSocketSerializedState | null;
|
|
70
74
|
|
|
71
75
|
public abstract getSessionId(webSocket: InferWebSocketSource<TWebSocket>): string | null;
|
|
72
76
|
|
package/src/AbstractWebSocket.ts
CHANGED
|
@@ -54,7 +54,10 @@ export interface AbstractWebSocketConfig {
|
|
|
54
54
|
room: string;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
export abstract class AbstractWebSocket<
|
|
57
|
+
export abstract class AbstractWebSocket<
|
|
58
|
+
TWebSocket = any,
|
|
59
|
+
TAuthorize extends IOAuthorize<any, any> | null = null,
|
|
60
|
+
> {
|
|
58
61
|
/** The connection is not yet open. */
|
|
59
62
|
public readonly CONNECTING = 0;
|
|
60
63
|
/** The connection is open and ready to communicate. */
|
|
@@ -96,7 +99,10 @@ export abstract class AbstractWebSocket<TWebSocket = any, TAuthorize extends IOA
|
|
|
96
99
|
* result.
|
|
97
100
|
* @date August 28, 2022
|
|
98
101
|
*/
|
|
99
|
-
public abstract addEventListener<TType extends EventType>(
|
|
102
|
+
public abstract addEventListener<TType extends EventType>(
|
|
103
|
+
type: TType,
|
|
104
|
+
handler: AbstractListener<TType>,
|
|
105
|
+
): void;
|
|
100
106
|
|
|
101
107
|
public abstract close(code?: number | undefined, reason?: string | undefined): void;
|
|
102
108
|
|
|
@@ -120,7 +126,9 @@ export abstract class AbstractWebSocket<TWebSocket = any, TAuthorize extends IOA
|
|
|
120
126
|
});
|
|
121
127
|
}
|
|
122
128
|
|
|
123
|
-
public sendMessage<TMessage extends IOEventMessage<any> = IOEventMessage<any>>(
|
|
129
|
+
public sendMessage<TMessage extends IOEventMessage<any> = IOEventMessage<any>>(
|
|
130
|
+
data: TMessage,
|
|
131
|
+
): void {
|
|
124
132
|
return this.send(JSON.stringify(data));
|
|
125
133
|
}
|
|
126
134
|
}
|