@pluv/client 0.41.7 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +26 -0
- package/dist/index.d.mts +28 -10
- package/dist/index.d.ts +28 -10
- package/dist/index.js +47 -12
- package/dist/index.mjs +47 -12
- package/package.json +6 -6
- package/src/MockedRoom.ts +20 -16
- package/src/PluvClient.ts +23 -2
- package/src/PluvRoom.ts +32 -1
- package/src/UsersManager.ts +9 -7
- package/src/types.ts +7 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @pluv/client@0.
|
|
2
|
+
> @pluv/client@0.43.0 build /home/runner/work/pluv/pluv/packages/client
|
|
3
3
|
> tsup src/index.ts --format esm,cjs --dts
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
[34mCLI[39m Target: es6
|
|
9
9
|
[34mESM[39m Build start
|
|
10
10
|
[34mCJS[39m Build start
|
|
11
|
-
[
|
|
12
|
-
[
|
|
13
|
-
[
|
|
14
|
-
[
|
|
11
|
+
[32mCJS[39m [1mdist/index.js [22m[32m57.65 KB[39m
|
|
12
|
+
[32mCJS[39m ⚡️ Build success in 115ms
|
|
13
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m56.33 KB[39m
|
|
14
|
+
[32mESM[39m ⚡️ Build success in 116ms
|
|
15
15
|
[34mDTS[39m Build start
|
|
16
|
-
[32mDTS[39m ⚡️ Build success in
|
|
17
|
-
[32mDTS[39m [1mdist/index.d.mts [22m[
|
|
18
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
16
|
+
[32mDTS[39m ⚡️ Build success in 3550ms
|
|
17
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m25.24 KB[39m
|
|
18
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m25.24 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @pluv/client
|
|
2
2
|
|
|
3
|
+
## 0.43.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 65d9172: Added configurable limits for client-side validation. These should only be updated if you control the server and the server limits have been changed.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
// defaults
|
|
11
|
+
createClient({
|
|
12
|
+
limits: {
|
|
13
|
+
presenceMaxSize: 512,
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- @pluv/crdt@0.43.0
|
|
19
|
+
- @pluv/types@0.43.0
|
|
20
|
+
|
|
21
|
+
## 0.42.0
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- 7ae56bf: Periodically sync connection ids with the server to clear stale connections.
|
|
26
|
+
- @pluv/crdt@0.42.0
|
|
27
|
+
- @pluv/types@0.42.0
|
|
28
|
+
|
|
3
29
|
## 0.41.7
|
|
4
30
|
|
|
5
31
|
### Patch Changes
|
package/dist/index.d.mts
CHANGED
|
@@ -84,6 +84,7 @@ type SubscriptionCallback<TIO extends IOLike, TPresence extends JsonObject, TSub
|
|
|
84
84
|
|
|
85
85
|
type UsersManagerConfig<TPresence extends JsonObject = {}> = {
|
|
86
86
|
initialPresence?: TPresence;
|
|
87
|
+
limits: PluvClientLimits;
|
|
87
88
|
presence?: InputZodLike<TPresence>;
|
|
88
89
|
};
|
|
89
90
|
|
|
@@ -130,6 +131,7 @@ type ReconnectTimeoutMs = number | ((params: ReconnectTimeoutMsParams) => number
|
|
|
130
131
|
type RoomConfig<TIO extends IOLike, TMetadata extends JsonObject = {}, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}> = Id<{
|
|
131
132
|
addons?: readonly PluvRoomAddon<TIO, TMetadata, TPresence, TStorage>[];
|
|
132
133
|
debug?: boolean | PluvRoomDebug<TIO>;
|
|
134
|
+
limits: PluvClientLimits;
|
|
133
135
|
onAuthorizationFail?: (error: Error) => void;
|
|
134
136
|
metadata?: InputZodLike<TMetadata>;
|
|
135
137
|
publicKey?: PublicKey<TMetadata>;
|
|
@@ -144,6 +146,7 @@ declare class PluvRoom<TIO extends IOLike<any, any>, TMetadata extends JsonObjec
|
|
|
144
146
|
private readonly _debug;
|
|
145
147
|
private readonly _eventNotifier;
|
|
146
148
|
private readonly _intervals;
|
|
149
|
+
private readonly _limits;
|
|
147
150
|
private readonly _listeners;
|
|
148
151
|
private readonly _otherNotifier;
|
|
149
152
|
private readonly _publicKey;
|
|
@@ -206,6 +209,7 @@ declare class PluvRoom<TIO extends IOLike<any, any>, TMetadata extends JsonObjec
|
|
|
206
209
|
private _handleRegisteredMessage;
|
|
207
210
|
private _handleStorageReceivedMessage;
|
|
208
211
|
private _handleStorageUpdatedMessage;
|
|
212
|
+
private _handleSyncStateReceived;
|
|
209
213
|
private _handleUserJoinedMessage;
|
|
210
214
|
private _heartbeat;
|
|
211
215
|
private _logDebug;
|
|
@@ -234,6 +238,11 @@ declare class PluvRoom<TIO extends IOLike<any, any>, TMetadata extends JsonObjec
|
|
|
234
238
|
type PluvClientOptions<TIO extends IOLike<any, any>, TPresence extends JsonObject, TStorage extends Record<string, CrdtType<any, any>>, TMetadata extends JsonObject> = RoomEndpoints<TIO, TMetadata> & {
|
|
235
239
|
debug?: boolean;
|
|
236
240
|
initialStorage?: AbstractCrdtDocFactory<TStorage>;
|
|
241
|
+
/**
|
|
242
|
+
* @description Configurable limits defined for client-side validation. You should only set
|
|
243
|
+
* this if you control the server and have changed the limits there.
|
|
244
|
+
*/
|
|
245
|
+
limits?: PluvClientLimits;
|
|
237
246
|
metadata?: InputZodLike<TMetadata>;
|
|
238
247
|
presence?: InputZodLike<TPresence>;
|
|
239
248
|
publicKey?: PublicKey<TMetadata>;
|
|
@@ -254,6 +263,7 @@ declare class PluvClient<TIO extends IOLike<any, any>, TPresence extends JsonObj
|
|
|
254
263
|
private readonly _authEndpoint;
|
|
255
264
|
private readonly _debug;
|
|
256
265
|
private readonly _initialStorage?;
|
|
266
|
+
private readonly _limits;
|
|
257
267
|
private readonly _presence?;
|
|
258
268
|
private readonly _publicKey;
|
|
259
269
|
private readonly _rooms;
|
|
@@ -290,6 +300,12 @@ type MergeEventRecords<TEventRecords extends EventRecord<string, any>[], TRoot e
|
|
|
290
300
|
] ? MergeEventRecords<ITail, Omit<TRoot, keyof IHead> & {
|
|
291
301
|
[P in keyof IHead]: TRoot extends Record<P, any> ? TRoot[P] | IHead[P] : IHead[P];
|
|
292
302
|
}> : Id<TRoot>;
|
|
303
|
+
interface PluvClientLimits {
|
|
304
|
+
/**
|
|
305
|
+
* @description Maximum size of presence object in bytes
|
|
306
|
+
*/
|
|
307
|
+
presenceMaxSize?: number | null;
|
|
308
|
+
}
|
|
293
309
|
interface PublicKeyParams<TMetadata extends JsonObject> {
|
|
294
310
|
metadata: TMetadata;
|
|
295
311
|
}
|
|
@@ -367,19 +383,21 @@ type MockedRoomEvents<TIO extends IOLike> = Partial<{
|
|
|
367
383
|
}>;
|
|
368
384
|
type MockedRoomConfig<TIO extends IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}> = {
|
|
369
385
|
events?: MockedRoomEvents<MergeEvents<TEvents, TIO>>;
|
|
386
|
+
limits?: PluvClientLimits;
|
|
370
387
|
router?: PluvRouter<TIO, TPresence, TStorage, TEvents>;
|
|
371
|
-
} & Pick<CrdtManagerOptions<TStorage>, "initialStorage"> & UsersManagerConfig<TPresence>;
|
|
388
|
+
} & Pick<CrdtManagerOptions<TStorage>, "initialStorage"> & Omit<UsersManagerConfig<TPresence>, "limits">;
|
|
372
389
|
declare class MockedRoom<TIO extends IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}> extends AbstractRoom<TIO, TPresence, TStorage> {
|
|
373
|
-
private _crdtManager;
|
|
374
|
-
private _crdtNotifier;
|
|
375
|
-
private _eventNotifier;
|
|
376
|
-
private _events?;
|
|
377
|
-
private
|
|
378
|
-
private
|
|
390
|
+
private readonly _crdtManager;
|
|
391
|
+
private readonly _crdtNotifier;
|
|
392
|
+
private readonly _eventNotifier;
|
|
393
|
+
private readonly _events?;
|
|
394
|
+
private readonly _limits;
|
|
395
|
+
private readonly _otherNotifier;
|
|
396
|
+
private readonly _router;
|
|
379
397
|
private _state;
|
|
380
|
-
private _stateNotifier;
|
|
381
|
-
private _subscriptions;
|
|
382
|
-
private _usersManager;
|
|
398
|
+
private readonly _stateNotifier;
|
|
399
|
+
private readonly _subscriptions;
|
|
400
|
+
private readonly _usersManager;
|
|
383
401
|
constructor(room: string, options: MockedRoomConfig<TIO, TPresence, TStorage, TEvents>);
|
|
384
402
|
get storageLoaded(): boolean;
|
|
385
403
|
broadcast: (<TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(event: TEvent, data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>) => Promise<void>) & { [event in keyof InferIOInput<TIO>]: (data: Id<InferIOInput<TIO>[event]>) => Promise<void>; };
|
package/dist/index.d.ts
CHANGED
|
@@ -84,6 +84,7 @@ type SubscriptionCallback<TIO extends IOLike, TPresence extends JsonObject, TSub
|
|
|
84
84
|
|
|
85
85
|
type UsersManagerConfig<TPresence extends JsonObject = {}> = {
|
|
86
86
|
initialPresence?: TPresence;
|
|
87
|
+
limits: PluvClientLimits;
|
|
87
88
|
presence?: InputZodLike<TPresence>;
|
|
88
89
|
};
|
|
89
90
|
|
|
@@ -130,6 +131,7 @@ type ReconnectTimeoutMs = number | ((params: ReconnectTimeoutMsParams) => number
|
|
|
130
131
|
type RoomConfig<TIO extends IOLike, TMetadata extends JsonObject = {}, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}> = Id<{
|
|
131
132
|
addons?: readonly PluvRoomAddon<TIO, TMetadata, TPresence, TStorage>[];
|
|
132
133
|
debug?: boolean | PluvRoomDebug<TIO>;
|
|
134
|
+
limits: PluvClientLimits;
|
|
133
135
|
onAuthorizationFail?: (error: Error) => void;
|
|
134
136
|
metadata?: InputZodLike<TMetadata>;
|
|
135
137
|
publicKey?: PublicKey<TMetadata>;
|
|
@@ -144,6 +146,7 @@ declare class PluvRoom<TIO extends IOLike<any, any>, TMetadata extends JsonObjec
|
|
|
144
146
|
private readonly _debug;
|
|
145
147
|
private readonly _eventNotifier;
|
|
146
148
|
private readonly _intervals;
|
|
149
|
+
private readonly _limits;
|
|
147
150
|
private readonly _listeners;
|
|
148
151
|
private readonly _otherNotifier;
|
|
149
152
|
private readonly _publicKey;
|
|
@@ -206,6 +209,7 @@ declare class PluvRoom<TIO extends IOLike<any, any>, TMetadata extends JsonObjec
|
|
|
206
209
|
private _handleRegisteredMessage;
|
|
207
210
|
private _handleStorageReceivedMessage;
|
|
208
211
|
private _handleStorageUpdatedMessage;
|
|
212
|
+
private _handleSyncStateReceived;
|
|
209
213
|
private _handleUserJoinedMessage;
|
|
210
214
|
private _heartbeat;
|
|
211
215
|
private _logDebug;
|
|
@@ -234,6 +238,11 @@ declare class PluvRoom<TIO extends IOLike<any, any>, TMetadata extends JsonObjec
|
|
|
234
238
|
type PluvClientOptions<TIO extends IOLike<any, any>, TPresence extends JsonObject, TStorage extends Record<string, CrdtType<any, any>>, TMetadata extends JsonObject> = RoomEndpoints<TIO, TMetadata> & {
|
|
235
239
|
debug?: boolean;
|
|
236
240
|
initialStorage?: AbstractCrdtDocFactory<TStorage>;
|
|
241
|
+
/**
|
|
242
|
+
* @description Configurable limits defined for client-side validation. You should only set
|
|
243
|
+
* this if you control the server and have changed the limits there.
|
|
244
|
+
*/
|
|
245
|
+
limits?: PluvClientLimits;
|
|
237
246
|
metadata?: InputZodLike<TMetadata>;
|
|
238
247
|
presence?: InputZodLike<TPresence>;
|
|
239
248
|
publicKey?: PublicKey<TMetadata>;
|
|
@@ -254,6 +263,7 @@ declare class PluvClient<TIO extends IOLike<any, any>, TPresence extends JsonObj
|
|
|
254
263
|
private readonly _authEndpoint;
|
|
255
264
|
private readonly _debug;
|
|
256
265
|
private readonly _initialStorage?;
|
|
266
|
+
private readonly _limits;
|
|
257
267
|
private readonly _presence?;
|
|
258
268
|
private readonly _publicKey;
|
|
259
269
|
private readonly _rooms;
|
|
@@ -290,6 +300,12 @@ type MergeEventRecords<TEventRecords extends EventRecord<string, any>[], TRoot e
|
|
|
290
300
|
] ? MergeEventRecords<ITail, Omit<TRoot, keyof IHead> & {
|
|
291
301
|
[P in keyof IHead]: TRoot extends Record<P, any> ? TRoot[P] | IHead[P] : IHead[P];
|
|
292
302
|
}> : Id<TRoot>;
|
|
303
|
+
interface PluvClientLimits {
|
|
304
|
+
/**
|
|
305
|
+
* @description Maximum size of presence object in bytes
|
|
306
|
+
*/
|
|
307
|
+
presenceMaxSize?: number | null;
|
|
308
|
+
}
|
|
293
309
|
interface PublicKeyParams<TMetadata extends JsonObject> {
|
|
294
310
|
metadata: TMetadata;
|
|
295
311
|
}
|
|
@@ -367,19 +383,21 @@ type MockedRoomEvents<TIO extends IOLike> = Partial<{
|
|
|
367
383
|
}>;
|
|
368
384
|
type MockedRoomConfig<TIO extends IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}> = {
|
|
369
385
|
events?: MockedRoomEvents<MergeEvents<TEvents, TIO>>;
|
|
386
|
+
limits?: PluvClientLimits;
|
|
370
387
|
router?: PluvRouter<TIO, TPresence, TStorage, TEvents>;
|
|
371
|
-
} & Pick<CrdtManagerOptions<TStorage>, "initialStorage"> & UsersManagerConfig<TPresence>;
|
|
388
|
+
} & Pick<CrdtManagerOptions<TStorage>, "initialStorage"> & Omit<UsersManagerConfig<TPresence>, "limits">;
|
|
372
389
|
declare class MockedRoom<TIO extends IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}> extends AbstractRoom<TIO, TPresence, TStorage> {
|
|
373
|
-
private _crdtManager;
|
|
374
|
-
private _crdtNotifier;
|
|
375
|
-
private _eventNotifier;
|
|
376
|
-
private _events?;
|
|
377
|
-
private
|
|
378
|
-
private
|
|
390
|
+
private readonly _crdtManager;
|
|
391
|
+
private readonly _crdtNotifier;
|
|
392
|
+
private readonly _eventNotifier;
|
|
393
|
+
private readonly _events?;
|
|
394
|
+
private readonly _limits;
|
|
395
|
+
private readonly _otherNotifier;
|
|
396
|
+
private readonly _router;
|
|
379
397
|
private _state;
|
|
380
|
-
private _stateNotifier;
|
|
381
|
-
private _subscriptions;
|
|
382
|
-
private _usersManager;
|
|
398
|
+
private readonly _stateNotifier;
|
|
399
|
+
private readonly _subscriptions;
|
|
400
|
+
private readonly _usersManager;
|
|
383
401
|
constructor(room: string, options: MockedRoomConfig<TIO, TPresence, TStorage, TEvents>);
|
|
384
402
|
get storageLoaded(): boolean;
|
|
385
403
|
broadcast: (<TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(event: TEvent, data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>) => Promise<void>) & { [event in keyof InferIOInput<TIO>]: (data: Id<InferIOInput<TIO>[event]>) => Promise<void>; };
|
package/dist/index.js
CHANGED
|
@@ -83,6 +83,9 @@ var AbstractStorageStore = class {
|
|
|
83
83
|
}
|
|
84
84
|
};
|
|
85
85
|
|
|
86
|
+
// src/constants.ts
|
|
87
|
+
var MAX_PRESENCE_SIZE_BYTES = 512;
|
|
88
|
+
|
|
86
89
|
// src/PluvProcedure.ts
|
|
87
90
|
var PluvProcedure = class _PluvProcedure {
|
|
88
91
|
constructor(config = {}) {
|
|
@@ -317,9 +320,6 @@ var StorageStore = class extends AbstractStorageStore {
|
|
|
317
320
|
}
|
|
318
321
|
};
|
|
319
322
|
|
|
320
|
-
// src/constants.ts
|
|
321
|
-
var MAX_PRESENCE_SIZE_BYTES = 512;
|
|
322
|
-
|
|
323
323
|
// src/utils/debounce.ts
|
|
324
324
|
var debounce = (fn, options) => {
|
|
325
325
|
const { wait, immediate = false } = options != null ? options : {};
|
|
@@ -364,8 +364,9 @@ var UsersManager = class {
|
|
|
364
364
|
this._myself = null;
|
|
365
365
|
this._others = /* @__PURE__ */ new Map();
|
|
366
366
|
this._presence = null;
|
|
367
|
-
const { initialPresence, presence = null } = config;
|
|
367
|
+
const { initialPresence, limits, presence = null } = config;
|
|
368
368
|
this.initialPresence = initialPresence;
|
|
369
|
+
this._limits = limits;
|
|
369
370
|
this._myPresence = initialPresence;
|
|
370
371
|
this._presence = presence;
|
|
371
372
|
}
|
|
@@ -498,7 +499,7 @@ var UsersManager = class {
|
|
|
498
499
|
const cleanedPresence = pickBy(this._myPresence, (value) => typeof value !== "undefined");
|
|
499
500
|
const updated = __spreadValues(__spreadValues(__spreadValues({}, this.initialPresence), cleanedPresence), cleanedPatch);
|
|
500
501
|
const bytes = new TextEncoder().encode(JSON.stringify(updated)).length;
|
|
501
|
-
if (bytes >
|
|
502
|
+
if (!!this._limits.presenceMaxSize && bytes > this._limits.presenceMaxSize) {
|
|
502
503
|
throw new Error(
|
|
503
504
|
`Large presence. Presence must be at most 512 bytes. Current size: ${bytes.toLocaleString()}`
|
|
504
505
|
);
|
|
@@ -563,6 +564,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
563
564
|
debug = false,
|
|
564
565
|
initialPresence,
|
|
565
566
|
initialStorage,
|
|
567
|
+
limits,
|
|
566
568
|
metadata,
|
|
567
569
|
onAuthorizationFail,
|
|
568
570
|
presence,
|
|
@@ -738,6 +740,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
738
740
|
this.metadata = metadata;
|
|
739
741
|
this._debug = debug;
|
|
740
742
|
this._endpoints = { authEndpoint, wsEndpoint };
|
|
743
|
+
this._limits = limits;
|
|
741
744
|
this._reconnectTimeoutMs = reconnectTimeoutMs;
|
|
742
745
|
this._storageStore = storage;
|
|
743
746
|
if (!!publicKey) this._publicKey = publicKey;
|
|
@@ -747,7 +750,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
747
750
|
}
|
|
748
751
|
};
|
|
749
752
|
this._router = router != null ? router : new PluvRouter({});
|
|
750
|
-
this._usersManager = new UsersManager({ initialPresence, presence });
|
|
753
|
+
this._usersManager = new UsersManager({ initialPresence, limits: this._limits, presence });
|
|
751
754
|
this._crdtManager = new CrdtManager({ initialStorage });
|
|
752
755
|
}
|
|
753
756
|
get storageLoaded() {
|
|
@@ -1113,6 +1116,21 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1113
1116
|
);
|
|
1114
1117
|
this._crdtNotifier.rootSubject.next(storageRoot);
|
|
1115
1118
|
}
|
|
1119
|
+
_handleSyncStateReceived(message) {
|
|
1120
|
+
var _a;
|
|
1121
|
+
const { connectionId } = message;
|
|
1122
|
+
if (!connectionId) return;
|
|
1123
|
+
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
1124
|
+
if (!this._usersManager.myself) return;
|
|
1125
|
+
const data = message.data;
|
|
1126
|
+
const active = new Set((_a = data == null ? void 0 : data.connectionIds) != null ? _a : []);
|
|
1127
|
+
const quitters = this._usersManager.getOthers().filter((other) => !active.has(other.connectionId));
|
|
1128
|
+
quitters.forEach((quitter) => {
|
|
1129
|
+
this._usersManager.deleteConnection(quitter.connectionId);
|
|
1130
|
+
});
|
|
1131
|
+
const remaining = this._usersManager.getOthers();
|
|
1132
|
+
this._stateNotifier.subjects.others.next(remaining);
|
|
1133
|
+
}
|
|
1116
1134
|
_handleUserJoinedMessage(message) {
|
|
1117
1135
|
const { connectionId } = message;
|
|
1118
1136
|
if (!connectionId) return;
|
|
@@ -1250,6 +1268,10 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1250
1268
|
this._handleStorageUpdatedMessage(message);
|
|
1251
1269
|
return;
|
|
1252
1270
|
}
|
|
1271
|
+
case "$syncStateReceived": {
|
|
1272
|
+
this._handleSyncStateReceived(message);
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1253
1275
|
case "$userJoined": {
|
|
1254
1276
|
this._handleUserJoinedMessage(message);
|
|
1255
1277
|
return;
|
|
@@ -1383,6 +1405,7 @@ var PluvClient = class {
|
|
|
1383
1405
|
debug: options.debug,
|
|
1384
1406
|
initialPresence: options.initialPresence,
|
|
1385
1407
|
initialStorage: this._initialStorage,
|
|
1408
|
+
limits: this._limits,
|
|
1386
1409
|
metadata: this.metadata,
|
|
1387
1410
|
onAuthorizationFail: options.onAuthorizationFail,
|
|
1388
1411
|
presence: this._presence,
|
|
@@ -1417,11 +1440,23 @@ var PluvClient = class {
|
|
|
1417
1440
|
this._rooms.delete(toLeave.id);
|
|
1418
1441
|
this._logDebug(`Left and deleted room: ${room}`);
|
|
1419
1442
|
});
|
|
1420
|
-
const {
|
|
1443
|
+
const {
|
|
1444
|
+
authEndpoint,
|
|
1445
|
+
debug = false,
|
|
1446
|
+
initialStorage,
|
|
1447
|
+
limits,
|
|
1448
|
+
metadata,
|
|
1449
|
+
presence,
|
|
1450
|
+
publicKey,
|
|
1451
|
+
wsEndpoint
|
|
1452
|
+
} = options;
|
|
1421
1453
|
this.metadata = metadata;
|
|
1422
1454
|
this._authEndpoint = authEndpoint;
|
|
1423
1455
|
this._debug = debug;
|
|
1424
1456
|
this._initialStorage = initialStorage;
|
|
1457
|
+
this._limits = __spreadValues({
|
|
1458
|
+
presenceMaxSize: MAX_PRESENCE_SIZE_BYTES
|
|
1459
|
+
}, limits);
|
|
1425
1460
|
this._presence = presence;
|
|
1426
1461
|
this._wsEndpoint = wsEndpoint;
|
|
1427
1462
|
if (!!publicKey) this._publicKey = publicKey;
|
|
@@ -1459,7 +1494,7 @@ var infer = (callback) => callback;
|
|
|
1459
1494
|
// src/MockedRoom.ts
|
|
1460
1495
|
var MockedRoom = class extends AbstractRoom {
|
|
1461
1496
|
constructor(room, options) {
|
|
1462
|
-
const { events, initialPresence, initialStorage, presence, router } = options;
|
|
1497
|
+
const { events, initialPresence, initialStorage, limits, presence, router } = options;
|
|
1463
1498
|
super(room);
|
|
1464
1499
|
this._crdtNotifier = new CrdtNotifier();
|
|
1465
1500
|
this._eventNotifier = new EventNotifier();
|
|
@@ -1590,11 +1625,11 @@ var MockedRoom = class extends AbstractRoom {
|
|
|
1590
1625
|
if (!!myself) this._stateNotifier.subjects["myself"].next(myself);
|
|
1591
1626
|
};
|
|
1592
1627
|
this._events = events;
|
|
1628
|
+
this._limits = __spreadValues({
|
|
1629
|
+
presenceMaxSize: MAX_PRESENCE_SIZE_BYTES
|
|
1630
|
+
}, limits);
|
|
1593
1631
|
this._router = router != null ? router : new PluvRouter({});
|
|
1594
|
-
this._usersManager = new UsersManager({
|
|
1595
|
-
initialPresence,
|
|
1596
|
-
presence
|
|
1597
|
-
});
|
|
1632
|
+
this._usersManager = new UsersManager({ initialPresence, limits: this._limits, presence });
|
|
1598
1633
|
this._crdtManager = new CrdtManager({
|
|
1599
1634
|
initialStorage
|
|
1600
1635
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -52,6 +52,9 @@ var AbstractStorageStore = class {
|
|
|
52
52
|
}
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
+
// src/constants.ts
|
|
56
|
+
var MAX_PRESENCE_SIZE_BYTES = 512;
|
|
57
|
+
|
|
55
58
|
// src/PluvProcedure.ts
|
|
56
59
|
var PluvProcedure = class _PluvProcedure {
|
|
57
60
|
constructor(config = {}) {
|
|
@@ -286,9 +289,6 @@ var StorageStore = class extends AbstractStorageStore {
|
|
|
286
289
|
}
|
|
287
290
|
};
|
|
288
291
|
|
|
289
|
-
// src/constants.ts
|
|
290
|
-
var MAX_PRESENCE_SIZE_BYTES = 512;
|
|
291
|
-
|
|
292
292
|
// src/utils/debounce.ts
|
|
293
293
|
var debounce = (fn, options) => {
|
|
294
294
|
const { wait, immediate = false } = options != null ? options : {};
|
|
@@ -333,8 +333,9 @@ var UsersManager = class {
|
|
|
333
333
|
this._myself = null;
|
|
334
334
|
this._others = /* @__PURE__ */ new Map();
|
|
335
335
|
this._presence = null;
|
|
336
|
-
const { initialPresence, presence = null } = config;
|
|
336
|
+
const { initialPresence, limits, presence = null } = config;
|
|
337
337
|
this.initialPresence = initialPresence;
|
|
338
|
+
this._limits = limits;
|
|
338
339
|
this._myPresence = initialPresence;
|
|
339
340
|
this._presence = presence;
|
|
340
341
|
}
|
|
@@ -467,7 +468,7 @@ var UsersManager = class {
|
|
|
467
468
|
const cleanedPresence = pickBy(this._myPresence, (value) => typeof value !== "undefined");
|
|
468
469
|
const updated = __spreadValues(__spreadValues(__spreadValues({}, this.initialPresence), cleanedPresence), cleanedPatch);
|
|
469
470
|
const bytes = new TextEncoder().encode(JSON.stringify(updated)).length;
|
|
470
|
-
if (bytes >
|
|
471
|
+
if (!!this._limits.presenceMaxSize && bytes > this._limits.presenceMaxSize) {
|
|
471
472
|
throw new Error(
|
|
472
473
|
`Large presence. Presence must be at most 512 bytes. Current size: ${bytes.toLocaleString()}`
|
|
473
474
|
);
|
|
@@ -532,6 +533,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
532
533
|
debug = false,
|
|
533
534
|
initialPresence,
|
|
534
535
|
initialStorage,
|
|
536
|
+
limits,
|
|
535
537
|
metadata,
|
|
536
538
|
onAuthorizationFail,
|
|
537
539
|
presence,
|
|
@@ -707,6 +709,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
707
709
|
this.metadata = metadata;
|
|
708
710
|
this._debug = debug;
|
|
709
711
|
this._endpoints = { authEndpoint, wsEndpoint };
|
|
712
|
+
this._limits = limits;
|
|
710
713
|
this._reconnectTimeoutMs = reconnectTimeoutMs;
|
|
711
714
|
this._storageStore = storage;
|
|
712
715
|
if (!!publicKey) this._publicKey = publicKey;
|
|
@@ -716,7 +719,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
716
719
|
}
|
|
717
720
|
};
|
|
718
721
|
this._router = router != null ? router : new PluvRouter({});
|
|
719
|
-
this._usersManager = new UsersManager({ initialPresence, presence });
|
|
722
|
+
this._usersManager = new UsersManager({ initialPresence, limits: this._limits, presence });
|
|
720
723
|
this._crdtManager = new CrdtManager({ initialStorage });
|
|
721
724
|
}
|
|
722
725
|
get storageLoaded() {
|
|
@@ -1082,6 +1085,21 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1082
1085
|
);
|
|
1083
1086
|
this._crdtNotifier.rootSubject.next(storageRoot);
|
|
1084
1087
|
}
|
|
1088
|
+
_handleSyncStateReceived(message) {
|
|
1089
|
+
var _a;
|
|
1090
|
+
const { connectionId } = message;
|
|
1091
|
+
if (!connectionId) return;
|
|
1092
|
+
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
1093
|
+
if (!this._usersManager.myself) return;
|
|
1094
|
+
const data = message.data;
|
|
1095
|
+
const active = new Set((_a = data == null ? void 0 : data.connectionIds) != null ? _a : []);
|
|
1096
|
+
const quitters = this._usersManager.getOthers().filter((other) => !active.has(other.connectionId));
|
|
1097
|
+
quitters.forEach((quitter) => {
|
|
1098
|
+
this._usersManager.deleteConnection(quitter.connectionId);
|
|
1099
|
+
});
|
|
1100
|
+
const remaining = this._usersManager.getOthers();
|
|
1101
|
+
this._stateNotifier.subjects.others.next(remaining);
|
|
1102
|
+
}
|
|
1085
1103
|
_handleUserJoinedMessage(message) {
|
|
1086
1104
|
const { connectionId } = message;
|
|
1087
1105
|
if (!connectionId) return;
|
|
@@ -1219,6 +1237,10 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1219
1237
|
this._handleStorageUpdatedMessage(message);
|
|
1220
1238
|
return;
|
|
1221
1239
|
}
|
|
1240
|
+
case "$syncStateReceived": {
|
|
1241
|
+
this._handleSyncStateReceived(message);
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1222
1244
|
case "$userJoined": {
|
|
1223
1245
|
this._handleUserJoinedMessage(message);
|
|
1224
1246
|
return;
|
|
@@ -1352,6 +1374,7 @@ var PluvClient = class {
|
|
|
1352
1374
|
debug: options.debug,
|
|
1353
1375
|
initialPresence: options.initialPresence,
|
|
1354
1376
|
initialStorage: this._initialStorage,
|
|
1377
|
+
limits: this._limits,
|
|
1355
1378
|
metadata: this.metadata,
|
|
1356
1379
|
onAuthorizationFail: options.onAuthorizationFail,
|
|
1357
1380
|
presence: this._presence,
|
|
@@ -1386,11 +1409,23 @@ var PluvClient = class {
|
|
|
1386
1409
|
this._rooms.delete(toLeave.id);
|
|
1387
1410
|
this._logDebug(`Left and deleted room: ${room}`);
|
|
1388
1411
|
});
|
|
1389
|
-
const {
|
|
1412
|
+
const {
|
|
1413
|
+
authEndpoint,
|
|
1414
|
+
debug = false,
|
|
1415
|
+
initialStorage,
|
|
1416
|
+
limits,
|
|
1417
|
+
metadata,
|
|
1418
|
+
presence,
|
|
1419
|
+
publicKey,
|
|
1420
|
+
wsEndpoint
|
|
1421
|
+
} = options;
|
|
1390
1422
|
this.metadata = metadata;
|
|
1391
1423
|
this._authEndpoint = authEndpoint;
|
|
1392
1424
|
this._debug = debug;
|
|
1393
1425
|
this._initialStorage = initialStorage;
|
|
1426
|
+
this._limits = __spreadValues({
|
|
1427
|
+
presenceMaxSize: MAX_PRESENCE_SIZE_BYTES
|
|
1428
|
+
}, limits);
|
|
1394
1429
|
this._presence = presence;
|
|
1395
1430
|
this._wsEndpoint = wsEndpoint;
|
|
1396
1431
|
if (!!publicKey) this._publicKey = publicKey;
|
|
@@ -1428,7 +1463,7 @@ var infer = (callback) => callback;
|
|
|
1428
1463
|
// src/MockedRoom.ts
|
|
1429
1464
|
var MockedRoom = class extends AbstractRoom {
|
|
1430
1465
|
constructor(room, options) {
|
|
1431
|
-
const { events, initialPresence, initialStorage, presence, router } = options;
|
|
1466
|
+
const { events, initialPresence, initialStorage, limits, presence, router } = options;
|
|
1432
1467
|
super(room);
|
|
1433
1468
|
this._crdtNotifier = new CrdtNotifier();
|
|
1434
1469
|
this._eventNotifier = new EventNotifier();
|
|
@@ -1559,11 +1594,11 @@ var MockedRoom = class extends AbstractRoom {
|
|
|
1559
1594
|
if (!!myself) this._stateNotifier.subjects["myself"].next(myself);
|
|
1560
1595
|
};
|
|
1561
1596
|
this._events = events;
|
|
1597
|
+
this._limits = __spreadValues({
|
|
1598
|
+
presenceMaxSize: MAX_PRESENCE_SIZE_BYTES
|
|
1599
|
+
}, limits);
|
|
1562
1600
|
this._router = router != null ? router : new PluvRouter({});
|
|
1563
|
-
this._usersManager = new UsersManager({
|
|
1564
|
-
initialPresence,
|
|
1565
|
-
presence
|
|
1566
|
-
});
|
|
1601
|
+
this._usersManager = new UsersManager({ initialPresence, limits: this._limits, presence });
|
|
1567
1602
|
this._crdtManager = new CrdtManager({
|
|
1568
1603
|
initialStorage
|
|
1569
1604
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pluv/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.0",
|
|
4
4
|
"description": "@pluv/io frontend client",
|
|
5
5
|
"author": "leedavidcs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,15 +18,15 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"wonka": "^6.3.5",
|
|
21
|
-
"@pluv/crdt": "^0.
|
|
22
|
-
"@pluv/types": "^0.
|
|
21
|
+
"@pluv/crdt": "^0.43.0",
|
|
22
|
+
"@pluv/types": "^0.43.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"eslint": "^9.25.
|
|
25
|
+
"eslint": "^9.25.1",
|
|
26
26
|
"tsup": "^8.4.0",
|
|
27
27
|
"typescript": "^5.8.3",
|
|
28
|
-
"@pluv/tsconfig": "^0.
|
|
29
|
-
"eslint-config-pluv": "^0.
|
|
28
|
+
"@pluv/tsconfig": "^0.43.0",
|
|
29
|
+
"eslint-config-pluv": "^0.43.0"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "tsup src/index.ts --format esm,cjs --dts",
|
package/src/MockedRoom.ts
CHANGED
|
@@ -20,11 +20,13 @@ import type {
|
|
|
20
20
|
EventResolver,
|
|
21
21
|
EventResolverContext,
|
|
22
22
|
InternalSubscriptions,
|
|
23
|
+
PluvClientLimits,
|
|
23
24
|
UpdateMyPresenceAction,
|
|
24
25
|
UserInfo,
|
|
25
26
|
WebSocketConnection,
|
|
26
27
|
WebSocketState,
|
|
27
28
|
} from "./types";
|
|
29
|
+
import { MAX_PRESENCE_SIZE_BYTES } from "./constants";
|
|
28
30
|
|
|
29
31
|
export type MockedRoomEvents<TIO extends IOLike> = Partial<{
|
|
30
32
|
[P in keyof InferIOInput<TIO>]: (data: Id<InferIOInput<TIO>[P]>) => Partial<InferIOOutput<TIO>>;
|
|
@@ -37,9 +39,10 @@ export type MockedRoomConfig<
|
|
|
37
39
|
TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {},
|
|
38
40
|
> = {
|
|
39
41
|
events?: MockedRoomEvents<MergeEvents<TEvents, TIO>>;
|
|
42
|
+
limits?: PluvClientLimits;
|
|
40
43
|
router?: PluvRouter<TIO, TPresence, TStorage, TEvents>;
|
|
41
44
|
} & Pick<CrdtManagerOptions<TStorage>, "initialStorage"> &
|
|
42
|
-
UsersManagerConfig<TPresence>;
|
|
45
|
+
Omit<UsersManagerConfig<TPresence>, "limits">;
|
|
43
46
|
|
|
44
47
|
export class MockedRoom<
|
|
45
48
|
TIO extends IOLike,
|
|
@@ -47,12 +50,13 @@ export class MockedRoom<
|
|
|
47
50
|
TStorage extends Record<string, CrdtType<any, any>> = {},
|
|
48
51
|
TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {},
|
|
49
52
|
> extends AbstractRoom<TIO, TPresence, TStorage> {
|
|
50
|
-
private _crdtManager: CrdtManager<TStorage>;
|
|
51
|
-
private _crdtNotifier = new CrdtNotifier<TStorage>();
|
|
52
|
-
private _eventNotifier = new EventNotifier<MergeEvents<TEvents, TIO>>();
|
|
53
|
-
private _events?: MockedRoomEvents<MergeEvents<TEvents, TIO>>;
|
|
54
|
-
private
|
|
55
|
-
private
|
|
53
|
+
private readonly _crdtManager: CrdtManager<TStorage>;
|
|
54
|
+
private readonly _crdtNotifier = new CrdtNotifier<TStorage>();
|
|
55
|
+
private readonly _eventNotifier = new EventNotifier<MergeEvents<TEvents, TIO>>();
|
|
56
|
+
private readonly _events?: MockedRoomEvents<MergeEvents<TEvents, TIO>>;
|
|
57
|
+
private readonly _limits: PluvClientLimits;
|
|
58
|
+
private readonly _otherNotifier = new OtherNotifier<TIO, TPresence>();
|
|
59
|
+
private readonly _router: PluvRouter<TIO, TPresence, TStorage, TEvents>;
|
|
56
60
|
private _state: WebSocketState<TIO> = {
|
|
57
61
|
authorization: {
|
|
58
62
|
token: null,
|
|
@@ -66,24 +70,24 @@ export class MockedRoom<
|
|
|
66
70
|
},
|
|
67
71
|
webSocket: null,
|
|
68
72
|
};
|
|
69
|
-
private _stateNotifier = new StateNotifier<TIO, TPresence>();
|
|
70
|
-
private _subscriptions: InternalSubscriptions = {
|
|
73
|
+
private readonly _stateNotifier = new StateNotifier<TIO, TPresence>();
|
|
74
|
+
private readonly _subscriptions: InternalSubscriptions = {
|
|
71
75
|
observeCrdt: null,
|
|
72
76
|
};
|
|
73
|
-
private _usersManager: UsersManager<TIO, TPresence>;
|
|
77
|
+
private readonly _usersManager: UsersManager<TIO, TPresence>;
|
|
74
78
|
|
|
75
79
|
constructor(room: string, options: MockedRoomConfig<TIO, TPresence, TStorage, TEvents>) {
|
|
76
|
-
const { events, initialPresence, initialStorage, presence, router } = options;
|
|
80
|
+
const { events, initialPresence, initialStorage, limits, presence, router } = options;
|
|
77
81
|
|
|
78
82
|
super(room);
|
|
79
83
|
|
|
80
84
|
this._events = events;
|
|
81
|
-
|
|
85
|
+
this._limits = {
|
|
86
|
+
presenceMaxSize: MAX_PRESENCE_SIZE_BYTES,
|
|
87
|
+
...limits,
|
|
88
|
+
};
|
|
82
89
|
this._router = router ?? (new PluvRouter({}) as PluvRouter<TIO, TPresence, TStorage, TEvents>);
|
|
83
|
-
this._usersManager = new UsersManager<TIO, TPresence>({
|
|
84
|
-
initialPresence,
|
|
85
|
-
presence,
|
|
86
|
-
});
|
|
90
|
+
this._usersManager = new UsersManager<TIO, TPresence>({ initialPresence, limits: this._limits, presence });
|
|
87
91
|
|
|
88
92
|
this._crdtManager = new CrdtManager<TStorage>({
|
|
89
93
|
initialStorage,
|
package/src/PluvClient.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AbstractCrdtDocFactory, CrdtType } from "@pluv/crdt";
|
|
2
2
|
import type { InputZodLike, IOLike, JsonObject } from "@pluv/types";
|
|
3
|
+
import { MAX_PRESENCE_SIZE_BYTES } from "./constants";
|
|
3
4
|
import type { InferCallback } from "./infer";
|
|
4
5
|
import { PluvProcedure } from "./PluvProcedure";
|
|
5
6
|
import type {
|
|
@@ -14,7 +15,7 @@ import type {
|
|
|
14
15
|
import { PluvRoom } from "./PluvRoom";
|
|
15
16
|
import type { PluvRouterEventConfig } from "./PluvRouter";
|
|
16
17
|
import { PluvRouter } from "./PluvRouter";
|
|
17
|
-
import type { PublicKey, WithMetadata } from "./types";
|
|
18
|
+
import type { PluvClientLimits, PublicKey, WithMetadata } from "./types";
|
|
18
19
|
|
|
19
20
|
export type PluvClientOptions<
|
|
20
21
|
TIO extends IOLike<any, any>,
|
|
@@ -24,6 +25,11 @@ export type PluvClientOptions<
|
|
|
24
25
|
> = RoomEndpoints<TIO, TMetadata> & {
|
|
25
26
|
debug?: boolean;
|
|
26
27
|
initialStorage?: AbstractCrdtDocFactory<TStorage>;
|
|
28
|
+
/**
|
|
29
|
+
* @description Configurable limits defined for client-side validation. You should only set
|
|
30
|
+
* this if you control the server and have changed the limits there.
|
|
31
|
+
*/
|
|
32
|
+
limits?: PluvClientLimits;
|
|
27
33
|
metadata?: InputZodLike<TMetadata>;
|
|
28
34
|
presence?: InputZodLike<TPresence>;
|
|
29
35
|
publicKey?: PublicKey<TMetadata>;
|
|
@@ -61,6 +67,7 @@ export class PluvClient<
|
|
|
61
67
|
private readonly _authEndpoint: AuthEndpoint<TMetadata> | undefined;
|
|
62
68
|
private readonly _debug: boolean;
|
|
63
69
|
private readonly _initialStorage?: AbstractCrdtDocFactory<TStorage>;
|
|
70
|
+
private readonly _limits: PluvClientLimits;
|
|
64
71
|
private readonly _presence?: InputZodLike<TPresence>;
|
|
65
72
|
private readonly _publicKey: PublicKey<TMetadata> | null = null;
|
|
66
73
|
private readonly _rooms = new Map<string, PluvRoom<TIO, TMetadata, TPresence, TStorage, any>>();
|
|
@@ -77,13 +84,26 @@ export class PluvClient<
|
|
|
77
84
|
}
|
|
78
85
|
|
|
79
86
|
constructor(options: PluvClientOptions<TIO, TPresence, TStorage, TMetadata>) {
|
|
80
|
-
const {
|
|
87
|
+
const {
|
|
88
|
+
authEndpoint,
|
|
89
|
+
debug = false,
|
|
90
|
+
initialStorage,
|
|
91
|
+
limits,
|
|
92
|
+
metadata,
|
|
93
|
+
presence,
|
|
94
|
+
publicKey,
|
|
95
|
+
wsEndpoint,
|
|
96
|
+
} = options;
|
|
81
97
|
|
|
82
98
|
this.metadata = metadata;
|
|
83
99
|
|
|
84
100
|
this._authEndpoint = authEndpoint as AuthEndpoint<TMetadata>;
|
|
85
101
|
this._debug = debug;
|
|
86
102
|
this._initialStorage = initialStorage;
|
|
103
|
+
this._limits = {
|
|
104
|
+
presenceMaxSize: MAX_PRESENCE_SIZE_BYTES,
|
|
105
|
+
...limits,
|
|
106
|
+
};
|
|
87
107
|
this._presence = presence;
|
|
88
108
|
this._wsEndpoint = wsEndpoint;
|
|
89
109
|
|
|
@@ -104,6 +124,7 @@ export class PluvClient<
|
|
|
104
124
|
debug: options.debug,
|
|
105
125
|
initialPresence: options.initialPresence,
|
|
106
126
|
initialStorage: this._initialStorage,
|
|
127
|
+
limits: this._limits,
|
|
107
128
|
metadata: this.metadata,
|
|
108
129
|
onAuthorizationFail: options.onAuthorizationFail,
|
|
109
130
|
presence: this._presence,
|
package/src/PluvRoom.ts
CHANGED
|
@@ -37,6 +37,7 @@ import type {
|
|
|
37
37
|
EventResolver,
|
|
38
38
|
EventResolverContext,
|
|
39
39
|
InternalSubscriptions,
|
|
40
|
+
PluvClientLimits,
|
|
40
41
|
PublicKey,
|
|
41
42
|
UpdateMyPresenceAction,
|
|
42
43
|
UserInfo,
|
|
@@ -169,6 +170,7 @@ export type RoomConfig<
|
|
|
169
170
|
{
|
|
170
171
|
addons?: readonly PluvRoomAddon<TIO, TMetadata, TPresence, TStorage>[];
|
|
171
172
|
debug?: boolean | PluvRoomDebug<TIO>;
|
|
173
|
+
limits: PluvClientLimits;
|
|
172
174
|
onAuthorizationFail?: (error: Error) => void;
|
|
173
175
|
metadata?: InputZodLike<TMetadata>;
|
|
174
176
|
publicKey?: PublicKey<TMetadata>;
|
|
@@ -197,6 +199,7 @@ export class PluvRoom<
|
|
|
197
199
|
private readonly _intervals: IntervalIds = {
|
|
198
200
|
heartbeat: null,
|
|
199
201
|
};
|
|
202
|
+
private readonly _limits: PluvClientLimits;
|
|
200
203
|
private readonly _listeners: InternalListeners;
|
|
201
204
|
private readonly _otherNotifier = new OtherNotifier<TIO, TPresence>();
|
|
202
205
|
private readonly _publicKey: PublicKey<TMetadata> | null = null;
|
|
@@ -237,6 +240,7 @@ export class PluvRoom<
|
|
|
237
240
|
debug = false,
|
|
238
241
|
initialPresence,
|
|
239
242
|
initialStorage,
|
|
243
|
+
limits,
|
|
240
244
|
metadata,
|
|
241
245
|
onAuthorizationFail,
|
|
242
246
|
presence,
|
|
@@ -259,6 +263,7 @@ export class PluvRoom<
|
|
|
259
263
|
|
|
260
264
|
this._debug = debug;
|
|
261
265
|
this._endpoints = { authEndpoint, wsEndpoint } as RoomEndpoints<TIO, TMetadata>;
|
|
266
|
+
this._limits = limits;
|
|
262
267
|
this._reconnectTimeoutMs = reconnectTimeoutMs;
|
|
263
268
|
this._storageStore = storage;
|
|
264
269
|
|
|
@@ -271,7 +276,7 @@ export class PluvRoom<
|
|
|
271
276
|
};
|
|
272
277
|
|
|
273
278
|
this._router = router ?? (new PluvRouter({}) as PluvRouter<TIO, TPresence, TStorage, TEvents>);
|
|
274
|
-
this._usersManager = new UsersManager<TIO, TPresence>({ initialPresence, presence });
|
|
279
|
+
this._usersManager = new UsersManager<TIO, TPresence>({ initialPresence, limits: this._limits, presence });
|
|
275
280
|
this._crdtManager = new CrdtManager<TStorage>({ initialStorage });
|
|
276
281
|
}
|
|
277
282
|
|
|
@@ -1017,6 +1022,28 @@ export class PluvRoom<
|
|
|
1017
1022
|
this._crdtNotifier.rootSubject.next(storageRoot);
|
|
1018
1023
|
}
|
|
1019
1024
|
|
|
1025
|
+
private _handleSyncStateReceived(message: IOEventMessage<TIO>): void {
|
|
1026
|
+
const { connectionId } = message;
|
|
1027
|
+
|
|
1028
|
+
if (!connectionId) return;
|
|
1029
|
+
// Should not reach here
|
|
1030
|
+
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
1031
|
+
if (!this._usersManager.myself) return;
|
|
1032
|
+
|
|
1033
|
+
const data = message.data as BaseIOEventRecord<InferIOAuthorize<TIO>>["$syncStateReceived"];
|
|
1034
|
+
const active = new Set(data?.connectionIds ?? []);
|
|
1035
|
+
|
|
1036
|
+
const quitters = this._usersManager.getOthers().filter((other) => !active.has(other.connectionId));
|
|
1037
|
+
|
|
1038
|
+
quitters.forEach((quitter) => {
|
|
1039
|
+
this._usersManager.deleteConnection(quitter.connectionId);
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
const remaining = this._usersManager.getOthers();
|
|
1043
|
+
|
|
1044
|
+
this._stateNotifier.subjects.others.next(remaining);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1020
1047
|
private _handleUserJoinedMessage(message: IOEventMessage<TIO>): void {
|
|
1021
1048
|
const { connectionId } = message;
|
|
1022
1049
|
|
|
@@ -1190,6 +1217,10 @@ export class PluvRoom<
|
|
|
1190
1217
|
this._handleStorageUpdatedMessage(message);
|
|
1191
1218
|
return;
|
|
1192
1219
|
}
|
|
1220
|
+
case "$syncStateReceived": {
|
|
1221
|
+
this._handleSyncStateReceived(message);
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1193
1224
|
case "$userJoined": {
|
|
1194
1225
|
this._handleUserJoinedMessage(message);
|
|
1195
1226
|
return;
|
package/src/UsersManager.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { InputZodLike, IOLike, JsonObject, OptionalProps } from "@pluv/types";
|
|
2
|
-
import {
|
|
3
|
-
import type { UserInfo } from "./types";
|
|
2
|
+
import type { PluvClientLimits, UserInfo } from "./types";
|
|
4
3
|
import { pickBy } from "./utils";
|
|
5
4
|
|
|
6
5
|
export type Presence = Record<string, unknown>;
|
|
7
6
|
|
|
8
7
|
export type UsersManagerConfig<TPresence extends JsonObject = {}> = {
|
|
9
8
|
initialPresence?: TPresence;
|
|
9
|
+
limits: PluvClientLimits;
|
|
10
10
|
presence?: InputZodLike<TPresence>;
|
|
11
11
|
};
|
|
12
12
|
|
|
@@ -24,12 +24,13 @@ export type DeleteConnectionResult<TIO extends IOLike, TPresence extends JsonObj
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
export class UsersManager<TIO extends IOLike, TPresence extends JsonObject = {}> {
|
|
27
|
-
|
|
27
|
+
public readonly initialPresence: TPresence;
|
|
28
|
+
|
|
29
|
+
private readonly _idMap = {
|
|
28
30
|
fromConnectionId: new Map<[connectionId: string][0], [clientId: string][0]>(),
|
|
29
31
|
fromClientId: new Map<[clientId: string][0], Set<[connectionId: string][0]>>(),
|
|
30
32
|
};
|
|
31
|
-
|
|
32
|
-
public readonly initialPresence: TPresence;
|
|
33
|
+
private _limits: PluvClientLimits;
|
|
33
34
|
/**
|
|
34
35
|
* @description This presence can be updated while the user is not
|
|
35
36
|
* connected.
|
|
@@ -43,10 +44,11 @@ export class UsersManager<TIO extends IOLike, TPresence extends JsonObject = {}>
|
|
|
43
44
|
private _presence: InputZodLike<TPresence> | null = null;
|
|
44
45
|
|
|
45
46
|
constructor(config: UsersManagerConfig<TPresence>) {
|
|
46
|
-
const { initialPresence, presence = null } = config;
|
|
47
|
+
const { initialPresence, limits, presence = null } = config;
|
|
47
48
|
|
|
48
49
|
this.initialPresence = initialPresence as TPresence;
|
|
49
50
|
|
|
51
|
+
this._limits = limits;
|
|
50
52
|
this._myPresence = initialPresence as TPresence;
|
|
51
53
|
this._presence = presence;
|
|
52
54
|
}
|
|
@@ -256,7 +258,7 @@ export class UsersManager<TIO extends IOLike, TPresence extends JsonObject = {}>
|
|
|
256
258
|
const updated = { ...this.initialPresence, ...cleanedPresence, ...cleanedPatch } as TPresence;
|
|
257
259
|
const bytes = new TextEncoder().encode(JSON.stringify(updated)).length;
|
|
258
260
|
|
|
259
|
-
if (bytes >
|
|
261
|
+
if (!!this._limits.presenceMaxSize && bytes > this._limits.presenceMaxSize) {
|
|
260
262
|
throw new Error(
|
|
261
263
|
`Large presence. Presence must be at most 512 bytes. Current size: ${bytes.toLocaleString()}`,
|
|
262
264
|
);
|
package/src/types.ts
CHANGED
|
@@ -57,6 +57,13 @@ export type MergeEventRecords<
|
|
|
57
57
|
>
|
|
58
58
|
: Id<TRoot>;
|
|
59
59
|
|
|
60
|
+
export interface PluvClientLimits {
|
|
61
|
+
/**
|
|
62
|
+
* @description Maximum size of presence object in bytes
|
|
63
|
+
*/
|
|
64
|
+
presenceMaxSize?: number | null;
|
|
65
|
+
}
|
|
66
|
+
|
|
60
67
|
export interface PublicKeyParams<TMetadata extends JsonObject> {
|
|
61
68
|
metadata: TMetadata;
|
|
62
69
|
}
|