@pluv/client 0.11.1 → 0.12.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 +246 -0
- package/dist/index.d.mts +28 -5
- package/dist/index.d.ts +28 -5
- package/dist/index.js +141 -27
- package/dist/index.mjs +141 -27
- package/package.json +7 -7
- package/src/AbstractRoom.ts +28 -9
- package/src/AbstractStorageStore.ts +0 -2
- package/src/CrdtManager.ts +33 -10
- package/src/CrdtNotifier.ts +14 -2
- package/src/EventNotifier.ts +3 -3
- package/src/MockedRoom.ts +76 -19
- package/src/OtherNotifier.ts +3 -3
- package/src/PluvClient.ts +6 -6
- package/src/PluvRoom.ts +111 -22
- package/src/StateNotifier.ts +6 -6
- package/src/UsersManager.ts +4 -4
- package/src/createClient.ts +1 -1
- package/src/index.ts +4 -1
- package/src/types.ts +1 -1
package/src/MockedRoom.ts
CHANGED
|
@@ -31,14 +31,14 @@ import { UsersManager } from "./UsersManager";
|
|
|
31
31
|
|
|
32
32
|
export type MockedRoomEvents<TIO extends IOLike> = Partial<{
|
|
33
33
|
[P in keyof InferIOInput<TIO>]: (
|
|
34
|
-
data: Id<InferIOInput<TIO>[P]
|
|
34
|
+
data: Id<InferIOInput<TIO>[P]>,
|
|
35
35
|
) => Partial<InferIOOutput<TIO>>;
|
|
36
36
|
}>;
|
|
37
37
|
|
|
38
38
|
export type MockedRoomConfig<
|
|
39
39
|
TIO extends IOLike,
|
|
40
40
|
TPresence extends JsonObject = {},
|
|
41
|
-
TStorage extends Record<string, AbstractType<any>> = {}
|
|
41
|
+
TStorage extends Record<string, AbstractType<any>> = {},
|
|
42
42
|
> = { events?: MockedRoomEvents<TIO> } & Omit<
|
|
43
43
|
CrdtManagerOptions<TStorage>,
|
|
44
44
|
"encodedState"
|
|
@@ -48,7 +48,7 @@ export type MockedRoomConfig<
|
|
|
48
48
|
export class MockedRoom<
|
|
49
49
|
TIO extends IOLike,
|
|
50
50
|
TPresence extends JsonObject = {},
|
|
51
|
-
TStorage extends Record<string, AbstractType<any>> = {}
|
|
51
|
+
TStorage extends Record<string, AbstractType<any>> = {},
|
|
52
52
|
> extends AbstractRoom<TIO, TPresence, TStorage> {
|
|
53
53
|
private _crdtManager: CrdtManager<TStorage> | null = null;
|
|
54
54
|
private _crdtNotifier = new CrdtNotifier<TStorage>();
|
|
@@ -75,7 +75,7 @@ export class MockedRoom<
|
|
|
75
75
|
|
|
76
76
|
constructor(
|
|
77
77
|
room: string,
|
|
78
|
-
options: MockedRoomConfig<TIO, TPresence, TStorage
|
|
78
|
+
options: MockedRoomConfig<TIO, TPresence, TStorage>,
|
|
79
79
|
) {
|
|
80
80
|
const { events, initialPresence, initialStorage, presence } = options;
|
|
81
81
|
|
|
@@ -97,7 +97,7 @@ export class MockedRoom<
|
|
|
97
97
|
|
|
98
98
|
public broadcast<TEvent extends keyof InferIOInput<TIO>>(
|
|
99
99
|
event: TEvent,
|
|
100
|
-
data: Id<InferIOInput<TIO>[TEvent]
|
|
100
|
+
data: Id<InferIOInput<TIO>[TEvent]>,
|
|
101
101
|
): void {
|
|
102
102
|
if (!this._events) return;
|
|
103
103
|
|
|
@@ -117,9 +117,17 @@ export class MockedRoom<
|
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
public canRedo = (): boolean => {
|
|
121
|
+
return !!this._crdtManager?.doc.canRedo();
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
public canUndo = (): boolean => {
|
|
125
|
+
return !!this._crdtManager?.doc.canUndo();
|
|
126
|
+
};
|
|
127
|
+
|
|
120
128
|
public event = <TEvent extends keyof InferIOOutput<TIO>>(
|
|
121
129
|
event: TEvent,
|
|
122
|
-
callback: EventNotifierSubscriptionCallback<TIO, TEvent
|
|
130
|
+
callback: EventNotifierSubscriptionCallback<TIO, TEvent>,
|
|
123
131
|
): (() => void) => {
|
|
124
132
|
return this._eventNotifier.subscribe(event, callback);
|
|
125
133
|
};
|
|
@@ -127,7 +135,7 @@ export class MockedRoom<
|
|
|
127
135
|
public getConnection(): WebSocketConnection {
|
|
128
136
|
// Create a read-only clone of the connection state
|
|
129
137
|
return Object.freeze(
|
|
130
|
-
JSON.parse(JSON.stringify(this._state.connection))
|
|
138
|
+
JSON.parse(JSON.stringify(this._state.connection)),
|
|
131
139
|
);
|
|
132
140
|
}
|
|
133
141
|
|
|
@@ -140,7 +148,7 @@ export class MockedRoom<
|
|
|
140
148
|
};
|
|
141
149
|
|
|
142
150
|
public getOther = (
|
|
143
|
-
connectionId: string
|
|
151
|
+
connectionId: string,
|
|
144
152
|
): Id<UserInfo<TIO, TPresence>> | null => {
|
|
145
153
|
return this._usersManager.getOther(connectionId);
|
|
146
154
|
};
|
|
@@ -150,34 +158,72 @@ export class MockedRoom<
|
|
|
150
158
|
};
|
|
151
159
|
|
|
152
160
|
public getStorage = <TKey extends keyof TStorage>(
|
|
153
|
-
key: TKey
|
|
161
|
+
key: TKey,
|
|
154
162
|
): TStorage[TKey] | null => {
|
|
155
163
|
return this._crdtManager?.get(key) ?? null;
|
|
156
164
|
};
|
|
157
165
|
|
|
158
166
|
public other = (
|
|
159
167
|
connectionId: string,
|
|
160
|
-
callback: OtherNotifierSubscriptionCallback<TIO
|
|
168
|
+
callback: OtherNotifierSubscriptionCallback<TIO>,
|
|
161
169
|
): (() => void) => {
|
|
162
170
|
return this._otherNotifier.subscribe(connectionId, callback);
|
|
163
171
|
};
|
|
164
172
|
|
|
173
|
+
public redo = (): void => {
|
|
174
|
+
this._crdtManager?.doc.redo();
|
|
175
|
+
};
|
|
176
|
+
|
|
165
177
|
public storage = <TKey extends keyof TStorage>(
|
|
166
178
|
key: TKey,
|
|
167
|
-
fn: (value: InferYjsSharedTypeJson<TStorage[TKey]>) => void
|
|
179
|
+
fn: (value: InferYjsSharedTypeJson<TStorage[TKey]>) => void,
|
|
168
180
|
): (() => void) => {
|
|
169
181
|
return this._crdtNotifier.subscribe(key, fn);
|
|
170
182
|
};
|
|
171
183
|
|
|
184
|
+
public storageRoot = (
|
|
185
|
+
fn: (value: {
|
|
186
|
+
[P in keyof TStorage]: InferYjsSharedTypeJson<TStorage[P]>;
|
|
187
|
+
}) => void,
|
|
188
|
+
): (() => void) => {
|
|
189
|
+
return this._crdtNotifier.subcribeRoot(fn);
|
|
190
|
+
};
|
|
191
|
+
|
|
172
192
|
public subscribe = <
|
|
173
|
-
TSubject extends keyof StateNotifierSubjects<TIO, TPresence
|
|
193
|
+
TSubject extends keyof StateNotifierSubjects<TIO, TPresence>,
|
|
174
194
|
>(
|
|
175
195
|
name: TSubject,
|
|
176
|
-
callback: SubscriptionCallback<TIO, TPresence, TSubject
|
|
196
|
+
callback: SubscriptionCallback<TIO, TPresence, TSubject>,
|
|
177
197
|
): (() => void) => {
|
|
178
198
|
return this._stateNotifier.subscribe(name, callback);
|
|
179
199
|
};
|
|
180
200
|
|
|
201
|
+
public transact = (
|
|
202
|
+
fn: (storage: TStorage) => void,
|
|
203
|
+
origin?: string,
|
|
204
|
+
): void => {
|
|
205
|
+
const _origin = origin ?? this._state.connection.id;
|
|
206
|
+
const crdtManager = this._crdtManager;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* !HACK
|
|
210
|
+
* @description Don't transact anything if there is no origin, because
|
|
211
|
+
* that means the user isn't connected yet. This will mean events are
|
|
212
|
+
* lost unfortunately.
|
|
213
|
+
* @date September 23, 2023
|
|
214
|
+
*/
|
|
215
|
+
if (typeof _origin !== "string") return;
|
|
216
|
+
if (!crdtManager) return;
|
|
217
|
+
|
|
218
|
+
crdtManager.doc.transact(() => {
|
|
219
|
+
fn(crdtManager.doc.storage);
|
|
220
|
+
}, _origin);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
public undo = (): void => {
|
|
224
|
+
this._crdtManager?.doc.undo();
|
|
225
|
+
};
|
|
226
|
+
|
|
181
227
|
public updateMyPresence = (presence: Partial<TPresence>): void => {
|
|
182
228
|
this._usersManager.updateMyPresence(presence);
|
|
183
229
|
|
|
@@ -202,14 +248,25 @@ export class MockedRoom<
|
|
|
202
248
|
|
|
203
249
|
const sharedTypes = this._crdtManager.doc.getSharedTypes();
|
|
204
250
|
|
|
205
|
-
Object.entries(sharedTypes).
|
|
206
|
-
|
|
251
|
+
const storageRoot = Object.entries(sharedTypes).reduce(
|
|
252
|
+
(acc, [prop, sharedType]) => {
|
|
253
|
+
if (!this._crdtManager) return acc;
|
|
254
|
+
|
|
255
|
+
const serialized = sharedType.toJSON();
|
|
256
|
+
|
|
257
|
+
this._crdtNotifier.subject(prop).next(serialized);
|
|
207
258
|
|
|
208
|
-
|
|
259
|
+
return { ...acc, [prop]: serialized };
|
|
260
|
+
},
|
|
261
|
+
{} as {
|
|
262
|
+
[P in keyof TStorage]: InferYjsSharedTypeJson<
|
|
263
|
+
TStorage[P]
|
|
264
|
+
>;
|
|
265
|
+
},
|
|
266
|
+
);
|
|
209
267
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
268
|
+
this._crdtNotifier.rootSubject.next(storageRoot);
|
|
269
|
+
},
|
|
213
270
|
);
|
|
214
271
|
|
|
215
272
|
this._subscriptions.observeCrdt = unsubscribe;
|
package/src/OtherNotifier.ts
CHANGED
|
@@ -4,14 +4,14 @@ import { makeSubject, subscribe } from "wonka";
|
|
|
4
4
|
import { UserInfo } from "./types";
|
|
5
5
|
|
|
6
6
|
export type OtherNotifierSubscriptionCallback<TIO extends IOLike> = (
|
|
7
|
-
value: Id<UserInfo<TIO>> | null
|
|
7
|
+
value: Id<UserInfo<TIO>> | null,
|
|
8
8
|
) => void;
|
|
9
9
|
|
|
10
10
|
export class OtherNotifier<TIO extends IOLike> {
|
|
11
11
|
public subjects = new Map<string, Subject<Id<UserInfo<TIO>> | null>>();
|
|
12
12
|
|
|
13
13
|
public subject<TEvent extends string>(
|
|
14
|
-
name: TEvent
|
|
14
|
+
name: TEvent,
|
|
15
15
|
): Subject<Id<UserInfo<TIO>> | null> {
|
|
16
16
|
const subject = this.subjects.get(name);
|
|
17
17
|
|
|
@@ -26,7 +26,7 @@ export class OtherNotifier<TIO extends IOLike> {
|
|
|
26
26
|
|
|
27
27
|
public subscribe<TEvent extends string>(
|
|
28
28
|
name: TEvent,
|
|
29
|
-
callback: OtherNotifierSubscriptionCallback<TIO
|
|
29
|
+
callback: OtherNotifierSubscriptionCallback<TIO>,
|
|
30
30
|
): () => void {
|
|
31
31
|
const source = this.subject(name).source;
|
|
32
32
|
|
package/src/PluvClient.ts
CHANGED
|
@@ -30,10 +30,10 @@ export class PluvClient<TIO extends IOLike = IOLike> {
|
|
|
30
30
|
|
|
31
31
|
public createRoom = <
|
|
32
32
|
TPresence extends JsonObject = {},
|
|
33
|
-
TStorage extends Record<string, AbstractType<any>> = {}
|
|
33
|
+
TStorage extends Record<string, AbstractType<any>> = {},
|
|
34
34
|
>(
|
|
35
35
|
room: string,
|
|
36
|
-
options: PluvRoomOptions<TIO, TPresence, TStorage> = {}
|
|
36
|
+
options: PluvRoomOptions<TIO, TPresence, TStorage> = {},
|
|
37
37
|
): PluvRoom<TIO, TPresence, TStorage> => {
|
|
38
38
|
const oldRoom = this.getRoom<TPresence, TStorage>(room);
|
|
39
39
|
|
|
@@ -57,7 +57,7 @@ export class PluvClient<TIO extends IOLike = IOLike> {
|
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
public enter = async (
|
|
60
|
-
room: string | PluvRoom<TIO, any, any
|
|
60
|
+
room: string | PluvRoom<TIO, any, any>,
|
|
61
61
|
): Promise<PluvRoom<TIO, JsonObject, any>> => {
|
|
62
62
|
const toEnter = typeof room === "string" ? this.getRoom(room) : room;
|
|
63
63
|
|
|
@@ -76,9 +76,9 @@ export class PluvClient<TIO extends IOLike = IOLike> {
|
|
|
76
76
|
|
|
77
77
|
public getRoom = <
|
|
78
78
|
TPresence extends JsonObject = {},
|
|
79
|
-
TStorage extends Record<string, AbstractType<any>> = {}
|
|
79
|
+
TStorage extends Record<string, AbstractType<any>> = {},
|
|
80
80
|
>(
|
|
81
|
-
room: string
|
|
81
|
+
room: string,
|
|
82
82
|
): PluvRoom<TIO, TPresence, TStorage> | null => {
|
|
83
83
|
const found = this._rooms.get(room) as
|
|
84
84
|
| PluvRoom<TIO, TPresence, TStorage>
|
|
@@ -92,7 +92,7 @@ export class PluvClient<TIO extends IOLike = IOLike> {
|
|
|
92
92
|
};
|
|
93
93
|
|
|
94
94
|
public leave = async (
|
|
95
|
-
room: string | PluvRoom<TIO, any, any
|
|
95
|
+
room: string | PluvRoom<TIO, any, any>,
|
|
96
96
|
): Promise<void> => {
|
|
97
97
|
const toLeave = typeof room === "string" ? this.getRoom(room) : room;
|
|
98
98
|
|
package/src/PluvRoom.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { InferYjsSharedTypeJson } from "@pluv/crdt-yjs";
|
|
1
|
+
import { InferYjsSharedTypeJson, TrackOriginOptions } from "@pluv/crdt-yjs";
|
|
2
2
|
import type {
|
|
3
3
|
BaseIOEventRecord,
|
|
4
4
|
EventMessage,
|
|
@@ -44,7 +44,8 @@ const ADD_TO_STORAGE_STATE_DEBOUNCE_MS = 1_000;
|
|
|
44
44
|
const HEARTBEAT_INTERVAL_MS = 10_000;
|
|
45
45
|
const PONG_TIMEOUT_MS = 2_000;
|
|
46
46
|
const RECONNECT_TIMEOUT_MS = 30_000;
|
|
47
|
-
const
|
|
47
|
+
const ORIGIN_INITIALIZED = "$INITIALIZED";
|
|
48
|
+
const ORIGIN_STORAGE_UPDATED = "$STORAGE_UPDATED";
|
|
48
49
|
|
|
49
50
|
export const DEFAULT_PLUV_CLIENT_ADDON = <
|
|
50
51
|
TIO extends IOLike = IOLike,
|
|
@@ -143,7 +144,9 @@ export type RoomConfig<
|
|
|
143
144
|
TIO extends IOLike,
|
|
144
145
|
TPresence extends JsonObject = {},
|
|
145
146
|
TStorage extends Record<string, AbstractType<any>> = {},
|
|
146
|
-
> = RoomEndpoints<TIO> &
|
|
147
|
+
> = RoomEndpoints<TIO> &
|
|
148
|
+
PluvRoomOptions<TIO, TPresence, TStorage> &
|
|
149
|
+
TrackOriginOptions;
|
|
147
150
|
|
|
148
151
|
export class PluvRoom<
|
|
149
152
|
TIO extends IOLike,
|
|
@@ -152,6 +155,7 @@ export class PluvRoom<
|
|
|
152
155
|
> extends AbstractRoom<TIO, TPresence, TStorage> {
|
|
153
156
|
readonly _endpoints: RoomEndpoints<TIO>;
|
|
154
157
|
|
|
158
|
+
private _captureTimeout: number | null = null;
|
|
155
159
|
private _crdtManager: CrdtManager<TStorage>;
|
|
156
160
|
private _crdtNotifier = new CrdtNotifier<TStorage>();
|
|
157
161
|
private _debug: boolean | PluvRoomDebug<TIO>;
|
|
@@ -182,6 +186,7 @@ export class PluvRoom<
|
|
|
182
186
|
pong: null,
|
|
183
187
|
reconnect: null,
|
|
184
188
|
};
|
|
189
|
+
private _trackedOrigins: readonly string[] | null = null;
|
|
185
190
|
private _windowListeners: WindowListeners | null = null;
|
|
186
191
|
private _wsListeners: WebSocketListeners | null = null;
|
|
187
192
|
private _usersManager: UsersManager<TIO, TPresence>;
|
|
@@ -190,11 +195,13 @@ export class PluvRoom<
|
|
|
190
195
|
const {
|
|
191
196
|
addons = [],
|
|
192
197
|
authEndpoint,
|
|
198
|
+
captureTimeout,
|
|
193
199
|
debug = false,
|
|
194
200
|
initialPresence,
|
|
195
201
|
initialStorage,
|
|
196
202
|
onAuthorizationFail,
|
|
197
203
|
presence,
|
|
204
|
+
trackedOrigins,
|
|
198
205
|
wsEndpoint,
|
|
199
206
|
} = options;
|
|
200
207
|
|
|
@@ -230,7 +237,14 @@ export class PluvRoom<
|
|
|
230
237
|
presence,
|
|
231
238
|
});
|
|
232
239
|
|
|
233
|
-
this._crdtManager = new CrdtManager<TStorage>({
|
|
240
|
+
this._crdtManager = new CrdtManager<TStorage>({
|
|
241
|
+
captureTimeout,
|
|
242
|
+
initialStorage,
|
|
243
|
+
trackedOrigins,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
this._captureTimeout = captureTimeout ?? null;
|
|
247
|
+
this._trackedOrigins = trackedOrigins ?? null;
|
|
234
248
|
}
|
|
235
249
|
|
|
236
250
|
public get webSocket(): WebSocket | null {
|
|
@@ -249,6 +263,14 @@ export class PluvRoom<
|
|
|
249
263
|
this._sendMessage({ data, type });
|
|
250
264
|
}
|
|
251
265
|
|
|
266
|
+
public canRedo = (): boolean => {
|
|
267
|
+
return !!this._crdtManager.doc.canRedo();
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
public canUndo = (): boolean => {
|
|
271
|
+
return !!this._crdtManager.doc.canUndo();
|
|
272
|
+
};
|
|
273
|
+
|
|
252
274
|
public async connect(): Promise<void> {
|
|
253
275
|
if (!this._endpoints.wsEndpoint) {
|
|
254
276
|
throw new Error("Must provide an wsEndpoint.");
|
|
@@ -272,8 +294,6 @@ export class PluvRoom<
|
|
|
272
294
|
await this._storageStore.initialize();
|
|
273
295
|
await this._applyStorageStore();
|
|
274
296
|
|
|
275
|
-
console.log(this._getWsEndpoint(this.id));
|
|
276
|
-
|
|
277
297
|
const url = new URL(this._getWsEndpoint(this.id));
|
|
278
298
|
|
|
279
299
|
let authToken: string | null = null;
|
|
@@ -375,6 +395,10 @@ export class PluvRoom<
|
|
|
375
395
|
return this._otherNotifier.subscribe(connectionId, callback);
|
|
376
396
|
};
|
|
377
397
|
|
|
398
|
+
public redo = (): void => {
|
|
399
|
+
this._crdtManager.doc.redo();
|
|
400
|
+
};
|
|
401
|
+
|
|
378
402
|
public storage = <TKey extends keyof TStorage>(
|
|
379
403
|
key: TKey,
|
|
380
404
|
fn: (value: InferYjsSharedTypeJson<TStorage[TKey]>) => void,
|
|
@@ -382,6 +406,14 @@ export class PluvRoom<
|
|
|
382
406
|
return this._crdtNotifier.subscribe(key, fn);
|
|
383
407
|
};
|
|
384
408
|
|
|
409
|
+
public storageRoot = (
|
|
410
|
+
fn: (value: {
|
|
411
|
+
[P in keyof TStorage]: InferYjsSharedTypeJson<TStorage[P]>;
|
|
412
|
+
}) => void,
|
|
413
|
+
): (() => void) => {
|
|
414
|
+
return this._crdtNotifier.subcribeRoot(fn);
|
|
415
|
+
};
|
|
416
|
+
|
|
385
417
|
public subscribe = <
|
|
386
418
|
TSubject extends keyof StateNotifierSubjects<TIO, TPresence>,
|
|
387
419
|
>(
|
|
@@ -391,6 +423,30 @@ export class PluvRoom<
|
|
|
391
423
|
return this._stateNotifier.subscribe(name, callback);
|
|
392
424
|
};
|
|
393
425
|
|
|
426
|
+
public transact = (
|
|
427
|
+
fn: (storage: TStorage) => void,
|
|
428
|
+
origin?: string,
|
|
429
|
+
): void => {
|
|
430
|
+
const _origin = origin ?? this._state.connection.id;
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* !HACK
|
|
434
|
+
* @description Don't transact anything if there is no origin, because
|
|
435
|
+
* that means the user isn't connected yet. This will mean events are
|
|
436
|
+
* lost unfortunately.
|
|
437
|
+
* @date September 23, 2023
|
|
438
|
+
*/
|
|
439
|
+
if (typeof _origin !== "string") return;
|
|
440
|
+
|
|
441
|
+
this._crdtManager.doc.transact(() => {
|
|
442
|
+
fn(this._crdtManager.doc.storage);
|
|
443
|
+
}, _origin);
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
public undo = (): void => {
|
|
447
|
+
this._crdtManager.doc.undo();
|
|
448
|
+
};
|
|
449
|
+
|
|
394
450
|
public updateMyPresence = (presence: Partial<TPresence>): void => {
|
|
395
451
|
this._usersManager.updateMyPresence(presence);
|
|
396
452
|
|
|
@@ -414,12 +470,15 @@ export class PluvRoom<
|
|
|
414
470
|
|
|
415
471
|
private async _applyStorageStore(): Promise<void> {
|
|
416
472
|
const updates = await this._storageStore.getUpdates();
|
|
473
|
+
const trackedOrigins = this._getTrackedOrigins();
|
|
417
474
|
|
|
418
475
|
this._crdtManager.initialize({
|
|
476
|
+
captureTimeout: this._captureTimeout ?? undefined,
|
|
419
477
|
onInitialized: () => {
|
|
420
478
|
this._observeCrdt();
|
|
421
479
|
},
|
|
422
|
-
origin:
|
|
480
|
+
origin: ORIGIN_INITIALIZED,
|
|
481
|
+
trackedOrigins: trackedOrigins ?? undefined,
|
|
423
482
|
update: updates,
|
|
424
483
|
});
|
|
425
484
|
|
|
@@ -548,11 +607,20 @@ export class PluvRoom<
|
|
|
548
607
|
private _emitSharedTypes(): void {
|
|
549
608
|
const sharedTypes = this._crdtManager.doc.getSharedTypes();
|
|
550
609
|
|
|
551
|
-
Object.entries(sharedTypes).
|
|
552
|
-
|
|
610
|
+
const storageRoot = Object.entries(sharedTypes).reduce(
|
|
611
|
+
(acc, [prop, sharedType]) => {
|
|
612
|
+
const serialized = sharedType.toJSON();
|
|
553
613
|
|
|
554
|
-
|
|
555
|
-
|
|
614
|
+
this._crdtNotifier.subject(prop).next(serialized);
|
|
615
|
+
|
|
616
|
+
return { ...acc, [prop]: serialized };
|
|
617
|
+
},
|
|
618
|
+
{} as {
|
|
619
|
+
[P in keyof TStorage]: InferYjsSharedTypeJson<TStorage[P]>;
|
|
620
|
+
},
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
this._crdtNotifier.rootSubject.next(storageRoot);
|
|
556
624
|
}
|
|
557
625
|
|
|
558
626
|
private _flattenStorageStore = debounce(
|
|
@@ -630,6 +698,14 @@ export class PluvRoom<
|
|
|
630
698
|
}
|
|
631
699
|
}
|
|
632
700
|
|
|
701
|
+
private _getTrackedOrigins(): readonly string[] | null {
|
|
702
|
+
const connectionId = this._state.connection.id;
|
|
703
|
+
|
|
704
|
+
return !connectionId
|
|
705
|
+
? this._trackedOrigins
|
|
706
|
+
: [connectionId, ...(this._trackedOrigins ?? [])];
|
|
707
|
+
}
|
|
708
|
+
|
|
633
709
|
private _getWsEndpoint(room: string): string {
|
|
634
710
|
switch (typeof this._endpoints.wsEndpoint) {
|
|
635
711
|
case "undefined":
|
|
@@ -772,11 +848,15 @@ export class PluvRoom<
|
|
|
772
848
|
InferIOAuthorize<TIO>
|
|
773
849
|
>["$STORAGE_RECEIVED"];
|
|
774
850
|
|
|
851
|
+
const trackedOrigins = this._getTrackedOrigins();
|
|
852
|
+
|
|
775
853
|
this._crdtManager.initialize({
|
|
854
|
+
captureTimeout: this._captureTimeout ?? undefined,
|
|
776
855
|
onInitialized: async () => {
|
|
777
856
|
this._observeCrdt();
|
|
778
857
|
},
|
|
779
|
-
origin:
|
|
858
|
+
origin: ORIGIN_INITIALIZED,
|
|
859
|
+
trackedOrigins: trackedOrigins ?? undefined,
|
|
780
860
|
update: data.state,
|
|
781
861
|
});
|
|
782
862
|
|
|
@@ -790,7 +870,7 @@ export class PluvRoom<
|
|
|
790
870
|
|
|
791
871
|
this._sendMessage({
|
|
792
872
|
type: "$UPDATE_STORAGE",
|
|
793
|
-
data: { origin:
|
|
873
|
+
data: { origin: ORIGIN_INITIALIZED, update },
|
|
794
874
|
});
|
|
795
875
|
}
|
|
796
876
|
|
|
@@ -803,21 +883,30 @@ export class PluvRoom<
|
|
|
803
883
|
|
|
804
884
|
const data = message.data as BaseIOEventRecord<
|
|
805
885
|
InferIOAuthorize<TIO>
|
|
806
|
-
>[
|
|
886
|
+
>[typeof ORIGIN_STORAGE_UPDATED];
|
|
807
887
|
|
|
808
888
|
if (!this._crdtManager) return;
|
|
809
889
|
|
|
810
|
-
this._crdtManager.doc.applyUpdate(data.state,
|
|
890
|
+
this._crdtManager.doc.applyUpdate(data.state, ORIGIN_STORAGE_UPDATED);
|
|
811
891
|
|
|
812
892
|
const sharedTypes = this._crdtManager.doc.getSharedTypes();
|
|
813
893
|
|
|
814
|
-
Object.entries(sharedTypes).
|
|
815
|
-
|
|
894
|
+
const storageRoot = Object.entries(sharedTypes).reduce(
|
|
895
|
+
(acc, [prop, sharedType]) => {
|
|
896
|
+
if (!this._crdtManager) return acc;
|
|
816
897
|
|
|
817
|
-
|
|
898
|
+
const serialized = sharedType.toJSON();
|
|
818
899
|
|
|
819
|
-
|
|
820
|
-
|
|
900
|
+
this._crdtNotifier.subject(prop).next(serialized);
|
|
901
|
+
|
|
902
|
+
return { ...acc, [prop]: serialized };
|
|
903
|
+
},
|
|
904
|
+
{} as {
|
|
905
|
+
[P in keyof TStorage]: InferYjsSharedTypeJson<TStorage[P]>;
|
|
906
|
+
},
|
|
907
|
+
);
|
|
908
|
+
|
|
909
|
+
this._crdtNotifier.rootSubject.next(storageRoot);
|
|
821
910
|
}
|
|
822
911
|
|
|
823
912
|
private _handleUserJoinedMessage(message: IOEventMessage<TIO>): void {
|
|
@@ -880,13 +969,13 @@ export class PluvRoom<
|
|
|
880
969
|
this._addToStorageStore(update);
|
|
881
970
|
this._emitSharedTypes();
|
|
882
971
|
|
|
883
|
-
if (origin ===
|
|
972
|
+
if (origin === ORIGIN_INITIALIZED || origin === this) return;
|
|
884
973
|
|
|
885
974
|
if (!this._state.webSocket) return;
|
|
886
975
|
if (!this._state.connection.id) return;
|
|
887
976
|
if (this._state.webSocket.readyState !== WebSocket.OPEN) return;
|
|
888
977
|
|
|
889
|
-
if (origin ===
|
|
978
|
+
if (origin === ORIGIN_STORAGE_UPDATED) return;
|
|
890
979
|
|
|
891
980
|
this._sendMessage({
|
|
892
981
|
type: "$UPDATE_STORAGE",
|
package/src/StateNotifier.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { UserInfo, WebSocketState } from "./types";
|
|
|
5
5
|
|
|
6
6
|
export interface StateNotifierSubjects<
|
|
7
7
|
TIO extends IOLike,
|
|
8
|
-
TPresence extends JsonObject
|
|
8
|
+
TPresence extends JsonObject,
|
|
9
9
|
> {
|
|
10
10
|
connection: Subject<Id<WebSocketState<TIO>>>;
|
|
11
11
|
"my-presence": Subject<TPresence | null>;
|
|
@@ -16,7 +16,7 @@ export interface StateNotifierSubjects<
|
|
|
16
16
|
type InferSubjectValue<
|
|
17
17
|
TIO extends IOLike,
|
|
18
18
|
TPresence extends JsonObject,
|
|
19
|
-
TSubject extends keyof StateNotifierSubjects<TIO, TPresence
|
|
19
|
+
TSubject extends keyof StateNotifierSubjects<TIO, TPresence>,
|
|
20
20
|
> = StateNotifierSubjects<TIO, TPresence>[TSubject] extends Subject<
|
|
21
21
|
infer IValue
|
|
22
22
|
>
|
|
@@ -26,12 +26,12 @@ type InferSubjectValue<
|
|
|
26
26
|
export type SubscriptionCallback<
|
|
27
27
|
TIO extends IOLike,
|
|
28
28
|
TPresence extends JsonObject,
|
|
29
|
-
TSubject extends keyof StateNotifierSubjects<TIO, TPresence
|
|
29
|
+
TSubject extends keyof StateNotifierSubjects<TIO, TPresence>,
|
|
30
30
|
> = (value: InferSubjectValue<TIO, TPresence, TSubject>) => void;
|
|
31
31
|
|
|
32
32
|
export class StateNotifier<
|
|
33
33
|
TIO extends IOLike,
|
|
34
|
-
TPresence extends JsonObject = {}
|
|
34
|
+
TPresence extends JsonObject = {},
|
|
35
35
|
> {
|
|
36
36
|
public subjects: StateNotifierSubjects<TIO, TPresence> = {
|
|
37
37
|
connection: makeSubject<Id<WebSocketState<TIO>>>(),
|
|
@@ -41,10 +41,10 @@ export class StateNotifier<
|
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
public subscribe<
|
|
44
|
-
TSubject extends keyof StateNotifierSubjects<TIO, TPresence
|
|
44
|
+
TSubject extends keyof StateNotifierSubjects<TIO, TPresence>,
|
|
45
45
|
>(
|
|
46
46
|
name: TSubject,
|
|
47
|
-
callback: SubscriptionCallback<TIO, TPresence, TSubject
|
|
47
|
+
callback: SubscriptionCallback<TIO, TPresence, TSubject>,
|
|
48
48
|
): () => void {
|
|
49
49
|
const source = this.subjects[name]?.source as any;
|
|
50
50
|
|
package/src/UsersManager.ts
CHANGED
|
@@ -17,7 +17,7 @@ export type UsersManagerConfig<TPresence extends JsonObject = {}> = {
|
|
|
17
17
|
|
|
18
18
|
export class UsersManager<
|
|
19
19
|
TIO extends IOLike,
|
|
20
|
-
TPresence extends JsonObject = {}
|
|
20
|
+
TPresence extends JsonObject = {},
|
|
21
21
|
> {
|
|
22
22
|
private _initialPresence: TPresence;
|
|
23
23
|
/**
|
|
@@ -62,7 +62,7 @@ export class UsersManager<
|
|
|
62
62
|
|
|
63
63
|
public patchPresence(
|
|
64
64
|
connectionId: string,
|
|
65
|
-
patch: Partial<TPresence
|
|
65
|
+
patch: Partial<TPresence>,
|
|
66
66
|
): void {
|
|
67
67
|
if (this._myself?.connectionId === connectionId) {
|
|
68
68
|
const presence = {
|
|
@@ -100,7 +100,7 @@ export class UsersManager<
|
|
|
100
100
|
|
|
101
101
|
public setMyself(
|
|
102
102
|
connectionId: string,
|
|
103
|
-
user: Id<InferIOAuthorizeUser<InferIOAuthorize<TIO
|
|
103
|
+
user: Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>>,
|
|
104
104
|
): void {
|
|
105
105
|
this._myself = {
|
|
106
106
|
connectionId,
|
|
@@ -129,7 +129,7 @@ export class UsersManager<
|
|
|
129
129
|
public setUser(
|
|
130
130
|
connectionId: string,
|
|
131
131
|
user: Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>>,
|
|
132
|
-
presence?: TPresence
|
|
132
|
+
presence?: TPresence,
|
|
133
133
|
): void {
|
|
134
134
|
if (this._myself?.connectionId === connectionId) return;
|
|
135
135
|
|
package/src/createClient.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { PluvClientOptions } from "./PluvClient";
|
|
|
3
3
|
import { PluvClient } from "./PluvClient";
|
|
4
4
|
|
|
5
5
|
export const createClient = <TIO extends IOLike = IOLike>(
|
|
6
|
-
options: PluvClientOptions<TIO
|
|
6
|
+
options: PluvClientOptions<TIO>,
|
|
7
7
|
): PluvClient<TIO> => {
|
|
8
8
|
return new PluvClient<TIO>(options);
|
|
9
9
|
};
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED