@pluv/io 1.0.0 → 1.0.2
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/README.md +5 -0
- package/dist/index.d.mts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +67 -32
- package/dist/index.mjs +67 -32
- 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 +101 -37
- 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.2 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.2 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
|
-
[32mESM[39m [1mdist/index.mjs [22m[
|
|
16
|
-
[32mESM[39m ⚡️ Build success in
|
|
17
|
-
[32mCJS[39m [1mdist/index.js [22m[
|
|
18
|
-
[32mCJS[39m ⚡️ Build success in
|
|
15
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m51.32 KB[39m
|
|
16
|
+
[32mESM[39m ⚡️ Build success in 143ms
|
|
17
|
+
[32mCJS[39m [1mdist/index.js [22m[32m53.49 KB[39m
|
|
18
|
+
[32mCJS[39m ⚡️ Build success in 146ms
|
|
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 3112ms
|
|
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,20 @@
|
|
|
1
1
|
# @pluv/io
|
|
2
2
|
|
|
3
|
+
## 1.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- @pluv/crdt@1.0.2
|
|
8
|
+
- @pluv/types@1.0.2
|
|
9
|
+
|
|
10
|
+
## 1.0.1
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- 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.
|
|
15
|
+
- @pluv/crdt@1.0.1
|
|
16
|
+
- @pluv/types@1.0.1
|
|
17
|
+
|
|
3
18
|
## 1.0.0
|
|
4
19
|
|
|
5
20
|
### 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.isEmpty() ? null : 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.2";
|
|
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: { changeKind: "unchanged", 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,
|
|
@@ -1201,13 +1228,10 @@ var PluvServer = class {
|
|
|
1201
1228
|
user: session == null ? void 0 : session.user,
|
|
1202
1229
|
webSocket: session == null ? void 0 : session.webSocket.webSocket
|
|
1203
1230
|
});
|
|
1231
|
+
return { $storageReceived: { changeKind: "initialized", state: encodedState2 } };
|
|
1204
1232
|
}
|
|
1205
|
-
const encodedState =
|
|
1206
|
-
|
|
1207
|
-
if (!!this._limits.storageMaxSize && storageSize > this._limits.storageMaxSize) {
|
|
1208
|
-
throw new Error("Storage has exceeded the size limit");
|
|
1209
|
-
}
|
|
1210
|
-
return { $storageReceived: { state: encodedState } };
|
|
1233
|
+
const encodedState = doc.getEncodedState();
|
|
1234
|
+
return { $storageReceived: { changeKind: "empty", state: encodedState } };
|
|
1211
1235
|
})),
|
|
1212
1236
|
$ping: this._procedure.self((data, { session }) => {
|
|
1213
1237
|
if (!session) return {};
|
|
@@ -1274,7 +1298,13 @@ var PluvServer = class {
|
|
|
1274
1298
|
platform,
|
|
1275
1299
|
router = new PluvRouter({})
|
|
1276
1300
|
} = options;
|
|
1277
|
-
const {
|
|
1301
|
+
const {
|
|
1302
|
+
onRoomDeleted,
|
|
1303
|
+
onRoomMessage,
|
|
1304
|
+
onStorageUpdated,
|
|
1305
|
+
onUserConnected,
|
|
1306
|
+
onUserDisconnected
|
|
1307
|
+
} = options;
|
|
1278
1308
|
platform.validateConfig(options);
|
|
1279
1309
|
this._crdt = crdt;
|
|
1280
1310
|
this._debug = debug;
|
|
@@ -1295,7 +1325,8 @@ var PluvServer = class {
|
|
|
1295
1325
|
}
|
|
1296
1326
|
get fetch() {
|
|
1297
1327
|
return (...args) => {
|
|
1298
|
-
if (!this._platform._fetch)
|
|
1328
|
+
if (!this._platform._fetch)
|
|
1329
|
+
throw new Error(`\`${this._platform._name}\` does not support \`fetch\``);
|
|
1299
1330
|
return this._platform._fetch(...args);
|
|
1300
1331
|
};
|
|
1301
1332
|
}
|
|
@@ -1321,7 +1352,8 @@ var PluvServer = class {
|
|
|
1321
1352
|
if (this._platform._config.handleMode !== "io") {
|
|
1322
1353
|
throw new Error(`\`createRoom\` is unsupported for \`${this._platform._name}\``);
|
|
1323
1354
|
}
|
|
1324
|
-
if (!/^[a-z0-9](?:[a-z0-9-_]*[a-z0-9])?$/i.test(room))
|
|
1355
|
+
if (!/^[a-z0-9](?:[a-z0-9-_]*[a-z0-9])?$/i.test(room))
|
|
1356
|
+
throw new Error("Unsupported room name");
|
|
1325
1357
|
const roomContext = platformRoomContext;
|
|
1326
1358
|
const context = typeof this._context === "function" ? this._context(roomContext) : this._context;
|
|
1327
1359
|
const newRoom = new IORoom(room, __spreadProps(__spreadValues({}, !!_meta ? { _meta } : {}), {
|
|
@@ -1391,7 +1423,8 @@ var PluvIO = class {
|
|
|
1391
1423
|
createToken(params) {
|
|
1392
1424
|
return __async(this, null, function* () {
|
|
1393
1425
|
var _a;
|
|
1394
|
-
if (!this._authorize)
|
|
1426
|
+
if (!this._authorize)
|
|
1427
|
+
throw new Error("IO does not specify authorize during initialization.");
|
|
1395
1428
|
const ioAuthorize = this._getIOAuthorize(params);
|
|
1396
1429
|
const user = params.user;
|
|
1397
1430
|
const parsed = !!ioAuthorize ? ioAuthorize.user.parse(user) : user;
|
|
@@ -1409,7 +1442,9 @@ var PluvIO = class {
|
|
|
1409
1442
|
if (this._platform._createToken) return yield this._platform._createToken(params);
|
|
1410
1443
|
const secret = (_a = ioAuthorize == null ? void 0 : ioAuthorize.secret) != null ? _a : null;
|
|
1411
1444
|
if (!secret) throw new Error("`authorize` was specified without a valid secret");
|
|
1412
|
-
return yield authorize({ platform: this._platform, secret }).encode(
|
|
1445
|
+
return yield authorize({ platform: this._platform, secret }).encode(
|
|
1446
|
+
params
|
|
1447
|
+
);
|
|
1413
1448
|
});
|
|
1414
1449
|
}
|
|
1415
1450
|
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.isEmpty() ? null : 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.2";
|
|
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: { changeKind: "unchanged", 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,
|
|
@@ -1159,13 +1186,10 @@ var PluvServer = class {
|
|
|
1159
1186
|
user: session == null ? void 0 : session.user,
|
|
1160
1187
|
webSocket: session == null ? void 0 : session.webSocket.webSocket
|
|
1161
1188
|
});
|
|
1189
|
+
return { $storageReceived: { changeKind: "initialized", state: encodedState2 } };
|
|
1162
1190
|
}
|
|
1163
|
-
const encodedState =
|
|
1164
|
-
|
|
1165
|
-
if (!!this._limits.storageMaxSize && storageSize > this._limits.storageMaxSize) {
|
|
1166
|
-
throw new Error("Storage has exceeded the size limit");
|
|
1167
|
-
}
|
|
1168
|
-
return { $storageReceived: { state: encodedState } };
|
|
1191
|
+
const encodedState = doc.getEncodedState();
|
|
1192
|
+
return { $storageReceived: { changeKind: "empty", state: encodedState } };
|
|
1169
1193
|
})),
|
|
1170
1194
|
$ping: this._procedure.self((data, { session }) => {
|
|
1171
1195
|
if (!session) return {};
|
|
@@ -1232,7 +1256,13 @@ var PluvServer = class {
|
|
|
1232
1256
|
platform,
|
|
1233
1257
|
router = new PluvRouter({})
|
|
1234
1258
|
} = options;
|
|
1235
|
-
const {
|
|
1259
|
+
const {
|
|
1260
|
+
onRoomDeleted,
|
|
1261
|
+
onRoomMessage,
|
|
1262
|
+
onStorageUpdated,
|
|
1263
|
+
onUserConnected,
|
|
1264
|
+
onUserDisconnected
|
|
1265
|
+
} = options;
|
|
1236
1266
|
platform.validateConfig(options);
|
|
1237
1267
|
this._crdt = crdt;
|
|
1238
1268
|
this._debug = debug;
|
|
@@ -1253,7 +1283,8 @@ var PluvServer = class {
|
|
|
1253
1283
|
}
|
|
1254
1284
|
get fetch() {
|
|
1255
1285
|
return (...args) => {
|
|
1256
|
-
if (!this._platform._fetch)
|
|
1286
|
+
if (!this._platform._fetch)
|
|
1287
|
+
throw new Error(`\`${this._platform._name}\` does not support \`fetch\``);
|
|
1257
1288
|
return this._platform._fetch(...args);
|
|
1258
1289
|
};
|
|
1259
1290
|
}
|
|
@@ -1279,7 +1310,8 @@ var PluvServer = class {
|
|
|
1279
1310
|
if (this._platform._config.handleMode !== "io") {
|
|
1280
1311
|
throw new Error(`\`createRoom\` is unsupported for \`${this._platform._name}\``);
|
|
1281
1312
|
}
|
|
1282
|
-
if (!/^[a-z0-9](?:[a-z0-9-_]*[a-z0-9])?$/i.test(room))
|
|
1313
|
+
if (!/^[a-z0-9](?:[a-z0-9-_]*[a-z0-9])?$/i.test(room))
|
|
1314
|
+
throw new Error("Unsupported room name");
|
|
1283
1315
|
const roomContext = platformRoomContext;
|
|
1284
1316
|
const context = typeof this._context === "function" ? this._context(roomContext) : this._context;
|
|
1285
1317
|
const newRoom = new IORoom(room, __spreadProps(__spreadValues({}, !!_meta ? { _meta } : {}), {
|
|
@@ -1349,7 +1381,8 @@ var PluvIO = class {
|
|
|
1349
1381
|
createToken(params) {
|
|
1350
1382
|
return __async(this, null, function* () {
|
|
1351
1383
|
var _a;
|
|
1352
|
-
if (!this._authorize)
|
|
1384
|
+
if (!this._authorize)
|
|
1385
|
+
throw new Error("IO does not specify authorize during initialization.");
|
|
1353
1386
|
const ioAuthorize = this._getIOAuthorize(params);
|
|
1354
1387
|
const user = params.user;
|
|
1355
1388
|
const parsed = !!ioAuthorize ? ioAuthorize.user.parse(user) : user;
|
|
@@ -1367,7 +1400,9 @@ var PluvIO = class {
|
|
|
1367
1400
|
if (this._platform._createToken) return yield this._platform._createToken(params);
|
|
1368
1401
|
const secret = (_a = ioAuthorize == null ? void 0 : ioAuthorize.secret) != null ? _a : null;
|
|
1369
1402
|
if (!secret) throw new Error("`authorize` was specified without a valid secret");
|
|
1370
|
-
return yield authorize({ platform: this._platform, secret }).encode(
|
|
1403
|
+
return yield authorize({ platform: this._platform, secret }).encode(
|
|
1404
|
+
params
|
|
1405
|
+
);
|
|
1371
1406
|
});
|
|
1372
1407
|
}
|
|
1373
1408
|
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.2",
|
|
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.2",
|
|
25
|
+
"@pluv/types": "^1.0.2"
|
|
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.2",
|
|
33
|
+
"eslint-config-pluv": "^1.0.2"
|
|
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
|
}
|