@pluv/client 0.42.0 → 0.44.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 +7 -7
- package/CHANGELOG.md +31 -0
- package/dist/index.d.mts +35 -17
- package/dist/index.d.ts +35 -17
- package/dist/index.js +64 -41
- package/dist/index.mjs +64 -41
- package/package.json +5 -5
- package/src/CrdtManager.ts +1 -1
- package/src/MockedRoom.ts +20 -16
- package/src/PluvClient.ts +23 -2
- package/src/PluvRoom.ts +26 -7
- package/src/UsersManager.ts +9 -7
- package/src/enums.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.44.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
|
-
[32mESM[39m [1mdist/index.mjs [22m[
|
|
11
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m56.45 KB[39m
|
|
12
12
|
[32mESM[39m ⚡️ Build success in 112ms
|
|
13
|
-
[32mCJS[39m [1mdist/index.js [22m[32m57.
|
|
14
|
-
[32mCJS[39m ⚡️ Build success in
|
|
13
|
+
[32mCJS[39m [1mdist/index.js [22m[32m57.76 KB[39m
|
|
14
|
+
[32mCJS[39m ⚡️ Build success in 112ms
|
|
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 3446ms
|
|
17
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m25.36 KB[39m
|
|
18
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m25.36 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# @pluv/client
|
|
2
2
|
|
|
3
|
+
## 0.44.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c9fcea5: **BREAKING** The `ConnectionState` enum is now a `const` to avoid several pitfalls of using TypeScript enums.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- fba9354: Fix potential edge case where event handlers may be called redundantly.
|
|
12
|
+
- bc2d684: Fix `PluvRoom` not properly reconnecting when connections are unavailable.
|
|
13
|
+
- @pluv/crdt@0.44.0
|
|
14
|
+
- @pluv/types@0.44.0
|
|
15
|
+
|
|
16
|
+
## 0.43.0
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- 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.
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
// defaults
|
|
24
|
+
createClient({
|
|
25
|
+
limits: {
|
|
26
|
+
presenceMaxSize: 512,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
- @pluv/crdt@0.43.0
|
|
32
|
+
- @pluv/types@0.43.0
|
|
33
|
+
|
|
3
34
|
## 0.42.0
|
|
4
35
|
|
|
5
36
|
### Patch Changes
|
package/dist/index.d.mts
CHANGED
|
@@ -6,13 +6,14 @@ import { Subject } from 'wonka';
|
|
|
6
6
|
|
|
7
7
|
type EventNotifierSubscriptionCallback<TIO extends IOLike<any, any>, TEvent extends keyof InferIOOutput<TIO>> = (value: Id<IOEventMessage<TIO, TEvent>>) => void;
|
|
8
8
|
|
|
9
|
-
declare
|
|
10
|
-
Closed
|
|
11
|
-
Connecting
|
|
12
|
-
Open
|
|
13
|
-
Unavailable
|
|
14
|
-
Untouched
|
|
15
|
-
}
|
|
9
|
+
declare const ConnectionState: {
|
|
10
|
+
readonly Closed: "Closed";
|
|
11
|
+
readonly Connecting: "Connecting";
|
|
12
|
+
readonly Open: "Open";
|
|
13
|
+
readonly Unavailable: "Unavailable";
|
|
14
|
+
readonly Untouched: "Untouched";
|
|
15
|
+
};
|
|
16
|
+
type ConnectionState = (typeof ConnectionState)[keyof typeof ConnectionState];
|
|
16
17
|
|
|
17
18
|
declare const identity: <T extends unknown>(x: T) => T;
|
|
18
19
|
|
|
@@ -84,6 +85,7 @@ type SubscriptionCallback<TIO extends IOLike, TPresence extends JsonObject, TSub
|
|
|
84
85
|
|
|
85
86
|
type UsersManagerConfig<TPresence extends JsonObject = {}> = {
|
|
86
87
|
initialPresence?: TPresence;
|
|
88
|
+
limits: PluvClientLimits;
|
|
87
89
|
presence?: InputZodLike<TPresence>;
|
|
88
90
|
};
|
|
89
91
|
|
|
@@ -130,6 +132,7 @@ type ReconnectTimeoutMs = number | ((params: ReconnectTimeoutMsParams) => number
|
|
|
130
132
|
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
133
|
addons?: readonly PluvRoomAddon<TIO, TMetadata, TPresence, TStorage>[];
|
|
132
134
|
debug?: boolean | PluvRoomDebug<TIO>;
|
|
135
|
+
limits: PluvClientLimits;
|
|
133
136
|
onAuthorizationFail?: (error: Error) => void;
|
|
134
137
|
metadata?: InputZodLike<TMetadata>;
|
|
135
138
|
publicKey?: PublicKey<TMetadata>;
|
|
@@ -144,6 +147,7 @@ declare class PluvRoom<TIO extends IOLike<any, any>, TMetadata extends JsonObjec
|
|
|
144
147
|
private readonly _debug;
|
|
145
148
|
private readonly _eventNotifier;
|
|
146
149
|
private readonly _intervals;
|
|
150
|
+
private readonly _limits;
|
|
147
151
|
private readonly _listeners;
|
|
148
152
|
private readonly _otherNotifier;
|
|
149
153
|
private readonly _publicKey;
|
|
@@ -235,6 +239,11 @@ declare class PluvRoom<TIO extends IOLike<any, any>, TMetadata extends JsonObjec
|
|
|
235
239
|
type PluvClientOptions<TIO extends IOLike<any, any>, TPresence extends JsonObject, TStorage extends Record<string, CrdtType<any, any>>, TMetadata extends JsonObject> = RoomEndpoints<TIO, TMetadata> & {
|
|
236
240
|
debug?: boolean;
|
|
237
241
|
initialStorage?: AbstractCrdtDocFactory<TStorage>;
|
|
242
|
+
/**
|
|
243
|
+
* @description Configurable limits defined for client-side validation. You should only set
|
|
244
|
+
* this if you control the server and have changed the limits there.
|
|
245
|
+
*/
|
|
246
|
+
limits?: PluvClientLimits;
|
|
238
247
|
metadata?: InputZodLike<TMetadata>;
|
|
239
248
|
presence?: InputZodLike<TPresence>;
|
|
240
249
|
publicKey?: PublicKey<TMetadata>;
|
|
@@ -255,6 +264,7 @@ declare class PluvClient<TIO extends IOLike<any, any>, TPresence extends JsonObj
|
|
|
255
264
|
private readonly _authEndpoint;
|
|
256
265
|
private readonly _debug;
|
|
257
266
|
private readonly _initialStorage?;
|
|
267
|
+
private readonly _limits;
|
|
258
268
|
private readonly _presence?;
|
|
259
269
|
private readonly _publicKey;
|
|
260
270
|
private readonly _rooms;
|
|
@@ -291,6 +301,12 @@ type MergeEventRecords<TEventRecords extends EventRecord<string, any>[], TRoot e
|
|
|
291
301
|
] ? MergeEventRecords<ITail, Omit<TRoot, keyof IHead> & {
|
|
292
302
|
[P in keyof IHead]: TRoot extends Record<P, any> ? TRoot[P] | IHead[P] : IHead[P];
|
|
293
303
|
}> : Id<TRoot>;
|
|
304
|
+
interface PluvClientLimits {
|
|
305
|
+
/**
|
|
306
|
+
* @description Maximum size of presence object in bytes
|
|
307
|
+
*/
|
|
308
|
+
presenceMaxSize?: number | null;
|
|
309
|
+
}
|
|
294
310
|
interface PublicKeyParams<TMetadata extends JsonObject> {
|
|
295
311
|
metadata: TMetadata;
|
|
296
312
|
}
|
|
@@ -368,19 +384,21 @@ type MockedRoomEvents<TIO extends IOLike> = Partial<{
|
|
|
368
384
|
}>;
|
|
369
385
|
type MockedRoomConfig<TIO extends IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}> = {
|
|
370
386
|
events?: MockedRoomEvents<MergeEvents<TEvents, TIO>>;
|
|
387
|
+
limits?: PluvClientLimits;
|
|
371
388
|
router?: PluvRouter<TIO, TPresence, TStorage, TEvents>;
|
|
372
|
-
} & Pick<CrdtManagerOptions<TStorage>, "initialStorage"> & UsersManagerConfig<TPresence>;
|
|
389
|
+
} & Pick<CrdtManagerOptions<TStorage>, "initialStorage"> & Omit<UsersManagerConfig<TPresence>, "limits">;
|
|
373
390
|
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> {
|
|
374
|
-
private _crdtManager;
|
|
375
|
-
private _crdtNotifier;
|
|
376
|
-
private _eventNotifier;
|
|
377
|
-
private _events?;
|
|
378
|
-
private
|
|
379
|
-
private
|
|
391
|
+
private readonly _crdtManager;
|
|
392
|
+
private readonly _crdtNotifier;
|
|
393
|
+
private readonly _eventNotifier;
|
|
394
|
+
private readonly _events?;
|
|
395
|
+
private readonly _limits;
|
|
396
|
+
private readonly _otherNotifier;
|
|
397
|
+
private readonly _router;
|
|
380
398
|
private _state;
|
|
381
|
-
private _stateNotifier;
|
|
382
|
-
private _subscriptions;
|
|
383
|
-
private _usersManager;
|
|
399
|
+
private readonly _stateNotifier;
|
|
400
|
+
private readonly _subscriptions;
|
|
401
|
+
private readonly _usersManager;
|
|
384
402
|
constructor(room: string, options: MockedRoomConfig<TIO, TPresence, TStorage, TEvents>);
|
|
385
403
|
get storageLoaded(): boolean;
|
|
386
404
|
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
|
@@ -6,13 +6,14 @@ import { Subject } from 'wonka';
|
|
|
6
6
|
|
|
7
7
|
type EventNotifierSubscriptionCallback<TIO extends IOLike<any, any>, TEvent extends keyof InferIOOutput<TIO>> = (value: Id<IOEventMessage<TIO, TEvent>>) => void;
|
|
8
8
|
|
|
9
|
-
declare
|
|
10
|
-
Closed
|
|
11
|
-
Connecting
|
|
12
|
-
Open
|
|
13
|
-
Unavailable
|
|
14
|
-
Untouched
|
|
15
|
-
}
|
|
9
|
+
declare const ConnectionState: {
|
|
10
|
+
readonly Closed: "Closed";
|
|
11
|
+
readonly Connecting: "Connecting";
|
|
12
|
+
readonly Open: "Open";
|
|
13
|
+
readonly Unavailable: "Unavailable";
|
|
14
|
+
readonly Untouched: "Untouched";
|
|
15
|
+
};
|
|
16
|
+
type ConnectionState = (typeof ConnectionState)[keyof typeof ConnectionState];
|
|
16
17
|
|
|
17
18
|
declare const identity: <T extends unknown>(x: T) => T;
|
|
18
19
|
|
|
@@ -84,6 +85,7 @@ type SubscriptionCallback<TIO extends IOLike, TPresence extends JsonObject, TSub
|
|
|
84
85
|
|
|
85
86
|
type UsersManagerConfig<TPresence extends JsonObject = {}> = {
|
|
86
87
|
initialPresence?: TPresence;
|
|
88
|
+
limits: PluvClientLimits;
|
|
87
89
|
presence?: InputZodLike<TPresence>;
|
|
88
90
|
};
|
|
89
91
|
|
|
@@ -130,6 +132,7 @@ type ReconnectTimeoutMs = number | ((params: ReconnectTimeoutMsParams) => number
|
|
|
130
132
|
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
133
|
addons?: readonly PluvRoomAddon<TIO, TMetadata, TPresence, TStorage>[];
|
|
132
134
|
debug?: boolean | PluvRoomDebug<TIO>;
|
|
135
|
+
limits: PluvClientLimits;
|
|
133
136
|
onAuthorizationFail?: (error: Error) => void;
|
|
134
137
|
metadata?: InputZodLike<TMetadata>;
|
|
135
138
|
publicKey?: PublicKey<TMetadata>;
|
|
@@ -144,6 +147,7 @@ declare class PluvRoom<TIO extends IOLike<any, any>, TMetadata extends JsonObjec
|
|
|
144
147
|
private readonly _debug;
|
|
145
148
|
private readonly _eventNotifier;
|
|
146
149
|
private readonly _intervals;
|
|
150
|
+
private readonly _limits;
|
|
147
151
|
private readonly _listeners;
|
|
148
152
|
private readonly _otherNotifier;
|
|
149
153
|
private readonly _publicKey;
|
|
@@ -235,6 +239,11 @@ declare class PluvRoom<TIO extends IOLike<any, any>, TMetadata extends JsonObjec
|
|
|
235
239
|
type PluvClientOptions<TIO extends IOLike<any, any>, TPresence extends JsonObject, TStorage extends Record<string, CrdtType<any, any>>, TMetadata extends JsonObject> = RoomEndpoints<TIO, TMetadata> & {
|
|
236
240
|
debug?: boolean;
|
|
237
241
|
initialStorage?: AbstractCrdtDocFactory<TStorage>;
|
|
242
|
+
/**
|
|
243
|
+
* @description Configurable limits defined for client-side validation. You should only set
|
|
244
|
+
* this if you control the server and have changed the limits there.
|
|
245
|
+
*/
|
|
246
|
+
limits?: PluvClientLimits;
|
|
238
247
|
metadata?: InputZodLike<TMetadata>;
|
|
239
248
|
presence?: InputZodLike<TPresence>;
|
|
240
249
|
publicKey?: PublicKey<TMetadata>;
|
|
@@ -255,6 +264,7 @@ declare class PluvClient<TIO extends IOLike<any, any>, TPresence extends JsonObj
|
|
|
255
264
|
private readonly _authEndpoint;
|
|
256
265
|
private readonly _debug;
|
|
257
266
|
private readonly _initialStorage?;
|
|
267
|
+
private readonly _limits;
|
|
258
268
|
private readonly _presence?;
|
|
259
269
|
private readonly _publicKey;
|
|
260
270
|
private readonly _rooms;
|
|
@@ -291,6 +301,12 @@ type MergeEventRecords<TEventRecords extends EventRecord<string, any>[], TRoot e
|
|
|
291
301
|
] ? MergeEventRecords<ITail, Omit<TRoot, keyof IHead> & {
|
|
292
302
|
[P in keyof IHead]: TRoot extends Record<P, any> ? TRoot[P] | IHead[P] : IHead[P];
|
|
293
303
|
}> : Id<TRoot>;
|
|
304
|
+
interface PluvClientLimits {
|
|
305
|
+
/**
|
|
306
|
+
* @description Maximum size of presence object in bytes
|
|
307
|
+
*/
|
|
308
|
+
presenceMaxSize?: number | null;
|
|
309
|
+
}
|
|
294
310
|
interface PublicKeyParams<TMetadata extends JsonObject> {
|
|
295
311
|
metadata: TMetadata;
|
|
296
312
|
}
|
|
@@ -368,19 +384,21 @@ type MockedRoomEvents<TIO extends IOLike> = Partial<{
|
|
|
368
384
|
}>;
|
|
369
385
|
type MockedRoomConfig<TIO extends IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}> = {
|
|
370
386
|
events?: MockedRoomEvents<MergeEvents<TEvents, TIO>>;
|
|
387
|
+
limits?: PluvClientLimits;
|
|
371
388
|
router?: PluvRouter<TIO, TPresence, TStorage, TEvents>;
|
|
372
|
-
} & Pick<CrdtManagerOptions<TStorage>, "initialStorage"> & UsersManagerConfig<TPresence>;
|
|
389
|
+
} & Pick<CrdtManagerOptions<TStorage>, "initialStorage"> & Omit<UsersManagerConfig<TPresence>, "limits">;
|
|
373
390
|
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> {
|
|
374
|
-
private _crdtManager;
|
|
375
|
-
private _crdtNotifier;
|
|
376
|
-
private _eventNotifier;
|
|
377
|
-
private _events?;
|
|
378
|
-
private
|
|
379
|
-
private
|
|
391
|
+
private readonly _crdtManager;
|
|
392
|
+
private readonly _crdtNotifier;
|
|
393
|
+
private readonly _eventNotifier;
|
|
394
|
+
private readonly _events?;
|
|
395
|
+
private readonly _limits;
|
|
396
|
+
private readonly _otherNotifier;
|
|
397
|
+
private readonly _router;
|
|
380
398
|
private _state;
|
|
381
|
-
private _stateNotifier;
|
|
382
|
-
private _subscriptions;
|
|
383
|
-
private _usersManager;
|
|
399
|
+
private readonly _stateNotifier;
|
|
400
|
+
private readonly _subscriptions;
|
|
401
|
+
private readonly _usersManager;
|
|
384
402
|
constructor(room: string, options: MockedRoomConfig<TIO, TPresence, TStorage, TEvents>);
|
|
385
403
|
get storageLoaded(): boolean;
|
|
386
404
|
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 = {}) {
|
|
@@ -169,7 +172,7 @@ var CrdtManager = class {
|
|
|
169
172
|
}
|
|
170
173
|
_applyDocUpdates(doc, updates, origin) {
|
|
171
174
|
doc.transact(() => {
|
|
172
|
-
doc.batchApplyEncodedState(
|
|
175
|
+
doc.batchApplyEncodedState({ origin, updates });
|
|
173
176
|
}, origin);
|
|
174
177
|
return doc;
|
|
175
178
|
}
|
|
@@ -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
|
);
|
|
@@ -534,14 +535,13 @@ var UsersManager = class {
|
|
|
534
535
|
};
|
|
535
536
|
|
|
536
537
|
// src/enums.ts
|
|
537
|
-
var ConnectionState =
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
})(ConnectionState || {});
|
|
538
|
+
var ConnectionState = {
|
|
539
|
+
Closed: "Closed",
|
|
540
|
+
Connecting: "Connecting",
|
|
541
|
+
Open: "Open",
|
|
542
|
+
Unavailable: "Unavailable",
|
|
543
|
+
Untouched: "Untouched"
|
|
544
|
+
};
|
|
545
545
|
|
|
546
546
|
// src/PluvRoom.ts
|
|
547
547
|
var ADD_TO_STORAGE_STATE_DEBOUNCE_MS = 1e3;
|
|
@@ -563,6 +563,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
563
563
|
debug = false,
|
|
564
564
|
initialPresence,
|
|
565
565
|
initialStorage,
|
|
566
|
+
limits,
|
|
566
567
|
metadata,
|
|
567
568
|
onAuthorizationFail,
|
|
568
569
|
presence,
|
|
@@ -597,7 +598,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
597
598
|
attempts: 0,
|
|
598
599
|
count: 0,
|
|
599
600
|
id: null,
|
|
600
|
-
state:
|
|
601
|
+
state: ConnectionState.Untouched
|
|
601
602
|
},
|
|
602
603
|
webSocket: null
|
|
603
604
|
};
|
|
@@ -606,7 +607,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
606
607
|
this.broadcast = new Proxy(
|
|
607
608
|
(event, data) => __async(this, null, function* () {
|
|
608
609
|
if (!this._state.webSocket) return;
|
|
609
|
-
if (this._state.connection.state !==
|
|
610
|
+
if (this._state.connection.state !== ConnectionState.Open) return;
|
|
610
611
|
const type = event.toString();
|
|
611
612
|
const procedure = this._router._defs.events[type];
|
|
612
613
|
if (!(procedure == null ? void 0 : procedure.config.broadcast)) {
|
|
@@ -738,6 +739,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
738
739
|
this.metadata = metadata;
|
|
739
740
|
this._debug = debug;
|
|
740
741
|
this._endpoints = { authEndpoint, wsEndpoint };
|
|
742
|
+
this._limits = limits;
|
|
741
743
|
this._reconnectTimeoutMs = reconnectTimeoutMs;
|
|
742
744
|
this._storageStore = storage;
|
|
743
745
|
if (!!publicKey) this._publicKey = publicKey;
|
|
@@ -747,7 +749,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
747
749
|
}
|
|
748
750
|
};
|
|
749
751
|
this._router = router != null ? router : new PluvRouter({});
|
|
750
|
-
this._usersManager = new UsersManager({ initialPresence, presence });
|
|
752
|
+
this._usersManager = new UsersManager({ initialPresence, limits: this._limits, presence });
|
|
751
753
|
this._crdtManager = new CrdtManager({ initialStorage });
|
|
752
754
|
}
|
|
753
755
|
get storageLoaded() {
|
|
@@ -762,13 +764,13 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
762
764
|
const params = (_a = args[0]) != null ? _a : {};
|
|
763
765
|
const metadata = params.metadata;
|
|
764
766
|
this._setMetadata(metadata);
|
|
765
|
-
const canConnect = [
|
|
767
|
+
const canConnect = [ConnectionState.Closed, ConnectionState.Unavailable, ConnectionState.Untouched].some(
|
|
766
768
|
(state) => this._state.connection.state === state
|
|
767
769
|
);
|
|
768
770
|
if (!canConnect) return;
|
|
769
771
|
this._clearTimeout(this._timeouts.reconnect);
|
|
770
772
|
this._updateState((oldState) => {
|
|
771
|
-
oldState.connection.state =
|
|
773
|
+
oldState.connection.state = ConnectionState.Connecting;
|
|
772
774
|
return oldState;
|
|
773
775
|
});
|
|
774
776
|
yield this._storageStore.initialize();
|
|
@@ -806,7 +808,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
806
808
|
disconnect() {
|
|
807
809
|
return __async(this, null, function* () {
|
|
808
810
|
if (!this._state.webSocket) return;
|
|
809
|
-
const canDisconnect = [
|
|
811
|
+
const canDisconnect = [ConnectionState.Connecting, ConnectionState.Open].some(
|
|
810
812
|
(state) => this._state.connection.state === state
|
|
811
813
|
);
|
|
812
814
|
if (!canDisconnect) return;
|
|
@@ -815,7 +817,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
815
817
|
this._clearTimeout(this._timeouts.reconnect);
|
|
816
818
|
this._updateState((oldState) => {
|
|
817
819
|
oldState.authorization.token = null;
|
|
818
|
-
oldState.connection.state =
|
|
820
|
+
oldState.connection.state = ConnectionState.Closed;
|
|
819
821
|
oldState.webSocket = null;
|
|
820
822
|
return oldState;
|
|
821
823
|
});
|
|
@@ -854,7 +856,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
854
856
|
_attachWindowListeners() {
|
|
855
857
|
if (typeof window === "undefined") return;
|
|
856
858
|
if (typeof document === "undefined") return;
|
|
857
|
-
this._windowListeners = {
|
|
859
|
+
this._windowListeners = this._windowListeners || {
|
|
858
860
|
onNavigatorOffline: this._onNavigatorOffline.bind(this),
|
|
859
861
|
onNavigatorOnline: this._onNavigatorOnline.bind(this),
|
|
860
862
|
onVisibilityChange: this._onVisibilityChange.bind(this)
|
|
@@ -865,7 +867,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
865
867
|
}
|
|
866
868
|
_attachWsListeners() {
|
|
867
869
|
if (!this._state.webSocket) return;
|
|
868
|
-
this._wsListeners = {
|
|
870
|
+
this._wsListeners = this._wsListeners || {
|
|
869
871
|
onClose: this._onClose.bind(this),
|
|
870
872
|
onError: this._onError.bind(this),
|
|
871
873
|
onMessage: this._onMessage.bind(this),
|
|
@@ -1185,7 +1187,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1185
1187
|
const authError = error instanceof Error ? error : new Error("Authorization failed");
|
|
1186
1188
|
this._listeners.onAuthorizationFail(authError);
|
|
1187
1189
|
this._updateState((oldState) => {
|
|
1188
|
-
oldState.connection.state =
|
|
1190
|
+
oldState.connection.state = ConnectionState.Unavailable;
|
|
1189
1191
|
return oldState;
|
|
1190
1192
|
});
|
|
1191
1193
|
}
|
|
@@ -1209,7 +1211,15 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1209
1211
|
].some((okToRetryCode) => event.code === okToRetryCode);
|
|
1210
1212
|
this._updateState((oldState) => {
|
|
1211
1213
|
oldState.authorization.token = null;
|
|
1212
|
-
oldState.connection.state = shouldRetry ?
|
|
1214
|
+
oldState.connection.state = shouldRetry ? ConnectionState.Unavailable : (
|
|
1215
|
+
/**
|
|
1216
|
+
* !HACK
|
|
1217
|
+
* @description Assume these are not recoverable. We're just going to mark this
|
|
1218
|
+
* as Closed to avoid reconnect attempts.
|
|
1219
|
+
* @date April 28, 2025
|
|
1220
|
+
*/
|
|
1221
|
+
ConnectionState.Closed
|
|
1222
|
+
);
|
|
1213
1223
|
oldState.webSocket = null;
|
|
1214
1224
|
return oldState;
|
|
1215
1225
|
});
|
|
@@ -1277,10 +1287,10 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1277
1287
|
}
|
|
1278
1288
|
}
|
|
1279
1289
|
_onOpen() {
|
|
1280
|
-
if (this._state.connection.state !==
|
|
1290
|
+
if (this._state.connection.state !== ConnectionState.Connecting) return;
|
|
1281
1291
|
this._logDebug("WebSocket connected");
|
|
1282
1292
|
this._updateState((oldState) => {
|
|
1283
|
-
oldState.connection.state =
|
|
1293
|
+
oldState.connection.state = ConnectionState.Open;
|
|
1284
1294
|
return oldState;
|
|
1285
1295
|
});
|
|
1286
1296
|
this._clearInterval(this._intervals.heartbeat);
|
|
@@ -1288,16 +1298,16 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1288
1298
|
}
|
|
1289
1299
|
_onNavigatorOffline() {
|
|
1290
1300
|
return __async(this, null, function* () {
|
|
1291
|
-
if (this._state.connection.state ===
|
|
1301
|
+
if (this._state.connection.state === ConnectionState.Closed) return;
|
|
1292
1302
|
this._updateState((oldState) => {
|
|
1293
|
-
oldState.connection.state =
|
|
1303
|
+
oldState.connection.state = ConnectionState.Unavailable;
|
|
1294
1304
|
return oldState;
|
|
1295
1305
|
});
|
|
1296
1306
|
});
|
|
1297
1307
|
}
|
|
1298
1308
|
_onNavigatorOnline() {
|
|
1299
1309
|
return __async(this, null, function* () {
|
|
1300
|
-
if (this._state.connection.state !==
|
|
1310
|
+
if (this._state.connection.state !== ConnectionState.Unavailable) return;
|
|
1301
1311
|
yield this._reconnect();
|
|
1302
1312
|
});
|
|
1303
1313
|
}
|
|
@@ -1306,12 +1316,12 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1306
1316
|
if (typeof document === "undefined") return;
|
|
1307
1317
|
if (document.visibilityState !== "visible") return;
|
|
1308
1318
|
switch (this._state.connection.state) {
|
|
1309
|
-
case
|
|
1319
|
+
case ConnectionState.Open: {
|
|
1310
1320
|
this._clearInterval(this._intervals.heartbeat);
|
|
1311
1321
|
setInterval(this._heartbeat.bind(this), HEARTBEAT_INTERVAL_MS);
|
|
1312
1322
|
return;
|
|
1313
1323
|
}
|
|
1314
|
-
case
|
|
1324
|
+
case ConnectionState.Unavailable: {
|
|
1315
1325
|
yield this._reconnect();
|
|
1316
1326
|
return;
|
|
1317
1327
|
}
|
|
@@ -1339,8 +1349,8 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1339
1349
|
}
|
|
1340
1350
|
_reconnect() {
|
|
1341
1351
|
return __async(this, null, function* () {
|
|
1342
|
-
if (!this._state.webSocket) return;
|
|
1343
1352
|
this._closeWs();
|
|
1353
|
+
if (this._state.connection.state === ConnectionState.Closed) return;
|
|
1344
1354
|
const metadata = this._lastMetadata;
|
|
1345
1355
|
const params = typeof metadata === "undefined" ? [] : [{ metadata }];
|
|
1346
1356
|
yield this.connect(...params);
|
|
@@ -1402,6 +1412,7 @@ var PluvClient = class {
|
|
|
1402
1412
|
debug: options.debug,
|
|
1403
1413
|
initialPresence: options.initialPresence,
|
|
1404
1414
|
initialStorage: this._initialStorage,
|
|
1415
|
+
limits: this._limits,
|
|
1405
1416
|
metadata: this.metadata,
|
|
1406
1417
|
onAuthorizationFail: options.onAuthorizationFail,
|
|
1407
1418
|
presence: this._presence,
|
|
@@ -1436,11 +1447,23 @@ var PluvClient = class {
|
|
|
1436
1447
|
this._rooms.delete(toLeave.id);
|
|
1437
1448
|
this._logDebug(`Left and deleted room: ${room}`);
|
|
1438
1449
|
});
|
|
1439
|
-
const {
|
|
1450
|
+
const {
|
|
1451
|
+
authEndpoint,
|
|
1452
|
+
debug = false,
|
|
1453
|
+
initialStorage,
|
|
1454
|
+
limits,
|
|
1455
|
+
metadata,
|
|
1456
|
+
presence,
|
|
1457
|
+
publicKey,
|
|
1458
|
+
wsEndpoint
|
|
1459
|
+
} = options;
|
|
1440
1460
|
this.metadata = metadata;
|
|
1441
1461
|
this._authEndpoint = authEndpoint;
|
|
1442
1462
|
this._debug = debug;
|
|
1443
1463
|
this._initialStorage = initialStorage;
|
|
1464
|
+
this._limits = __spreadValues({
|
|
1465
|
+
presenceMaxSize: MAX_PRESENCE_SIZE_BYTES
|
|
1466
|
+
}, limits);
|
|
1444
1467
|
this._presence = presence;
|
|
1445
1468
|
this._wsEndpoint = wsEndpoint;
|
|
1446
1469
|
if (!!publicKey) this._publicKey = publicKey;
|
|
@@ -1478,7 +1501,7 @@ var infer = (callback) => callback;
|
|
|
1478
1501
|
// src/MockedRoom.ts
|
|
1479
1502
|
var MockedRoom = class extends AbstractRoom {
|
|
1480
1503
|
constructor(room, options) {
|
|
1481
|
-
const { events, initialPresence, initialStorage, presence, router } = options;
|
|
1504
|
+
const { events, initialPresence, initialStorage, limits, presence, router } = options;
|
|
1482
1505
|
super(room);
|
|
1483
1506
|
this._crdtNotifier = new CrdtNotifier();
|
|
1484
1507
|
this._eventNotifier = new EventNotifier();
|
|
@@ -1492,7 +1515,7 @@ var MockedRoom = class extends AbstractRoom {
|
|
|
1492
1515
|
attempts: 0,
|
|
1493
1516
|
count: 1,
|
|
1494
1517
|
id: null,
|
|
1495
|
-
state:
|
|
1518
|
+
state: ConnectionState.Untouched
|
|
1496
1519
|
},
|
|
1497
1520
|
webSocket: null
|
|
1498
1521
|
};
|
|
@@ -1503,7 +1526,7 @@ var MockedRoom = class extends AbstractRoom {
|
|
|
1503
1526
|
this.broadcast = new Proxy(
|
|
1504
1527
|
(event, data) => __async(this, null, function* () {
|
|
1505
1528
|
if (!this._state.webSocket) return;
|
|
1506
|
-
if (this._state.connection.state !==
|
|
1529
|
+
if (this._state.connection.state !== ConnectionState.Open) return;
|
|
1507
1530
|
const type = event.toString();
|
|
1508
1531
|
const procedure = this._router._defs.events[type];
|
|
1509
1532
|
if (!(procedure == null ? void 0 : procedure.config.broadcast)) {
|
|
@@ -1609,11 +1632,11 @@ var MockedRoom = class extends AbstractRoom {
|
|
|
1609
1632
|
if (!!myself) this._stateNotifier.subjects["myself"].next(myself);
|
|
1610
1633
|
};
|
|
1611
1634
|
this._events = events;
|
|
1635
|
+
this._limits = __spreadValues({
|
|
1636
|
+
presenceMaxSize: MAX_PRESENCE_SIZE_BYTES
|
|
1637
|
+
}, limits);
|
|
1612
1638
|
this._router = router != null ? router : new PluvRouter({});
|
|
1613
|
-
this._usersManager = new UsersManager({
|
|
1614
|
-
initialPresence,
|
|
1615
|
-
presence
|
|
1616
|
-
});
|
|
1639
|
+
this._usersManager = new UsersManager({ initialPresence, limits: this._limits, presence });
|
|
1617
1640
|
this._crdtManager = new CrdtManager({
|
|
1618
1641
|
initialStorage
|
|
1619
1642
|
});
|
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 = {}) {
|
|
@@ -138,7 +141,7 @@ var CrdtManager = class {
|
|
|
138
141
|
}
|
|
139
142
|
_applyDocUpdates(doc, updates, origin) {
|
|
140
143
|
doc.transact(() => {
|
|
141
|
-
doc.batchApplyEncodedState(
|
|
144
|
+
doc.batchApplyEncodedState({ origin, updates });
|
|
142
145
|
}, origin);
|
|
143
146
|
return doc;
|
|
144
147
|
}
|
|
@@ -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
|
);
|
|
@@ -503,14 +504,13 @@ var UsersManager = class {
|
|
|
503
504
|
};
|
|
504
505
|
|
|
505
506
|
// src/enums.ts
|
|
506
|
-
var ConnectionState =
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
})(ConnectionState || {});
|
|
507
|
+
var ConnectionState = {
|
|
508
|
+
Closed: "Closed",
|
|
509
|
+
Connecting: "Connecting",
|
|
510
|
+
Open: "Open",
|
|
511
|
+
Unavailable: "Unavailable",
|
|
512
|
+
Untouched: "Untouched"
|
|
513
|
+
};
|
|
514
514
|
|
|
515
515
|
// src/PluvRoom.ts
|
|
516
516
|
var ADD_TO_STORAGE_STATE_DEBOUNCE_MS = 1e3;
|
|
@@ -532,6 +532,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
532
532
|
debug = false,
|
|
533
533
|
initialPresence,
|
|
534
534
|
initialStorage,
|
|
535
|
+
limits,
|
|
535
536
|
metadata,
|
|
536
537
|
onAuthorizationFail,
|
|
537
538
|
presence,
|
|
@@ -566,7 +567,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
566
567
|
attempts: 0,
|
|
567
568
|
count: 0,
|
|
568
569
|
id: null,
|
|
569
|
-
state:
|
|
570
|
+
state: ConnectionState.Untouched
|
|
570
571
|
},
|
|
571
572
|
webSocket: null
|
|
572
573
|
};
|
|
@@ -575,7 +576,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
575
576
|
this.broadcast = new Proxy(
|
|
576
577
|
(event, data) => __async(this, null, function* () {
|
|
577
578
|
if (!this._state.webSocket) return;
|
|
578
|
-
if (this._state.connection.state !==
|
|
579
|
+
if (this._state.connection.state !== ConnectionState.Open) return;
|
|
579
580
|
const type = event.toString();
|
|
580
581
|
const procedure = this._router._defs.events[type];
|
|
581
582
|
if (!(procedure == null ? void 0 : procedure.config.broadcast)) {
|
|
@@ -707,6 +708,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
707
708
|
this.metadata = metadata;
|
|
708
709
|
this._debug = debug;
|
|
709
710
|
this._endpoints = { authEndpoint, wsEndpoint };
|
|
711
|
+
this._limits = limits;
|
|
710
712
|
this._reconnectTimeoutMs = reconnectTimeoutMs;
|
|
711
713
|
this._storageStore = storage;
|
|
712
714
|
if (!!publicKey) this._publicKey = publicKey;
|
|
@@ -716,7 +718,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
716
718
|
}
|
|
717
719
|
};
|
|
718
720
|
this._router = router != null ? router : new PluvRouter({});
|
|
719
|
-
this._usersManager = new UsersManager({ initialPresence, presence });
|
|
721
|
+
this._usersManager = new UsersManager({ initialPresence, limits: this._limits, presence });
|
|
720
722
|
this._crdtManager = new CrdtManager({ initialStorage });
|
|
721
723
|
}
|
|
722
724
|
get storageLoaded() {
|
|
@@ -731,13 +733,13 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
731
733
|
const params = (_a = args[0]) != null ? _a : {};
|
|
732
734
|
const metadata = params.metadata;
|
|
733
735
|
this._setMetadata(metadata);
|
|
734
|
-
const canConnect = [
|
|
736
|
+
const canConnect = [ConnectionState.Closed, ConnectionState.Unavailable, ConnectionState.Untouched].some(
|
|
735
737
|
(state) => this._state.connection.state === state
|
|
736
738
|
);
|
|
737
739
|
if (!canConnect) return;
|
|
738
740
|
this._clearTimeout(this._timeouts.reconnect);
|
|
739
741
|
this._updateState((oldState) => {
|
|
740
|
-
oldState.connection.state =
|
|
742
|
+
oldState.connection.state = ConnectionState.Connecting;
|
|
741
743
|
return oldState;
|
|
742
744
|
});
|
|
743
745
|
yield this._storageStore.initialize();
|
|
@@ -775,7 +777,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
775
777
|
disconnect() {
|
|
776
778
|
return __async(this, null, function* () {
|
|
777
779
|
if (!this._state.webSocket) return;
|
|
778
|
-
const canDisconnect = [
|
|
780
|
+
const canDisconnect = [ConnectionState.Connecting, ConnectionState.Open].some(
|
|
779
781
|
(state) => this._state.connection.state === state
|
|
780
782
|
);
|
|
781
783
|
if (!canDisconnect) return;
|
|
@@ -784,7 +786,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
784
786
|
this._clearTimeout(this._timeouts.reconnect);
|
|
785
787
|
this._updateState((oldState) => {
|
|
786
788
|
oldState.authorization.token = null;
|
|
787
|
-
oldState.connection.state =
|
|
789
|
+
oldState.connection.state = ConnectionState.Closed;
|
|
788
790
|
oldState.webSocket = null;
|
|
789
791
|
return oldState;
|
|
790
792
|
});
|
|
@@ -823,7 +825,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
823
825
|
_attachWindowListeners() {
|
|
824
826
|
if (typeof window === "undefined") return;
|
|
825
827
|
if (typeof document === "undefined") return;
|
|
826
|
-
this._windowListeners = {
|
|
828
|
+
this._windowListeners = this._windowListeners || {
|
|
827
829
|
onNavigatorOffline: this._onNavigatorOffline.bind(this),
|
|
828
830
|
onNavigatorOnline: this._onNavigatorOnline.bind(this),
|
|
829
831
|
onVisibilityChange: this._onVisibilityChange.bind(this)
|
|
@@ -834,7 +836,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
834
836
|
}
|
|
835
837
|
_attachWsListeners() {
|
|
836
838
|
if (!this._state.webSocket) return;
|
|
837
|
-
this._wsListeners = {
|
|
839
|
+
this._wsListeners = this._wsListeners || {
|
|
838
840
|
onClose: this._onClose.bind(this),
|
|
839
841
|
onError: this._onError.bind(this),
|
|
840
842
|
onMessage: this._onMessage.bind(this),
|
|
@@ -1154,7 +1156,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1154
1156
|
const authError = error instanceof Error ? error : new Error("Authorization failed");
|
|
1155
1157
|
this._listeners.onAuthorizationFail(authError);
|
|
1156
1158
|
this._updateState((oldState) => {
|
|
1157
|
-
oldState.connection.state =
|
|
1159
|
+
oldState.connection.state = ConnectionState.Unavailable;
|
|
1158
1160
|
return oldState;
|
|
1159
1161
|
});
|
|
1160
1162
|
}
|
|
@@ -1178,7 +1180,15 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1178
1180
|
].some((okToRetryCode) => event.code === okToRetryCode);
|
|
1179
1181
|
this._updateState((oldState) => {
|
|
1180
1182
|
oldState.authorization.token = null;
|
|
1181
|
-
oldState.connection.state = shouldRetry ?
|
|
1183
|
+
oldState.connection.state = shouldRetry ? ConnectionState.Unavailable : (
|
|
1184
|
+
/**
|
|
1185
|
+
* !HACK
|
|
1186
|
+
* @description Assume these are not recoverable. We're just going to mark this
|
|
1187
|
+
* as Closed to avoid reconnect attempts.
|
|
1188
|
+
* @date April 28, 2025
|
|
1189
|
+
*/
|
|
1190
|
+
ConnectionState.Closed
|
|
1191
|
+
);
|
|
1182
1192
|
oldState.webSocket = null;
|
|
1183
1193
|
return oldState;
|
|
1184
1194
|
});
|
|
@@ -1246,10 +1256,10 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1246
1256
|
}
|
|
1247
1257
|
}
|
|
1248
1258
|
_onOpen() {
|
|
1249
|
-
if (this._state.connection.state !==
|
|
1259
|
+
if (this._state.connection.state !== ConnectionState.Connecting) return;
|
|
1250
1260
|
this._logDebug("WebSocket connected");
|
|
1251
1261
|
this._updateState((oldState) => {
|
|
1252
|
-
oldState.connection.state =
|
|
1262
|
+
oldState.connection.state = ConnectionState.Open;
|
|
1253
1263
|
return oldState;
|
|
1254
1264
|
});
|
|
1255
1265
|
this._clearInterval(this._intervals.heartbeat);
|
|
@@ -1257,16 +1267,16 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1257
1267
|
}
|
|
1258
1268
|
_onNavigatorOffline() {
|
|
1259
1269
|
return __async(this, null, function* () {
|
|
1260
|
-
if (this._state.connection.state ===
|
|
1270
|
+
if (this._state.connection.state === ConnectionState.Closed) return;
|
|
1261
1271
|
this._updateState((oldState) => {
|
|
1262
|
-
oldState.connection.state =
|
|
1272
|
+
oldState.connection.state = ConnectionState.Unavailable;
|
|
1263
1273
|
return oldState;
|
|
1264
1274
|
});
|
|
1265
1275
|
});
|
|
1266
1276
|
}
|
|
1267
1277
|
_onNavigatorOnline() {
|
|
1268
1278
|
return __async(this, null, function* () {
|
|
1269
|
-
if (this._state.connection.state !==
|
|
1279
|
+
if (this._state.connection.state !== ConnectionState.Unavailable) return;
|
|
1270
1280
|
yield this._reconnect();
|
|
1271
1281
|
});
|
|
1272
1282
|
}
|
|
@@ -1275,12 +1285,12 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1275
1285
|
if (typeof document === "undefined") return;
|
|
1276
1286
|
if (document.visibilityState !== "visible") return;
|
|
1277
1287
|
switch (this._state.connection.state) {
|
|
1278
|
-
case
|
|
1288
|
+
case ConnectionState.Open: {
|
|
1279
1289
|
this._clearInterval(this._intervals.heartbeat);
|
|
1280
1290
|
setInterval(this._heartbeat.bind(this), HEARTBEAT_INTERVAL_MS);
|
|
1281
1291
|
return;
|
|
1282
1292
|
}
|
|
1283
|
-
case
|
|
1293
|
+
case ConnectionState.Unavailable: {
|
|
1284
1294
|
yield this._reconnect();
|
|
1285
1295
|
return;
|
|
1286
1296
|
}
|
|
@@ -1308,8 +1318,8 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1308
1318
|
}
|
|
1309
1319
|
_reconnect() {
|
|
1310
1320
|
return __async(this, null, function* () {
|
|
1311
|
-
if (!this._state.webSocket) return;
|
|
1312
1321
|
this._closeWs();
|
|
1322
|
+
if (this._state.connection.state === ConnectionState.Closed) return;
|
|
1313
1323
|
const metadata = this._lastMetadata;
|
|
1314
1324
|
const params = typeof metadata === "undefined" ? [] : [{ metadata }];
|
|
1315
1325
|
yield this.connect(...params);
|
|
@@ -1371,6 +1381,7 @@ var PluvClient = class {
|
|
|
1371
1381
|
debug: options.debug,
|
|
1372
1382
|
initialPresence: options.initialPresence,
|
|
1373
1383
|
initialStorage: this._initialStorage,
|
|
1384
|
+
limits: this._limits,
|
|
1374
1385
|
metadata: this.metadata,
|
|
1375
1386
|
onAuthorizationFail: options.onAuthorizationFail,
|
|
1376
1387
|
presence: this._presence,
|
|
@@ -1405,11 +1416,23 @@ var PluvClient = class {
|
|
|
1405
1416
|
this._rooms.delete(toLeave.id);
|
|
1406
1417
|
this._logDebug(`Left and deleted room: ${room}`);
|
|
1407
1418
|
});
|
|
1408
|
-
const {
|
|
1419
|
+
const {
|
|
1420
|
+
authEndpoint,
|
|
1421
|
+
debug = false,
|
|
1422
|
+
initialStorage,
|
|
1423
|
+
limits,
|
|
1424
|
+
metadata,
|
|
1425
|
+
presence,
|
|
1426
|
+
publicKey,
|
|
1427
|
+
wsEndpoint
|
|
1428
|
+
} = options;
|
|
1409
1429
|
this.metadata = metadata;
|
|
1410
1430
|
this._authEndpoint = authEndpoint;
|
|
1411
1431
|
this._debug = debug;
|
|
1412
1432
|
this._initialStorage = initialStorage;
|
|
1433
|
+
this._limits = __spreadValues({
|
|
1434
|
+
presenceMaxSize: MAX_PRESENCE_SIZE_BYTES
|
|
1435
|
+
}, limits);
|
|
1413
1436
|
this._presence = presence;
|
|
1414
1437
|
this._wsEndpoint = wsEndpoint;
|
|
1415
1438
|
if (!!publicKey) this._publicKey = publicKey;
|
|
@@ -1447,7 +1470,7 @@ var infer = (callback) => callback;
|
|
|
1447
1470
|
// src/MockedRoom.ts
|
|
1448
1471
|
var MockedRoom = class extends AbstractRoom {
|
|
1449
1472
|
constructor(room, options) {
|
|
1450
|
-
const { events, initialPresence, initialStorage, presence, router } = options;
|
|
1473
|
+
const { events, initialPresence, initialStorage, limits, presence, router } = options;
|
|
1451
1474
|
super(room);
|
|
1452
1475
|
this._crdtNotifier = new CrdtNotifier();
|
|
1453
1476
|
this._eventNotifier = new EventNotifier();
|
|
@@ -1461,7 +1484,7 @@ var MockedRoom = class extends AbstractRoom {
|
|
|
1461
1484
|
attempts: 0,
|
|
1462
1485
|
count: 1,
|
|
1463
1486
|
id: null,
|
|
1464
|
-
state:
|
|
1487
|
+
state: ConnectionState.Untouched
|
|
1465
1488
|
},
|
|
1466
1489
|
webSocket: null
|
|
1467
1490
|
};
|
|
@@ -1472,7 +1495,7 @@ var MockedRoom = class extends AbstractRoom {
|
|
|
1472
1495
|
this.broadcast = new Proxy(
|
|
1473
1496
|
(event, data) => __async(this, null, function* () {
|
|
1474
1497
|
if (!this._state.webSocket) return;
|
|
1475
|
-
if (this._state.connection.state !==
|
|
1498
|
+
if (this._state.connection.state !== ConnectionState.Open) return;
|
|
1476
1499
|
const type = event.toString();
|
|
1477
1500
|
const procedure = this._router._defs.events[type];
|
|
1478
1501
|
if (!(procedure == null ? void 0 : procedure.config.broadcast)) {
|
|
@@ -1578,11 +1601,11 @@ var MockedRoom = class extends AbstractRoom {
|
|
|
1578
1601
|
if (!!myself) this._stateNotifier.subjects["myself"].next(myself);
|
|
1579
1602
|
};
|
|
1580
1603
|
this._events = events;
|
|
1604
|
+
this._limits = __spreadValues({
|
|
1605
|
+
presenceMaxSize: MAX_PRESENCE_SIZE_BYTES
|
|
1606
|
+
}, limits);
|
|
1581
1607
|
this._router = router != null ? router : new PluvRouter({});
|
|
1582
|
-
this._usersManager = new UsersManager({
|
|
1583
|
-
initialPresence,
|
|
1584
|
-
presence
|
|
1585
|
-
});
|
|
1608
|
+
this._usersManager = new UsersManager({ initialPresence, limits: this._limits, presence });
|
|
1586
1609
|
this._crdtManager = new CrdtManager({
|
|
1587
1610
|
initialStorage
|
|
1588
1611
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pluv/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.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.44.0",
|
|
22
|
+
"@pluv/types": "^0.44.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
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.44.0",
|
|
29
|
+
"eslint-config-pluv": "^0.44.0"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "tsup src/index.ts --format esm,cjs --dts",
|
package/src/CrdtManager.ts
CHANGED
|
@@ -93,7 +93,7 @@ export class CrdtManager<TStorage extends Record<string, CrdtType<any, any>>> {
|
|
|
93
93
|
origin?: string,
|
|
94
94
|
): AbstractCrdtDoc<TStorage> {
|
|
95
95
|
doc.transact(() => {
|
|
96
|
-
doc.batchApplyEncodedState(
|
|
96
|
+
doc.batchApplyEncodedState({ origin, updates });
|
|
97
97
|
}, origin);
|
|
98
98
|
|
|
99
99
|
return doc;
|
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
|
|
|
@@ -358,7 +363,7 @@ export class PluvRoom<
|
|
|
358
363
|
|
|
359
364
|
this._setMetadata(metadata);
|
|
360
365
|
|
|
361
|
-
const canConnect = [ConnectionState.Closed, ConnectionState.Untouched].some(
|
|
366
|
+
const canConnect = [ConnectionState.Closed, ConnectionState.Unavailable, ConnectionState.Untouched].some(
|
|
362
367
|
(state) => this._state.connection.state === state,
|
|
363
368
|
);
|
|
364
369
|
|
|
@@ -610,7 +615,7 @@ export class PluvRoom<
|
|
|
610
615
|
if (typeof window === "undefined") return;
|
|
611
616
|
if (typeof document === "undefined") return;
|
|
612
617
|
|
|
613
|
-
this._windowListeners = {
|
|
618
|
+
this._windowListeners = this._windowListeners || {
|
|
614
619
|
onNavigatorOffline: this._onNavigatorOffline.bind(this),
|
|
615
620
|
onNavigatorOnline: this._onNavigatorOnline.bind(this),
|
|
616
621
|
onVisibilityChange: this._onVisibilityChange.bind(this),
|
|
@@ -624,7 +629,7 @@ export class PluvRoom<
|
|
|
624
629
|
private _attachWsListeners(): void {
|
|
625
630
|
if (!this._state.webSocket) return;
|
|
626
631
|
|
|
627
|
-
this._wsListeners = {
|
|
632
|
+
this._wsListeners = this._wsListeners || {
|
|
628
633
|
onClose: this._onClose.bind(this),
|
|
629
634
|
onError: this._onError.bind(this),
|
|
630
635
|
onMessage: this._onMessage.bind(this),
|
|
@@ -1147,7 +1152,15 @@ export class PluvRoom<
|
|
|
1147
1152
|
|
|
1148
1153
|
this._updateState((oldState) => {
|
|
1149
1154
|
oldState.authorization.token = null;
|
|
1150
|
-
oldState.connection.state = shouldRetry
|
|
1155
|
+
oldState.connection.state = shouldRetry
|
|
1156
|
+
? ConnectionState.Unavailable
|
|
1157
|
+
: /**
|
|
1158
|
+
* !HACK
|
|
1159
|
+
* @description Assume these are not recoverable. We're just going to mark this
|
|
1160
|
+
* as Closed to avoid reconnect attempts.
|
|
1161
|
+
* @date April 28, 2025
|
|
1162
|
+
*/
|
|
1163
|
+
ConnectionState.Closed;
|
|
1151
1164
|
oldState.webSocket = null;
|
|
1152
1165
|
|
|
1153
1166
|
return oldState;
|
|
@@ -1309,10 +1322,16 @@ export class PluvRoom<
|
|
|
1309
1322
|
}
|
|
1310
1323
|
|
|
1311
1324
|
private async _reconnect(): Promise<void> {
|
|
1312
|
-
if (!this._state.webSocket) return;
|
|
1313
|
-
|
|
1314
1325
|
this._closeWs();
|
|
1315
1326
|
|
|
1327
|
+
/**
|
|
1328
|
+
* @description User has manually closed this connection. We're not going to retry, since
|
|
1329
|
+
* the user probably intends for this to stay closed. They will need to invoke connect
|
|
1330
|
+
* themselves.
|
|
1331
|
+
* @date April 28, 2025
|
|
1332
|
+
*/
|
|
1333
|
+
if (this._state.connection.state === ConnectionState.Closed) return;
|
|
1334
|
+
|
|
1316
1335
|
const metadata = this._lastMetadata;
|
|
1317
1336
|
const params = (typeof metadata === "undefined" ? [] : [{ metadata }]) as RoomConnectParams<TMetadata>;
|
|
1318
1337
|
|
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/enums.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
export
|
|
2
|
-
Closed
|
|
3
|
-
Connecting
|
|
4
|
-
Open
|
|
5
|
-
Unavailable
|
|
6
|
-
Untouched
|
|
7
|
-
}
|
|
1
|
+
export const ConnectionState = {
|
|
2
|
+
Closed: "Closed",
|
|
3
|
+
Connecting: "Connecting",
|
|
4
|
+
Open: "Open",
|
|
5
|
+
Unavailable: "Unavailable",
|
|
6
|
+
Untouched: "Untouched",
|
|
7
|
+
} as const;
|
|
8
|
+
|
|
9
|
+
export type ConnectionState = (typeof ConnectionState)[keyof typeof ConnectionState];
|
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
|
}
|