@pluv/client 3.2.1 → 4.0.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 +14 -16
- package/CHANGELOG.md +28 -0
- package/dist/index.d.mts +286 -284
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1740 -1865
- package/dist/index.mjs.map +1 -0
- package/package.json +17 -13
- package/src/infer.ts +4 -2
- package/tsdown.config.ts +14 -0
- package/dist/index.d.ts +0 -354
- package/dist/index.js +0 -1944
package/dist/index.mjs
CHANGED
|
@@ -1,1915 +1,1790 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
7
|
-
var __typeError = (msg) => {
|
|
8
|
-
throw TypeError(msg);
|
|
9
|
-
};
|
|
10
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
11
|
-
var __spreadValues = (a, b) => {
|
|
12
|
-
for (var prop in b || (b = {}))
|
|
13
|
-
if (__hasOwnProp.call(b, prop))
|
|
14
|
-
__defNormalProp(a, prop, b[prop]);
|
|
15
|
-
if (__getOwnPropSymbols)
|
|
16
|
-
for (var prop of __getOwnPropSymbols(b)) {
|
|
17
|
-
if (__propIsEnum.call(b, prop))
|
|
18
|
-
__defNormalProp(a, prop, b[prop]);
|
|
19
|
-
}
|
|
20
|
-
return a;
|
|
21
|
-
};
|
|
22
|
-
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
23
|
-
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
24
|
-
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
25
|
-
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
26
|
-
var __async = (__this, __arguments, generator) => {
|
|
27
|
-
return new Promise((resolve, reject) => {
|
|
28
|
-
var fulfilled = (value) => {
|
|
29
|
-
try {
|
|
30
|
-
step(generator.next(value));
|
|
31
|
-
} catch (e) {
|
|
32
|
-
reject(e);
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
var rejected = (value) => {
|
|
36
|
-
try {
|
|
37
|
-
step(generator.throw(value));
|
|
38
|
-
} catch (e) {
|
|
39
|
-
reject(e);
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
43
|
-
step((generator = generator.apply(__this, __arguments)).next());
|
|
44
|
-
});
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
// src/index.ts
|
|
48
|
-
import { ConnectionState as ConnectionState3 } from "@pluv/types";
|
|
1
|
+
import { ConnectionState, ConnectionState as ConnectionState$1, StorageState } from "@pluv/types";
|
|
2
|
+
import { noop } from "@pluv/crdt";
|
|
3
|
+
import { makeSubject, subscribe } from "wonka";
|
|
49
4
|
|
|
50
|
-
|
|
5
|
+
//#region src/AbstractStorageStore.ts
|
|
51
6
|
var AbstractStorageStore = class {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
7
|
+
constructor(room) {
|
|
8
|
+
this.room = room;
|
|
9
|
+
}
|
|
55
10
|
};
|
|
56
11
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/constants.ts
|
|
14
|
+
const MAX_PRESENCE_SIZE_BYTES = 512;
|
|
15
|
+
const PLUV_PRESENCE_META_KEY = "__pluv";
|
|
60
16
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
};
|
|
93
|
-
}
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/PluvProcedure.ts
|
|
19
|
+
var PluvProcedure = class PluvProcedure {
|
|
20
|
+
get config() {
|
|
21
|
+
return {
|
|
22
|
+
broadcast: this._broadcast?.bind(this) ?? null,
|
|
23
|
+
input: this._input ?? null,
|
|
24
|
+
resolver: this._resolver()
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
constructor(config = {}) {
|
|
28
|
+
this._broadcast = null;
|
|
29
|
+
this._input = null;
|
|
30
|
+
const { broadcast, input } = config;
|
|
31
|
+
this._broadcast = broadcast ?? null;
|
|
32
|
+
this._input = input ?? null;
|
|
33
|
+
}
|
|
34
|
+
broadcast(resolver) {
|
|
35
|
+
const { broadcast } = this.config;
|
|
36
|
+
if (!!broadcast) throw new Error("Broadcast was already defined for this procedure");
|
|
37
|
+
return new PluvProcedure({
|
|
38
|
+
...this.config,
|
|
39
|
+
broadcast: resolver
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
input(input) {
|
|
43
|
+
return new PluvProcedure({ input });
|
|
44
|
+
}
|
|
45
|
+
_resolver() {
|
|
46
|
+
return (data, context) => this._broadcast?.(data, context);
|
|
47
|
+
}
|
|
94
48
|
};
|
|
95
49
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// src/CrdtManager.ts
|
|
100
|
-
import { noop } from "@pluv/crdt";
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/CrdtManager.ts
|
|
101
52
|
var CrdtManager = class {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
53
|
+
constructor(options) {
|
|
54
|
+
this.initialized = false;
|
|
55
|
+
const { initialStorage = noop.doc({}) } = options;
|
|
56
|
+
this._docFactory = initialStorage;
|
|
57
|
+
this.doc = this._docFactory.getEmpty();
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* !HACK
|
|
61
|
+
* @description If this is called before the doc is initialized. Omit the
|
|
62
|
+
* update. This unfortunately means updates can be dropped entirely
|
|
63
|
+
* @date June 13, 2023
|
|
64
|
+
*/
|
|
65
|
+
applyUpdate(params) {
|
|
66
|
+
const { origin, update } = params;
|
|
67
|
+
if (!this.initialized) return this;
|
|
68
|
+
const updates = typeof update === "string" ? [update] : update;
|
|
69
|
+
this._applyDocUpdates(this.doc, updates, origin);
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
destroy() {
|
|
73
|
+
this.initialized = false;
|
|
74
|
+
this.doc.destroy();
|
|
75
|
+
this.doc = this._docFactory.getEmpty();
|
|
76
|
+
}
|
|
77
|
+
get(key) {
|
|
78
|
+
return this.doc.get(key);
|
|
79
|
+
}
|
|
80
|
+
getInitialState() {
|
|
81
|
+
return this._docFactory.getInitialized().getEncodedState();
|
|
82
|
+
}
|
|
83
|
+
initialize(params) {
|
|
84
|
+
const { origin, update } = params;
|
|
85
|
+
/**
|
|
86
|
+
* !HACK
|
|
87
|
+
* @description If the doc was already initialized, we don't want to re-initialize
|
|
88
|
+
* the doc and overwrite what's already there. Skip the initialization and log a
|
|
89
|
+
* warning.
|
|
90
|
+
* @date May 7, 2025
|
|
91
|
+
*/
|
|
92
|
+
if (this.initialized) {
|
|
93
|
+
console.warn("Attempted to initialize storage multiple times");
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* !HACK
|
|
98
|
+
* @description If the doc is already populated, somehow we're attempting to overwrite
|
|
99
|
+
* a doc that was presumably initialized but not identified as initialized. Skip the
|
|
100
|
+
* initialization and log a warning.
|
|
101
|
+
* @date May 7, 2025
|
|
102
|
+
*/
|
|
103
|
+
if (!this.doc.isEmpty()) {
|
|
104
|
+
console.warn("Attempted to initialize over a non-empty storage");
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
const updates = typeof update === "string" ? [update] : update;
|
|
108
|
+
if (!updates.length) return this;
|
|
109
|
+
const initialized = this._docFactory.getInitialized();
|
|
110
|
+
const reference = initialized.get();
|
|
111
|
+
this.doc = this._applyDocUpdates(this.doc, updates, origin).rebuildStorage(reference);
|
|
112
|
+
this.initialized = true;
|
|
113
|
+
initialized.destroy();
|
|
114
|
+
return this;
|
|
115
|
+
}
|
|
116
|
+
resolveEncodedState(updates) {
|
|
117
|
+
return this._docFactory.resolveEncodedState(updates);
|
|
118
|
+
}
|
|
119
|
+
_applyDocUpdates(doc, updates, origin) {
|
|
120
|
+
doc.transact(() => {
|
|
121
|
+
doc.batchApplyEncodedState({
|
|
122
|
+
origin,
|
|
123
|
+
updates
|
|
124
|
+
});
|
|
125
|
+
}, origin);
|
|
126
|
+
return doc;
|
|
127
|
+
}
|
|
160
128
|
};
|
|
161
129
|
|
|
162
|
-
|
|
163
|
-
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region src/CrdtNotifier.ts
|
|
164
132
|
var CrdtNotifier = class {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return subscribe(callback)(subject.source).unsubscribe;
|
|
184
|
-
}
|
|
133
|
+
constructor() {
|
|
134
|
+
this.rootSubject = makeSubject();
|
|
135
|
+
this.subjects = /* @__PURE__ */ new Map();
|
|
136
|
+
}
|
|
137
|
+
subject(key) {
|
|
138
|
+
const subject = this.subjects.get(key);
|
|
139
|
+
if (subject) return subject;
|
|
140
|
+
const newSubject = makeSubject();
|
|
141
|
+
this.subjects.set(key, newSubject);
|
|
142
|
+
return newSubject;
|
|
143
|
+
}
|
|
144
|
+
subcribeRoot(callback) {
|
|
145
|
+
return subscribe(callback)(this.rootSubject.source).unsubscribe;
|
|
146
|
+
}
|
|
147
|
+
subscribe(key, callback) {
|
|
148
|
+
const subject = this.subject(key);
|
|
149
|
+
return subscribe(callback)(subject.source).unsubscribe;
|
|
150
|
+
}
|
|
185
151
|
};
|
|
186
152
|
|
|
187
|
-
|
|
188
|
-
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/EventNotifier.ts
|
|
189
155
|
var EventNotifier = class {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
156
|
+
constructor() {
|
|
157
|
+
this.subjects = /* @__PURE__ */ new Map();
|
|
158
|
+
}
|
|
159
|
+
subject(name) {
|
|
160
|
+
const subject = this.subjects.get(name);
|
|
161
|
+
if (subject) return subject;
|
|
162
|
+
const newSubject = makeSubject();
|
|
163
|
+
this.subjects.set(name, newSubject);
|
|
164
|
+
return newSubject;
|
|
165
|
+
}
|
|
166
|
+
subscribe(name, callback) {
|
|
167
|
+
const subject = this.subject(name);
|
|
168
|
+
return subscribe(callback)(subject.source).unsubscribe;
|
|
169
|
+
}
|
|
204
170
|
};
|
|
205
171
|
|
|
206
|
-
|
|
207
|
-
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/ListenerManager.ts
|
|
208
174
|
var ListenerManager = class {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
175
|
+
constructor() {
|
|
176
|
+
this.subjects = {
|
|
177
|
+
close: makeSubject(),
|
|
178
|
+
error: makeSubject(),
|
|
179
|
+
message: makeSubject(),
|
|
180
|
+
open: makeSubject()
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
subscribe(kind, callback) {
|
|
184
|
+
const source = this.subjects[kind]?.source;
|
|
185
|
+
if (!source) return () => void 0;
|
|
186
|
+
return subscribe(callback)(source).unsubscribe;
|
|
187
|
+
}
|
|
223
188
|
};
|
|
224
189
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (Object.keys(events).some((key) => key.startsWith("$"))) {
|
|
243
|
-
throw new Error('Procedures may not start with "$"');
|
|
244
|
-
}
|
|
245
|
-
return new _PluvRouter(events);
|
|
246
|
-
}
|
|
247
|
-
_isValidEventName(name) {
|
|
248
|
-
return /^[a-z_$][a-z0-9_$]*$/gi.test(name);
|
|
249
|
-
}
|
|
190
|
+
//#endregion
|
|
191
|
+
//#region src/PluvRouter.ts
|
|
192
|
+
var PluvRouter = class PluvRouter {
|
|
193
|
+
constructor(events) {
|
|
194
|
+
this._defs = { events: {} };
|
|
195
|
+
const invalidName = Object.keys(events).find((name) => !this._isValidEventName(name));
|
|
196
|
+
if (typeof invalidName === "string") throw new Error(`Invalid event name. Event names must be formatted as valid JavaScript variable names: "${invalidName}"`);
|
|
197
|
+
this._defs = { events };
|
|
198
|
+
}
|
|
199
|
+
static merge(...routers) {
|
|
200
|
+
const events = Object.assign(Object.create(null), ...routers.map((router) => router._defs.events));
|
|
201
|
+
if (Object.keys(events).some((key) => key.startsWith("$"))) throw new Error("Procedures may not start with \"$\"");
|
|
202
|
+
return new PluvRouter(events);
|
|
203
|
+
}
|
|
204
|
+
_isValidEventName(name) {
|
|
205
|
+
return /^[a-z_$][a-z0-9_$]*$/gi.test(name);
|
|
206
|
+
}
|
|
250
207
|
};
|
|
251
208
|
|
|
252
|
-
|
|
253
|
-
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region src/StateNotifier.ts
|
|
254
211
|
var StateNotifier = class {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
212
|
+
constructor() {
|
|
213
|
+
this.subjects = {
|
|
214
|
+
connection: makeSubject(),
|
|
215
|
+
"my-presence": makeSubject(),
|
|
216
|
+
myself: makeSubject(),
|
|
217
|
+
others: makeSubject(),
|
|
218
|
+
"storage-loaded": makeSubject()
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
subscribe(name, callback) {
|
|
222
|
+
const source = this.subjects[name]?.source;
|
|
223
|
+
if (!source) throw new Error(`Could not subscribe to source: ${name}`);
|
|
224
|
+
return subscribe(callback)(source).unsubscribe;
|
|
225
|
+
}
|
|
270
226
|
};
|
|
271
227
|
|
|
272
|
-
|
|
228
|
+
//#endregion
|
|
229
|
+
//#region src/StorageStore.ts
|
|
273
230
|
var StorageStore = class extends AbstractStorageStore {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
231
|
+
addUpdate() {
|
|
232
|
+
return Promise.resolve();
|
|
233
|
+
}
|
|
234
|
+
applyUpdates() {
|
|
235
|
+
return Promise.resolve();
|
|
236
|
+
}
|
|
237
|
+
destroy() {
|
|
238
|
+
return Promise.resolve();
|
|
239
|
+
}
|
|
240
|
+
flatten() {
|
|
241
|
+
return Promise.resolve();
|
|
242
|
+
}
|
|
243
|
+
getShouldFlatten() {
|
|
244
|
+
return Promise.resolve(false);
|
|
245
|
+
}
|
|
246
|
+
getUpdates() {
|
|
247
|
+
return Promise.resolve([]);
|
|
248
|
+
}
|
|
249
|
+
initialize() {
|
|
250
|
+
return Promise.resolve();
|
|
251
|
+
}
|
|
295
252
|
};
|
|
296
253
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
254
|
+
//#endregion
|
|
255
|
+
//#region src/utils/debounce.ts
|
|
256
|
+
const debounce = (fn, options) => {
|
|
257
|
+
const { wait, immediate = false } = options ?? {};
|
|
258
|
+
let prevResult;
|
|
259
|
+
let timeout = null;
|
|
260
|
+
return (...args) => {
|
|
261
|
+
if (typeof timeout === "number") clearTimeout(timeout);
|
|
262
|
+
timeout = setTimeout(() => {
|
|
263
|
+
timeout = null;
|
|
264
|
+
if (!immediate) prevResult = fn(args);
|
|
265
|
+
}, wait);
|
|
266
|
+
if (immediate && !timeout) {
|
|
267
|
+
fn(args);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
return prevResult;
|
|
271
|
+
};
|
|
314
272
|
};
|
|
315
273
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
274
|
+
//#endregion
|
|
275
|
+
//#region src/utils/pickBy.ts
|
|
276
|
+
const pickBy = (obj, by) => {
|
|
277
|
+
const result = {};
|
|
278
|
+
const keys = Object.keys(obj);
|
|
279
|
+
for (let i = 0; i < keys.length; ++i) {
|
|
280
|
+
const key = keys[i];
|
|
281
|
+
const value = obj[key];
|
|
282
|
+
if (by(value)) result[key] = value;
|
|
283
|
+
}
|
|
284
|
+
return result;
|
|
326
285
|
};
|
|
327
286
|
|
|
328
|
-
|
|
287
|
+
//#endregion
|
|
288
|
+
//#region src/UsersManager.ts
|
|
329
289
|
var UsersManager = class {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
290
|
+
constructor(config) {
|
|
291
|
+
this._idMap = {
|
|
292
|
+
fromConnectionId: /* @__PURE__ */ new Map(),
|
|
293
|
+
fromClientId: /* @__PURE__ */ new Map()
|
|
294
|
+
};
|
|
295
|
+
this._myself = null;
|
|
296
|
+
this._others = /* @__PURE__ */ new Map();
|
|
297
|
+
this._presence = null;
|
|
298
|
+
const { initialPresence, limits, presence = null } = config;
|
|
299
|
+
this.initialPresence = initialPresence;
|
|
300
|
+
this._limits = limits;
|
|
301
|
+
this._myPresence = initialPresence;
|
|
302
|
+
this._presence = presence;
|
|
303
|
+
}
|
|
304
|
+
get myPresence() {
|
|
305
|
+
return this._myPresence;
|
|
306
|
+
}
|
|
307
|
+
get myself() {
|
|
308
|
+
return this._myself;
|
|
309
|
+
}
|
|
310
|
+
addConnection(userInfo) {
|
|
311
|
+
/**
|
|
312
|
+
* @description Ensure that the presence is complete, so that it does not fail schema
|
|
313
|
+
* validation
|
|
314
|
+
* @date April 20, 2025
|
|
315
|
+
*/
|
|
316
|
+
const cleaned = pickBy(userInfo.presence ?? {}, (value) => typeof value !== "undefined");
|
|
317
|
+
const presence = {
|
|
318
|
+
...this.initialPresence,
|
|
319
|
+
...cleaned
|
|
320
|
+
};
|
|
321
|
+
const data = {
|
|
322
|
+
...userInfo,
|
|
323
|
+
presence
|
|
324
|
+
};
|
|
325
|
+
const myClientId = !!this._myself ? this.getClientId(this._myself) : null;
|
|
326
|
+
const clientId = this.getClientId(data);
|
|
327
|
+
const remaining = this._setConnectionId(userInfo.connectionId, clientId).size;
|
|
328
|
+
if (myClientId === clientId) return {
|
|
329
|
+
clientId,
|
|
330
|
+
data,
|
|
331
|
+
isMyself: true,
|
|
332
|
+
remaining
|
|
333
|
+
};
|
|
334
|
+
/**
|
|
335
|
+
* !HACK
|
|
336
|
+
* @description Don't override the other user, so that we ensure the user's presence isn't
|
|
337
|
+
* overwritten by whatever incoming connection.
|
|
338
|
+
* @date April 16, 2025
|
|
339
|
+
*/
|
|
340
|
+
if (!this._others.get(clientId)) this._others.set(clientId, data);
|
|
341
|
+
return {
|
|
342
|
+
clientId,
|
|
343
|
+
data,
|
|
344
|
+
isMyself: false,
|
|
345
|
+
remaining
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
clearConnections() {
|
|
349
|
+
this.removeMyself();
|
|
350
|
+
this._others.clear();
|
|
351
|
+
this._idMap.fromClientId.clear();
|
|
352
|
+
this._idMap.fromConnectionId.clear();
|
|
353
|
+
}
|
|
354
|
+
deleteConnection(connectionId) {
|
|
355
|
+
const clientId = this.getClientId(connectionId);
|
|
356
|
+
this._deleteConnectionId(connectionId);
|
|
357
|
+
if (!clientId) return null;
|
|
358
|
+
const userInfo = this._others.get(clientId) ?? null;
|
|
359
|
+
if (!userInfo) return null;
|
|
360
|
+
const remaining = this._getConnectionIds(clientId)?.size ?? 0;
|
|
361
|
+
const result = {
|
|
362
|
+
clientId,
|
|
363
|
+
remaining,
|
|
364
|
+
data: userInfo
|
|
365
|
+
};
|
|
366
|
+
/**
|
|
367
|
+
* @description A user may have multiple websocket connections open. If one connection is
|
|
368
|
+
* dropped, the user may still be connected via another websocket (e.g. on anther browser
|
|
369
|
+
* tab). In this case, we just want to remove the connection mapping for the connection
|
|
370
|
+
* that is dropped, but keep the remaining connections and the user.
|
|
371
|
+
* @date April 16, 2025
|
|
372
|
+
*/
|
|
373
|
+
if (!remaining) this._others.delete(clientId);
|
|
374
|
+
return result;
|
|
375
|
+
}
|
|
376
|
+
getClientId(input) {
|
|
377
|
+
if (typeof input !== "string") {
|
|
378
|
+
const userInfo = input;
|
|
379
|
+
const connectionId$1 = userInfo.connectionId;
|
|
380
|
+
return userInfo.user?.id ?? null ?? connectionId$1;
|
|
381
|
+
}
|
|
382
|
+
const connectionId = input;
|
|
383
|
+
if (this._myself?.connectionId === connectionId) return this.getClientId(this._myself);
|
|
384
|
+
return this._idMap.fromConnectionId.get(connectionId) ?? null;
|
|
385
|
+
}
|
|
386
|
+
getOther(connectionId) {
|
|
387
|
+
const clientId = this.getClientId(connectionId);
|
|
388
|
+
if (!clientId) return null;
|
|
389
|
+
return this._others.get(clientId) ?? null;
|
|
390
|
+
}
|
|
391
|
+
getOthers() {
|
|
392
|
+
return Array.from(this._others.values());
|
|
393
|
+
}
|
|
394
|
+
patchPresence(connectionId, patch) {
|
|
395
|
+
const clientId = this.getClientId(connectionId);
|
|
396
|
+
const myClientId = !!this._myself ? this.getClientId(this._myself) : null;
|
|
397
|
+
if (!clientId) return null;
|
|
398
|
+
if (myClientId === clientId) return this.updateMyPresence(patch);
|
|
399
|
+
const other = this._others.get(clientId);
|
|
400
|
+
if (!other) return null;
|
|
401
|
+
const cleanedPatch = pickBy(patch ?? {}, (value) => typeof value !== "undefined");
|
|
402
|
+
const cleanedPresence = pickBy(other.presence ?? {}, (value) => typeof value !== "undefined");
|
|
403
|
+
const presence = {
|
|
404
|
+
...this.initialPresence,
|
|
405
|
+
...cleanedPresence,
|
|
406
|
+
...cleanedPatch
|
|
407
|
+
};
|
|
408
|
+
const validated = this._presence ? this._presence.parse(presence) : presence;
|
|
409
|
+
/**
|
|
410
|
+
* !HACK
|
|
411
|
+
* @description We're patching internal metadata back into the presence field so it doesn't
|
|
412
|
+
* get stripped from validation
|
|
413
|
+
* @date May 13, 2025
|
|
414
|
+
*/
|
|
415
|
+
if (!!presence[PLUV_PRESENCE_META_KEY]) validated[PLUV_PRESENCE_META_KEY] = presence[PLUV_PRESENCE_META_KEY];
|
|
416
|
+
this._others.set(clientId, {
|
|
417
|
+
...other,
|
|
418
|
+
presence: validated
|
|
419
|
+
});
|
|
420
|
+
return validated;
|
|
421
|
+
}
|
|
422
|
+
removeMyself() {
|
|
423
|
+
if (!this._myself) return;
|
|
424
|
+
const connectionId = this._myself.connectionId;
|
|
425
|
+
this._deleteConnectionId(connectionId);
|
|
426
|
+
this._myself = null;
|
|
427
|
+
}
|
|
428
|
+
setMyself(userInfo) {
|
|
429
|
+
const connectionId = userInfo.connectionId;
|
|
430
|
+
const presence = userInfo.presence ?? this.initialPresence;
|
|
431
|
+
this._myself = {
|
|
432
|
+
...userInfo,
|
|
433
|
+
presence
|
|
434
|
+
};
|
|
435
|
+
this._myPresence = presence;
|
|
436
|
+
const clientId = this.getClientId(this._myself);
|
|
437
|
+
this._setConnectionId(connectionId, clientId);
|
|
438
|
+
}
|
|
439
|
+
setPresence(connectionId, presence) {
|
|
440
|
+
const clientId = this.getClientId(connectionId);
|
|
441
|
+
const myClientId = !!this._myself ? this.getClientId(this._myself) : null;
|
|
442
|
+
if (!clientId) return;
|
|
443
|
+
if (myClientId === clientId) {
|
|
444
|
+
if (!this._myself) return;
|
|
445
|
+
this._myself.presence = presence;
|
|
446
|
+
this._myPresence = presence;
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
const other = this._others.get(clientId);
|
|
450
|
+
if (!other) return;
|
|
451
|
+
this._others.set(clientId, {
|
|
452
|
+
...other,
|
|
453
|
+
presence
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* @description This method need not care about being connected. This is because a user should
|
|
458
|
+
* be able to view and update their own presence without being online
|
|
459
|
+
* @date April 20, 2025
|
|
460
|
+
*/
|
|
461
|
+
updateMyPresence(patch) {
|
|
462
|
+
const cleanedPatch = pickBy(patch, (value) => typeof value !== "undefined");
|
|
463
|
+
const cleanedPresence = pickBy(this._myPresence, (value) => typeof value !== "undefined");
|
|
464
|
+
const updated = {
|
|
465
|
+
...this.initialPresence,
|
|
466
|
+
...cleanedPresence,
|
|
467
|
+
...cleanedPatch
|
|
468
|
+
};
|
|
469
|
+
const bytes = new TextEncoder().encode(JSON.stringify(updated)).length;
|
|
470
|
+
if (!!this._limits.presenceMaxSize && bytes > this._limits.presenceMaxSize) throw new Error(`Large presence. Presence must be at most 512 bytes. Current size: ${bytes.toLocaleString()}`);
|
|
471
|
+
this._myPresence = updated;
|
|
472
|
+
if (!this._myself) return updated;
|
|
473
|
+
this._myself.presence = updated;
|
|
474
|
+
return updated;
|
|
475
|
+
}
|
|
476
|
+
_deleteConnectionId(connectionId) {
|
|
477
|
+
const clientId = this.getClientId(connectionId);
|
|
478
|
+
this._idMap.fromConnectionId.delete(connectionId);
|
|
479
|
+
if (!clientId) return;
|
|
480
|
+
const set = this._idMap.fromClientId.get(clientId) ?? null;
|
|
481
|
+
if (!set) return;
|
|
482
|
+
set.delete(connectionId);
|
|
483
|
+
if (!!set.size) return;
|
|
484
|
+
this._idMap.fromClientId.delete(clientId);
|
|
485
|
+
}
|
|
486
|
+
_getConnectionIds(clientId) {
|
|
487
|
+
return this._idMap.fromClientId.get(clientId) ?? null;
|
|
488
|
+
}
|
|
489
|
+
_setConnectionId(connectionId, clientId) {
|
|
490
|
+
const updated = (this._idMap.fromClientId.get(clientId) ?? /* @__PURE__ */ new Set()).add(connectionId);
|
|
491
|
+
this._idMap.fromConnectionId.set(connectionId, clientId);
|
|
492
|
+
this._idMap.fromClientId.set(clientId, updated);
|
|
493
|
+
return updated;
|
|
494
|
+
}
|
|
519
495
|
};
|
|
520
496
|
|
|
521
|
-
|
|
522
|
-
|
|
497
|
+
//#endregion
|
|
498
|
+
//#region src/UsersNotifier.ts
|
|
523
499
|
var UsersNotifier = class {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
({ others, event }) => callback(others, event)
|
|
553
|
-
)(this.others.source);
|
|
554
|
-
return subscription.unsubscribe;
|
|
555
|
-
}
|
|
500
|
+
constructor() {
|
|
501
|
+
this.others = makeSubject();
|
|
502
|
+
this._otherSubjects = /* @__PURE__ */ new Map();
|
|
503
|
+
}
|
|
504
|
+
clear() {
|
|
505
|
+
this._otherSubjects.forEach((subject) => {
|
|
506
|
+
subject.next(null);
|
|
507
|
+
});
|
|
508
|
+
this._otherSubjects.clear();
|
|
509
|
+
}
|
|
510
|
+
delete(clientId) {
|
|
511
|
+
this.other(clientId).next(null);
|
|
512
|
+
this._otherSubjects.delete(clientId);
|
|
513
|
+
}
|
|
514
|
+
other(clientId) {
|
|
515
|
+
const subject = this._otherSubjects.get(clientId);
|
|
516
|
+
if (subject) return subject;
|
|
517
|
+
const newSubject = makeSubject();
|
|
518
|
+
this._otherSubjects.set(clientId, newSubject);
|
|
519
|
+
return newSubject;
|
|
520
|
+
}
|
|
521
|
+
subscribeOther(clientId, callback) {
|
|
522
|
+
const source = this.other(clientId).source;
|
|
523
|
+
return subscribe(callback)(source).unsubscribe;
|
|
524
|
+
}
|
|
525
|
+
subscribeOthers(callback) {
|
|
526
|
+
return subscribe(({ others, event }) => callback(others, event))(this.others.source).unsubscribe;
|
|
527
|
+
}
|
|
556
528
|
};
|
|
557
529
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
});
|
|
570
|
-
var __storage;
|
|
530
|
+
//#endregion
|
|
531
|
+
//#region src/PluvRoom.ts
|
|
532
|
+
const ADD_TO_STORAGE_STATE_DEBOUNCE_MS = 1e3;
|
|
533
|
+
const HEARTBEAT_INTERVAL_MS = 1e4;
|
|
534
|
+
const PONG_TIMEOUT_MS = 2e3;
|
|
535
|
+
const RECONNECT_TIMEOUT_MS = 5e3;
|
|
536
|
+
const MIN_RECONNECT_TIMEOUT_MS = 1e3;
|
|
537
|
+
const MAX_RECONNECT_TIMEOUT_MS = 6e4;
|
|
538
|
+
const ORIGIN_INITIALIZED = "$initialized";
|
|
539
|
+
const ORIGIN_STORAGE_UPDATED = "$storageUpdated";
|
|
540
|
+
const DEFAULT_PLUV_CLIENT_ADDON = (input) => ({ storage: new StorageStore(input.room.id) });
|
|
571
541
|
var PluvRoom = class {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
}
|
|
1501
|
-
_sendMessage(data) {
|
|
1502
|
-
const webSocket = this._state.webSocket;
|
|
1503
|
-
if ((webSocket == null ? void 0 : webSocket.readyState) !== WebSocket.OPEN) return;
|
|
1504
|
-
const dataType = (() => {
|
|
1505
|
-
var _a, _b, _c;
|
|
1506
|
-
if (typeof data === "object") return (_a = data.type) != null ? _a : null;
|
|
1507
|
-
if (typeof data !== "string") return null;
|
|
1508
|
-
try {
|
|
1509
|
-
return (_c = (_b = JSON.parse(data)) == null ? void 0 : _b.type) != null ? _c : null;
|
|
1510
|
-
} catch (e) {
|
|
1511
|
-
return null;
|
|
1512
|
-
}
|
|
1513
|
-
})();
|
|
1514
|
-
const shouldLog = typeof this._debug === "boolean" ? this._debug : !dataType ? false : this._debug.input.some((value) => value === data.type);
|
|
1515
|
-
if (shouldLog) this._logDebug("WebSocket event sent: ", data.type, data);
|
|
1516
|
-
const message = typeof data === "string" ? data : JSON.stringify(data);
|
|
1517
|
-
webSocket.send(message);
|
|
1518
|
-
}
|
|
1519
|
-
_setMetadata(metadata) {
|
|
1520
|
-
const parsed = this.metadata ? this.metadata.parse(metadata) : metadata;
|
|
1521
|
-
this._lastMetadata = parsed;
|
|
1522
|
-
return parsed;
|
|
1523
|
-
}
|
|
1524
|
-
_updateState(updater) {
|
|
1525
|
-
let authorization;
|
|
1526
|
-
try {
|
|
1527
|
-
authorization = JSON.parse(JSON.stringify(this._state.authorization));
|
|
1528
|
-
} catch (e) {
|
|
1529
|
-
throw new Error("User is not JSON serializable");
|
|
1530
|
-
}
|
|
1531
|
-
const oldState = __spreadProps(__spreadValues({}, this._state), {
|
|
1532
|
-
authorization,
|
|
1533
|
-
connection: JSON.parse(JSON.stringify(this._state.connection)),
|
|
1534
|
-
storage: JSON.parse(JSON.stringify(this._state.storage)),
|
|
1535
|
-
webSocket: this._state.webSocket
|
|
1536
|
-
});
|
|
1537
|
-
const newState = updater(oldState);
|
|
1538
|
-
this._state = newState;
|
|
1539
|
-
this._logDebug("WebSocket new state: ", newState);
|
|
1540
|
-
this._stateNotifier.subjects.connection.next(newState);
|
|
1541
|
-
return newState;
|
|
1542
|
-
}
|
|
542
|
+
constructor(room, options) {
|
|
543
|
+
this._crdtNotifier = new CrdtNotifier();
|
|
544
|
+
this._eventNotifier = new EventNotifier();
|
|
545
|
+
this._intervals = { heartbeat: null };
|
|
546
|
+
this._listenerManager = new ListenerManager();
|
|
547
|
+
this._publicKey = null;
|
|
548
|
+
this._stateNotifier = new StateNotifier();
|
|
549
|
+
this._subscriptions = { observeCrdt: null };
|
|
550
|
+
this._timeouts = {
|
|
551
|
+
pong: null,
|
|
552
|
+
reconnect: null
|
|
553
|
+
};
|
|
554
|
+
this._usersNotifier = new UsersNotifier();
|
|
555
|
+
this._lastMetadata = null;
|
|
556
|
+
this._state = {
|
|
557
|
+
authorization: {
|
|
558
|
+
token: null,
|
|
559
|
+
user: null
|
|
560
|
+
},
|
|
561
|
+
connection: {
|
|
562
|
+
attempts: 0,
|
|
563
|
+
id: null,
|
|
564
|
+
state: ConnectionState$1.Untouched
|
|
565
|
+
},
|
|
566
|
+
storage: { state: StorageState.Unavailable },
|
|
567
|
+
webSocket: null
|
|
568
|
+
};
|
|
569
|
+
this._windowListeners = null;
|
|
570
|
+
this._wsListeners = null;
|
|
571
|
+
this.broadcast = new Proxy(async (event, data) => {
|
|
572
|
+
if (!this._state.webSocket) return;
|
|
573
|
+
if (this._state.connection.state !== ConnectionState$1.Open) return;
|
|
574
|
+
const type = event.toString();
|
|
575
|
+
const procedure = this._router._defs.events[type];
|
|
576
|
+
if (!procedure?.config.broadcast) {
|
|
577
|
+
this._sendMessage({
|
|
578
|
+
data,
|
|
579
|
+
type
|
|
580
|
+
});
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const myself = this._usersManager.myself;
|
|
584
|
+
if (!myself) return;
|
|
585
|
+
const parsed = procedure.config.input ? procedure.config.input.parse(data) : data;
|
|
586
|
+
const context = {
|
|
587
|
+
doc: this._crdtManager.doc,
|
|
588
|
+
others: this._usersManager.getOthers(),
|
|
589
|
+
room: this.id,
|
|
590
|
+
user: myself
|
|
591
|
+
};
|
|
592
|
+
const output = await procedure.config.broadcast(parsed, context);
|
|
593
|
+
Object.entries(output).forEach(([_type, _data]) => {
|
|
594
|
+
this._sendMessage({
|
|
595
|
+
data: _data,
|
|
596
|
+
type: _type
|
|
597
|
+
});
|
|
598
|
+
});
|
|
599
|
+
}, { get(fn, prop) {
|
|
600
|
+
return async (data) => {
|
|
601
|
+
return await fn(prop, data);
|
|
602
|
+
};
|
|
603
|
+
} });
|
|
604
|
+
this.canRedo = () => {
|
|
605
|
+
return !!this._crdtManager.doc.canRedo();
|
|
606
|
+
};
|
|
607
|
+
this.canUndo = () => {
|
|
608
|
+
return !!this._crdtManager.doc.canUndo();
|
|
609
|
+
};
|
|
610
|
+
this.getConnection = () => {
|
|
611
|
+
return Object.freeze(JSON.parse(JSON.stringify(this._state.connection)));
|
|
612
|
+
};
|
|
613
|
+
this.getDoc = () => {
|
|
614
|
+
return this._crdtManager.doc;
|
|
615
|
+
};
|
|
616
|
+
this.getMyPresence = () => {
|
|
617
|
+
return this._usersManager.myPresence;
|
|
618
|
+
};
|
|
619
|
+
this.getMyself = () => {
|
|
620
|
+
return this._usersManager.myself;
|
|
621
|
+
};
|
|
622
|
+
this.getOther = (connectionId) => {
|
|
623
|
+
return this._usersManager.getOther(connectionId);
|
|
624
|
+
};
|
|
625
|
+
this.getOthers = () => {
|
|
626
|
+
return this._usersManager.getOthers();
|
|
627
|
+
};
|
|
628
|
+
this.getStorage = (type) => {
|
|
629
|
+
const sharedType = this._crdtManager.get(type);
|
|
630
|
+
if (typeof sharedType === "undefined") return null;
|
|
631
|
+
return sharedType;
|
|
632
|
+
};
|
|
633
|
+
this.getStorageLoaded = () => {
|
|
634
|
+
return this._state.storage.state === StorageState.Offline || this._state.storage.state === StorageState.Synced;
|
|
635
|
+
};
|
|
636
|
+
this.redo = () => {
|
|
637
|
+
this._crdtManager.doc.redo();
|
|
638
|
+
};
|
|
639
|
+
this.subscribe = new Proxy((name, callback) => this._stateNotifier.subscribe(name, callback), { get: (fn, prop) => {
|
|
640
|
+
if (prop === "connection") return (callback) => {
|
|
641
|
+
return fn("connection", callback);
|
|
642
|
+
};
|
|
643
|
+
if (prop === "myPresence") return (callback) => {
|
|
644
|
+
return fn("my-presence", callback);
|
|
645
|
+
};
|
|
646
|
+
if (prop === "myself") return (callback) => {
|
|
647
|
+
return fn("myself", callback);
|
|
648
|
+
};
|
|
649
|
+
if (prop === "others") return (callback) => {
|
|
650
|
+
return this._usersNotifier.subscribeOthers(callback);
|
|
651
|
+
};
|
|
652
|
+
if (prop === "storageLoaded") return (callback) => {
|
|
653
|
+
return fn("storage-loaded", callback);
|
|
654
|
+
};
|
|
655
|
+
if (prop === "event") return this._event;
|
|
656
|
+
if (prop === "other") return this._other;
|
|
657
|
+
if (prop === "storage") return this.#_storage;
|
|
658
|
+
} });
|
|
659
|
+
this.transact = (fn, origin) => {
|
|
660
|
+
const _origin = origin ?? this._state.connection.id;
|
|
661
|
+
/**
|
|
662
|
+
* !HACK
|
|
663
|
+
* @description Don't transact anything if there is no origin, because
|
|
664
|
+
* that means the user isn't connected yet. This will mean events are
|
|
665
|
+
* lost unfortunately.
|
|
666
|
+
* @date September 23, 2023
|
|
667
|
+
*/
|
|
668
|
+
if (typeof _origin !== "string") return;
|
|
669
|
+
this._crdtManager.doc.transact(() => {
|
|
670
|
+
fn(this._crdtManager.doc.get());
|
|
671
|
+
}, _origin);
|
|
672
|
+
};
|
|
673
|
+
this.undo = () => {
|
|
674
|
+
this._crdtManager.doc.undo();
|
|
675
|
+
};
|
|
676
|
+
this.updateMyPresence = (presence$1) => {
|
|
677
|
+
const newPresence = typeof presence$1 === "function" ? presence$1(this.getMyPresence()) : presence$1;
|
|
678
|
+
this._usersManager.updateMyPresence(newPresence);
|
|
679
|
+
const myPresence = this._usersManager.myPresence;
|
|
680
|
+
const myself = this._usersManager.myself ?? null;
|
|
681
|
+
this._stateNotifier.subjects["my-presence"].next(myPresence);
|
|
682
|
+
if (!!myself) this._stateNotifier.subjects.myself.next(myself);
|
|
683
|
+
this.broadcast("$updatePresence", { presence: newPresence });
|
|
684
|
+
};
|
|
685
|
+
this._event = new Proxy((event, callback) => this._eventNotifier.subscribe(event, callback), { get(fn, prop) {
|
|
686
|
+
return (callback) => fn(prop, callback);
|
|
687
|
+
} });
|
|
688
|
+
this._flattenStorageStore = debounce(async () => {
|
|
689
|
+
if (!await this._storageStore.getShouldFlatten()) return;
|
|
690
|
+
const updates = await this._storageStore.getUpdates();
|
|
691
|
+
this._crdtManager.applyUpdate({
|
|
692
|
+
update: updates,
|
|
693
|
+
origin: this
|
|
694
|
+
});
|
|
695
|
+
const encodedState = this._crdtManager.doc.getEncodedState();
|
|
696
|
+
await this._storageStore.flatten(encodedState);
|
|
697
|
+
}, { wait: ADD_TO_STORAGE_STATE_DEBOUNCE_MS });
|
|
698
|
+
this._getAddon = (addons$1) => {
|
|
699
|
+
return addons$1.reduce((acc, addon$1) => () => ({
|
|
700
|
+
...acc({ room: this }),
|
|
701
|
+
...addon$1({ room: this })
|
|
702
|
+
}), DEFAULT_PLUV_CLIENT_ADDON);
|
|
703
|
+
};
|
|
704
|
+
this._other = (connectionId, callback) => {
|
|
705
|
+
const clientId = this._usersManager.getClientId(connectionId);
|
|
706
|
+
if (!clientId) return () => void 0;
|
|
707
|
+
return this._usersNotifier.subscribeOther(clientId, callback);
|
|
708
|
+
};
|
|
709
|
+
const { addons = [], authEndpoint, debug = false, initialPresence, initialStorage, limits, metadata, onAuthorizationFail, presence, publicKey, reconnectTimeoutMs = RECONNECT_TIMEOUT_MS, router, wsEndpoint } = options;
|
|
710
|
+
this.id = room;
|
|
711
|
+
this.metadata = metadata;
|
|
712
|
+
const addon = this._getAddon(addons);
|
|
713
|
+
const { storage } = {
|
|
714
|
+
...DEFAULT_PLUV_CLIENT_ADDON({ room: this }),
|
|
715
|
+
...addon({ room: this })
|
|
716
|
+
};
|
|
717
|
+
this._storageStore = storage;
|
|
718
|
+
this._debug = debug;
|
|
719
|
+
this._endpoints = {
|
|
720
|
+
authEndpoint,
|
|
721
|
+
wsEndpoint
|
|
722
|
+
};
|
|
723
|
+
this._limits = limits;
|
|
724
|
+
this._reconnectTimeoutMs = reconnectTimeoutMs;
|
|
725
|
+
if (!!publicKey) this._publicKey = publicKey;
|
|
726
|
+
this._listeners = { onAuthorizationFail: (error) => {
|
|
727
|
+
onAuthorizationFail?.(error);
|
|
728
|
+
} };
|
|
729
|
+
this._router = router ?? new PluvRouter({});
|
|
730
|
+
this._usersManager = new UsersManager({
|
|
731
|
+
initialPresence,
|
|
732
|
+
limits: this._limits,
|
|
733
|
+
presence
|
|
734
|
+
});
|
|
735
|
+
this._crdtManager = new CrdtManager({ initialStorage });
|
|
736
|
+
}
|
|
737
|
+
get webSocket() {
|
|
738
|
+
return this._state.webSocket;
|
|
739
|
+
}
|
|
740
|
+
addEventListener(kind, handler) {
|
|
741
|
+
return this._listenerManager.subscribe(kind, handler);
|
|
742
|
+
}
|
|
743
|
+
async connect(...args) {
|
|
744
|
+
const params = args[0] ?? {};
|
|
745
|
+
const metadata = params.metadata;
|
|
746
|
+
this._setMetadata(metadata);
|
|
747
|
+
if (![
|
|
748
|
+
ConnectionState$1.Closed,
|
|
749
|
+
ConnectionState$1.Unavailable,
|
|
750
|
+
ConnectionState$1.Untouched
|
|
751
|
+
].some((state) => this._state.connection.state === state)) return;
|
|
752
|
+
this._clearTimeout(this._timeouts.reconnect);
|
|
753
|
+
this._updateState((oldState) => {
|
|
754
|
+
oldState.connection.state = ConnectionState$1.Connecting;
|
|
755
|
+
return oldState;
|
|
756
|
+
});
|
|
757
|
+
await this._storageStore.initialize();
|
|
758
|
+
const wsEndpoint = this._getWsEndpoint(this.id, params);
|
|
759
|
+
const url = new URL(wsEndpoint);
|
|
760
|
+
let authToken = null;
|
|
761
|
+
let webSocket;
|
|
762
|
+
try {
|
|
763
|
+
const publicKey = this._getPublicKey(params);
|
|
764
|
+
authToken = await this._getAuthorization(this.id, params);
|
|
765
|
+
if (authToken) url.searchParams.set("token", encodeURIComponent(authToken));
|
|
766
|
+
if (publicKey) url.searchParams.set("public_key", encodeURIComponent(publicKey));
|
|
767
|
+
webSocket = new WebSocket(url.toString());
|
|
768
|
+
} catch (err) {
|
|
769
|
+
this._updateState((oldState) => {
|
|
770
|
+
oldState.connection.attempts += 1;
|
|
771
|
+
return oldState;
|
|
772
|
+
});
|
|
773
|
+
await this._storageStore.destroy();
|
|
774
|
+
this._pollReconnect();
|
|
775
|
+
await Promise.resolve(this._onAuthorizationFail(err));
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
this._updateState((oldState) => {
|
|
779
|
+
oldState.connection.attempts = 0;
|
|
780
|
+
oldState.authorization.token = authToken;
|
|
781
|
+
oldState.storage.state = StorageState.Loading;
|
|
782
|
+
oldState.webSocket = webSocket;
|
|
783
|
+
return oldState;
|
|
784
|
+
});
|
|
785
|
+
this._attachWindowListeners();
|
|
786
|
+
this._attachWsListeners();
|
|
787
|
+
}
|
|
788
|
+
async disconnect() {
|
|
789
|
+
if (!this._state.webSocket) return;
|
|
790
|
+
if (![ConnectionState$1.Connecting, ConnectionState$1.Open].some((state) => this._state.connection.state === state)) return;
|
|
791
|
+
this._closeWs();
|
|
792
|
+
this._clearInterval(this._intervals.heartbeat);
|
|
793
|
+
this._clearTimeout(this._timeouts.reconnect);
|
|
794
|
+
this._updateState((oldState) => {
|
|
795
|
+
oldState.authorization.token = null;
|
|
796
|
+
oldState.connection.state = ConnectionState$1.Closed;
|
|
797
|
+
oldState.storage.state = oldState.storage.state === StorageState.Synced ? StorageState.Offline : StorageState.Unavailable;
|
|
798
|
+
oldState.webSocket = null;
|
|
799
|
+
return oldState;
|
|
800
|
+
});
|
|
801
|
+
await this._storageStore.destroy();
|
|
802
|
+
this._crdtManager.destroy();
|
|
803
|
+
this._stateNotifier.subjects["my-presence"].next(null);
|
|
804
|
+
}
|
|
805
|
+
getStorageJson(type) {
|
|
806
|
+
if (this._state.connection.id === null) return null;
|
|
807
|
+
if (typeof type === "undefined") return this._crdtManager.doc.toJson();
|
|
808
|
+
if (typeof this._crdtManager.get(type) === "undefined") return null;
|
|
809
|
+
return this._crdtManager.doc.toJson(type);
|
|
810
|
+
}
|
|
811
|
+
async _addToStorageStore(update) {
|
|
812
|
+
await this._storageStore.addUpdate(update);
|
|
813
|
+
this._flattenStorageStore();
|
|
814
|
+
}
|
|
815
|
+
_attachWindowListeners() {
|
|
816
|
+
if (typeof window === "undefined") return;
|
|
817
|
+
if (typeof document === "undefined") return;
|
|
818
|
+
this._windowListeners = this._windowListeners || {
|
|
819
|
+
onNavigatorOffline: this._onNavigatorOffline.bind(this),
|
|
820
|
+
onNavigatorOnline: this._onNavigatorOnline.bind(this),
|
|
821
|
+
onVisibilityChange: this._onVisibilityChange.bind(this),
|
|
822
|
+
onWindowFocus: this._onWindowFocus.bind(this)
|
|
823
|
+
};
|
|
824
|
+
window.addEventListener("offline", this._windowListeners.onNavigatorOffline);
|
|
825
|
+
window.addEventListener("online", this._windowListeners.onNavigatorOnline);
|
|
826
|
+
document.addEventListener("visibilitychange", this._windowListeners.onVisibilityChange);
|
|
827
|
+
window.addEventListener("focus", this._windowListeners.onWindowFocus);
|
|
828
|
+
}
|
|
829
|
+
_attachWsListeners() {
|
|
830
|
+
if (!this._state.webSocket) return;
|
|
831
|
+
this._wsListeners = this._wsListeners || {
|
|
832
|
+
onClose: this._onClose.bind(this),
|
|
833
|
+
onError: this._onError.bind(this),
|
|
834
|
+
onMessage: this._onMessage.bind(this),
|
|
835
|
+
onOpen: this._onOpen.bind(this)
|
|
836
|
+
};
|
|
837
|
+
this._state.webSocket.addEventListener("close", this._wsListeners.onClose);
|
|
838
|
+
this._state.webSocket.addEventListener("error", this._wsListeners.onError);
|
|
839
|
+
this._state.webSocket.addEventListener("message", this._wsListeners.onMessage);
|
|
840
|
+
this._state.webSocket.addEventListener("open", this._wsListeners.onOpen);
|
|
841
|
+
}
|
|
842
|
+
_clearInterval(interval) {
|
|
843
|
+
if (Number.isNaN(interval)) return;
|
|
844
|
+
clearInterval(interval);
|
|
845
|
+
}
|
|
846
|
+
_clearTimeout(timeout) {
|
|
847
|
+
if (Number.isNaN(timeout)) return;
|
|
848
|
+
clearTimeout(timeout);
|
|
849
|
+
}
|
|
850
|
+
_closeWs() {
|
|
851
|
+
this._subscriptions.observeCrdt?.();
|
|
852
|
+
this._clearInterval(this._intervals.heartbeat);
|
|
853
|
+
this._clearTimeout(this._timeouts.pong);
|
|
854
|
+
this._detachWindowListeners();
|
|
855
|
+
this._usersManager.removeMyself();
|
|
856
|
+
this._usersManager.clearConnections();
|
|
857
|
+
this._usersNotifier.clear();
|
|
858
|
+
this._stateNotifier.subjects.myself.next(null);
|
|
859
|
+
this._stateNotifier.subjects.others.next([]);
|
|
860
|
+
this._usersNotifier.others.next({
|
|
861
|
+
others: [],
|
|
862
|
+
event: { kind: "clear" }
|
|
863
|
+
});
|
|
864
|
+
this._state.webSocket?.close();
|
|
865
|
+
this._detachWsListeners();
|
|
866
|
+
}
|
|
867
|
+
_detachWindowListeners() {
|
|
868
|
+
if (!this._windowListeners) return;
|
|
869
|
+
if (typeof window === "undefined") return;
|
|
870
|
+
if (typeof document === "undefined") return;
|
|
871
|
+
window.removeEventListener("offline", this._windowListeners.onNavigatorOffline);
|
|
872
|
+
window.removeEventListener("online", this._windowListeners.onNavigatorOnline);
|
|
873
|
+
document.removeEventListener("visibilitychange", this._windowListeners.onVisibilityChange);
|
|
874
|
+
window.removeEventListener("focus", this._windowListeners.onWindowFocus);
|
|
875
|
+
this._windowListeners = null;
|
|
876
|
+
}
|
|
877
|
+
_detachWsListeners() {
|
|
878
|
+
if (!this._state.webSocket || !this._wsListeners) return;
|
|
879
|
+
this._state.webSocket.removeEventListener("close", this._wsListeners.onClose);
|
|
880
|
+
this._state.webSocket.removeEventListener("error", this._wsListeners.onError);
|
|
881
|
+
this._state.webSocket.removeEventListener("message", this._wsListeners.onMessage);
|
|
882
|
+
this._state.webSocket.removeEventListener("open", this._wsListeners.onOpen);
|
|
883
|
+
this._wsListeners = null;
|
|
884
|
+
}
|
|
885
|
+
_emitSharedTypes() {
|
|
886
|
+
const sharedTypes = this._crdtManager.doc.get();
|
|
887
|
+
const storageRoot = Object.keys(sharedTypes).reduce((acc, prop) => {
|
|
888
|
+
const serialized = this._crdtManager.doc.toJson(prop);
|
|
889
|
+
this._crdtNotifier.subject(prop).next(serialized);
|
|
890
|
+
return {
|
|
891
|
+
...acc,
|
|
892
|
+
[prop]: serialized
|
|
893
|
+
};
|
|
894
|
+
}, {});
|
|
895
|
+
this._crdtNotifier.rootSubject.next(storageRoot);
|
|
896
|
+
}
|
|
897
|
+
_getAuthFetchOptions(room, params) {
|
|
898
|
+
const metadata = params.metadata;
|
|
899
|
+
if (typeof this._endpoints.authEndpoint === "undefined") return null;
|
|
900
|
+
if (this._endpoints.authEndpoint === true) return {
|
|
901
|
+
url: `/api/pluv/authorize?room=${room}`,
|
|
902
|
+
options: {}
|
|
903
|
+
};
|
|
904
|
+
if (typeof this._endpoints.authEndpoint === "string") return {
|
|
905
|
+
url: this._endpoints.authEndpoint,
|
|
906
|
+
options: {}
|
|
907
|
+
};
|
|
908
|
+
const result = this._endpoints.authEndpoint({
|
|
909
|
+
metadata,
|
|
910
|
+
room
|
|
911
|
+
});
|
|
912
|
+
return typeof result === "string" ? {
|
|
913
|
+
url: result,
|
|
914
|
+
options: {}
|
|
915
|
+
} : result;
|
|
916
|
+
}
|
|
917
|
+
async _getAuthorization(room, params) {
|
|
918
|
+
const fetchOptions = this._getAuthFetchOptions(room, params);
|
|
919
|
+
if (!fetchOptions) return null;
|
|
920
|
+
const { url, options } = fetchOptions;
|
|
921
|
+
const res = await fetch(url, options);
|
|
922
|
+
if (!res.ok || res.status !== 200) throw new Error("Room is unauthorized");
|
|
923
|
+
try {
|
|
924
|
+
return await res.text().then((text) => text.trim());
|
|
925
|
+
} catch (err) {
|
|
926
|
+
throw new Error(err instanceof Error ? err.message : "Room is unauthorized");
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
_getPublicKey(params) {
|
|
930
|
+
const metadata = params.metadata;
|
|
931
|
+
if (!this._publicKey) return null;
|
|
932
|
+
if (typeof this._publicKey === "string") return this._publicKey;
|
|
933
|
+
return this._publicKey({ metadata });
|
|
934
|
+
}
|
|
935
|
+
_getWsEndpoint(room, params) {
|
|
936
|
+
const metadata = params.metadata;
|
|
937
|
+
switch (typeof this._endpoints.wsEndpoint) {
|
|
938
|
+
case "undefined": return !!this._getPublicKey(params) ? `wss://rooms.pluv.io/api/room/${room}` : `/api/pluv/room/${room}`;
|
|
939
|
+
case "string": return this._endpoints.wsEndpoint;
|
|
940
|
+
default: return this._endpoints.wsEndpoint({
|
|
941
|
+
metadata,
|
|
942
|
+
room
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
_handleExit(message) {
|
|
947
|
+
const { connectionId } = message;
|
|
948
|
+
if (!connectionId) return;
|
|
949
|
+
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
950
|
+
const clientId = this._usersManager.getClientId(connectionId);
|
|
951
|
+
const deleted = this._usersManager.deleteConnection(connectionId);
|
|
952
|
+
const others = this._usersManager.getOthers();
|
|
953
|
+
this._stateNotifier.subjects.others.next(others);
|
|
954
|
+
if (!deleted) {
|
|
955
|
+
console.warn("Could not identify exited connection");
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
const { data: user, remaining } = deleted;
|
|
959
|
+
this._usersNotifier.others.next({
|
|
960
|
+
others,
|
|
961
|
+
event: {
|
|
962
|
+
kind: "leave",
|
|
963
|
+
user
|
|
964
|
+
}
|
|
965
|
+
});
|
|
966
|
+
/**
|
|
967
|
+
* @description A single user can have multiple connections. So we're checking that
|
|
968
|
+
* there isn't a remaining connection before choosing to delete that user for other
|
|
969
|
+
* connected participants.
|
|
970
|
+
* @date April 16, 2025
|
|
971
|
+
*/
|
|
972
|
+
if (!remaining && !!clientId) this._usersNotifier.delete(clientId);
|
|
973
|
+
}
|
|
974
|
+
_handlePresenceUpdatedMessage(message) {
|
|
975
|
+
const { connectionId } = message;
|
|
976
|
+
if (!connectionId) return;
|
|
977
|
+
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
978
|
+
const data = message.data;
|
|
979
|
+
const myself = this._usersManager.myself ?? null;
|
|
980
|
+
/**
|
|
981
|
+
* !HACK
|
|
982
|
+
* @description We're going to have the user's own presence be patched only via local calls
|
|
983
|
+
* to this.updateMyPresence. So we'll not update the user's presence in this handler to
|
|
984
|
+
* avoid weird update delays to the user's own presence.
|
|
985
|
+
*
|
|
986
|
+
* However, we will patch the user's own presence if the user has updated their own
|
|
987
|
+
* presence via another connection that is not this one (e.g. if the user has opened
|
|
988
|
+
* another connection in another browser tab/window somewhere).
|
|
989
|
+
* @date April 2, 2025
|
|
990
|
+
*/
|
|
991
|
+
if (myself?.connectionId === connectionId) return;
|
|
992
|
+
const updated = this._usersManager.patchPresence(connectionId, data.presence);
|
|
993
|
+
const myClientId = !!myself ? this._usersManager.getClientId(myself) : null;
|
|
994
|
+
const clientId = this._usersManager.getClientId(connectionId);
|
|
995
|
+
if (!!clientId && myClientId === clientId) {
|
|
996
|
+
this._stateNotifier.subjects["my-presence"].next(updated);
|
|
997
|
+
this._stateNotifier.subjects.myself.next(this._usersManager.myself);
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
if (!!clientId) {
|
|
1001
|
+
const other$1 = this._usersManager.getOther(connectionId);
|
|
1002
|
+
const others$1 = this._usersManager.getOthers();
|
|
1003
|
+
this._usersNotifier.other(clientId).next(other$1);
|
|
1004
|
+
this._stateNotifier.subjects.others.next(others$1);
|
|
1005
|
+
if (!!other$1) this._usersNotifier.others.next({
|
|
1006
|
+
others: others$1,
|
|
1007
|
+
event: {
|
|
1008
|
+
kind: "update",
|
|
1009
|
+
user: other$1
|
|
1010
|
+
}
|
|
1011
|
+
});
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* !HACK
|
|
1016
|
+
* @description User could not be found. Add the connection to keep others up-to-date
|
|
1017
|
+
* @date April 19, 2025
|
|
1018
|
+
*/
|
|
1019
|
+
const added = this._usersManager.addConnection({
|
|
1020
|
+
connectionId,
|
|
1021
|
+
presence: data.presence,
|
|
1022
|
+
user: message.user
|
|
1023
|
+
});
|
|
1024
|
+
const other = this._usersManager.getOther(connectionId);
|
|
1025
|
+
const others = this._usersManager.getOthers();
|
|
1026
|
+
this._usersNotifier.other(added.clientId).next(other);
|
|
1027
|
+
this._stateNotifier.subjects.others.next(others);
|
|
1028
|
+
this._usersNotifier.others.next({
|
|
1029
|
+
others,
|
|
1030
|
+
event: {
|
|
1031
|
+
kind: "update",
|
|
1032
|
+
user: added.data
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
_handleReceiveOthers(message) {
|
|
1037
|
+
if (!message.connectionId) return;
|
|
1038
|
+
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
1039
|
+
const data = message.data;
|
|
1040
|
+
Object.keys(data.others).forEach((connectionId) => {
|
|
1041
|
+
const { presence, user } = data.others[connectionId];
|
|
1042
|
+
const result = this._usersManager.addConnection({
|
|
1043
|
+
connectionId,
|
|
1044
|
+
presence,
|
|
1045
|
+
user
|
|
1046
|
+
});
|
|
1047
|
+
if (!!presence) this._usersManager.patchPresence(connectionId, presence);
|
|
1048
|
+
if (result.isMyself) return;
|
|
1049
|
+
const clientId = result.clientId;
|
|
1050
|
+
const other = this._usersManager.getOther(connectionId);
|
|
1051
|
+
this._usersNotifier.other(clientId).next(other);
|
|
1052
|
+
});
|
|
1053
|
+
const others = this._usersManager.getOthers();
|
|
1054
|
+
this._stateNotifier.subjects.others.next(others);
|
|
1055
|
+
this._usersNotifier.others.next({
|
|
1056
|
+
others,
|
|
1057
|
+
event: {
|
|
1058
|
+
kind: "sync",
|
|
1059
|
+
users: others
|
|
1060
|
+
}
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
async _handleRegisteredMessage(message) {
|
|
1064
|
+
const { connectionId } = message;
|
|
1065
|
+
const user = message.user;
|
|
1066
|
+
if (!connectionId) return;
|
|
1067
|
+
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
1068
|
+
const data = message.data;
|
|
1069
|
+
const state = data.state;
|
|
1070
|
+
this._updateState((oldState) => {
|
|
1071
|
+
oldState.connection.id = connectionId;
|
|
1072
|
+
oldState.authorization.user = user ?? null;
|
|
1073
|
+
return oldState;
|
|
1074
|
+
});
|
|
1075
|
+
const userInfo = {
|
|
1076
|
+
connectionId,
|
|
1077
|
+
presence: data.presence ?? void 0,
|
|
1078
|
+
user
|
|
1079
|
+
};
|
|
1080
|
+
this._usersManager.setMyself(userInfo);
|
|
1081
|
+
const presence = this._usersManager.myPresence;
|
|
1082
|
+
const myself = this._usersManager.myself ?? null;
|
|
1083
|
+
this._stateNotifier.subjects["my-presence"].next(presence);
|
|
1084
|
+
this._stateNotifier.subjects.myself.next(myself);
|
|
1085
|
+
const update = await (async () => {
|
|
1086
|
+
if (!!state) {
|
|
1087
|
+
this._logDebug("Retrieving initial state");
|
|
1088
|
+
return this._crdtManager.getInitialState();
|
|
1089
|
+
}
|
|
1090
|
+
const resolved = this._crdtManager.resolveEncodedState(await this._storageStore.getUpdates());
|
|
1091
|
+
if (!!resolved) {
|
|
1092
|
+
this._logDebug("Retrieving storage store state");
|
|
1093
|
+
return resolved;
|
|
1094
|
+
}
|
|
1095
|
+
this._logDebug("Retrieving initial state");
|
|
1096
|
+
return this._crdtManager.getInitialState();
|
|
1097
|
+
})();
|
|
1098
|
+
this._sendMessage({
|
|
1099
|
+
type: "$initializeSession",
|
|
1100
|
+
data: {
|
|
1101
|
+
presence,
|
|
1102
|
+
update
|
|
1103
|
+
}
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
async _handleStorageReceivedMessage(message) {
|
|
1107
|
+
const { connectionId } = message;
|
|
1108
|
+
if (!connectionId) return;
|
|
1109
|
+
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
1110
|
+
const data = message.data;
|
|
1111
|
+
const changeKind = data.changeKind;
|
|
1112
|
+
const state = data.state;
|
|
1113
|
+
const updates = await this._storageStore.getUpdates();
|
|
1114
|
+
const origin = ORIGIN_INITIALIZED;
|
|
1115
|
+
const updateStorage = (update) => {
|
|
1116
|
+
return this._crdtManager.initialized ? this._crdtManager.applyUpdate({
|
|
1117
|
+
update,
|
|
1118
|
+
origin
|
|
1119
|
+
}) : this._crdtManager.initialize({
|
|
1120
|
+
update,
|
|
1121
|
+
origin
|
|
1122
|
+
});
|
|
1123
|
+
};
|
|
1124
|
+
if (changeKind === "empty") {
|
|
1125
|
+
this._crdtManager.destroy();
|
|
1126
|
+
this._crdtManager.initialize({
|
|
1127
|
+
origin,
|
|
1128
|
+
update: updates
|
|
1129
|
+
});
|
|
1130
|
+
} else if (changeKind === "initialized") if (!!updates.length) updateStorage(updates);
|
|
1131
|
+
else {
|
|
1132
|
+
const encodedState$1 = updateStorage(state).doc.getEncodedState();
|
|
1133
|
+
this._addToStorageStore(encodedState$1);
|
|
1134
|
+
}
|
|
1135
|
+
else {
|
|
1136
|
+
const encodedState$1 = updateStorage(state).doc.getEncodedState();
|
|
1137
|
+
if (!!updates.length) this._crdtManager.applyUpdate({
|
|
1138
|
+
update: updates,
|
|
1139
|
+
origin
|
|
1140
|
+
});
|
|
1141
|
+
await this._addToStorageStore(encodedState$1);
|
|
1142
|
+
}
|
|
1143
|
+
const encodedState = this._crdtManager.doc.getEncodedState();
|
|
1144
|
+
this._emitSharedTypes();
|
|
1145
|
+
this._observeCrdt();
|
|
1146
|
+
this._updateState((oldState) => {
|
|
1147
|
+
oldState.storage.state = StorageState.Synced;
|
|
1148
|
+
return oldState;
|
|
1149
|
+
});
|
|
1150
|
+
this._stateNotifier.subjects["storage-loaded"].next(true);
|
|
1151
|
+
this._sendMessage({
|
|
1152
|
+
type: "$updateStorage",
|
|
1153
|
+
data: {
|
|
1154
|
+
origin,
|
|
1155
|
+
update: encodedState
|
|
1156
|
+
}
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
_handleStorageUpdatedMessage(message) {
|
|
1160
|
+
const { connectionId } = message;
|
|
1161
|
+
if (!connectionId) return;
|
|
1162
|
+
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
1163
|
+
const data = message.data;
|
|
1164
|
+
this._crdtManager.doc.applyEncodedState({
|
|
1165
|
+
origin: ORIGIN_STORAGE_UPDATED,
|
|
1166
|
+
update: data.state
|
|
1167
|
+
});
|
|
1168
|
+
const sharedTypes = this._crdtManager.doc.get();
|
|
1169
|
+
const storageRoot = Object.keys(sharedTypes).reduce((acc, prop) => {
|
|
1170
|
+
const serialized = this._crdtManager.doc.toJson(prop);
|
|
1171
|
+
this._crdtNotifier.subject(prop).next(serialized);
|
|
1172
|
+
return {
|
|
1173
|
+
...acc,
|
|
1174
|
+
[prop]: serialized
|
|
1175
|
+
};
|
|
1176
|
+
}, {});
|
|
1177
|
+
this._crdtNotifier.rootSubject.next(storageRoot);
|
|
1178
|
+
}
|
|
1179
|
+
_handleSyncStateReceived(message) {
|
|
1180
|
+
const { connectionId } = message;
|
|
1181
|
+
if (!connectionId) return;
|
|
1182
|
+
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
1183
|
+
if (!this._usersManager.myself) return;
|
|
1184
|
+
const data = message.data;
|
|
1185
|
+
const active = new Set(data?.connectionIds ?? []);
|
|
1186
|
+
this._usersManager.getOthers().filter((other) => !active.has(other.connectionId)).forEach((quitter) => {
|
|
1187
|
+
this._usersManager.deleteConnection(quitter.connectionId);
|
|
1188
|
+
const remaining$1 = this._usersManager.getOthers();
|
|
1189
|
+
this._usersNotifier.others.next({
|
|
1190
|
+
others: remaining$1,
|
|
1191
|
+
event: {
|
|
1192
|
+
kind: "leave",
|
|
1193
|
+
user: quitter
|
|
1194
|
+
}
|
|
1195
|
+
});
|
|
1196
|
+
});
|
|
1197
|
+
const remaining = this._usersManager.getOthers();
|
|
1198
|
+
this._stateNotifier.subjects.others.next(remaining);
|
|
1199
|
+
}
|
|
1200
|
+
_handleUserJoinedMessage(message) {
|
|
1201
|
+
const { connectionId } = message;
|
|
1202
|
+
if (!connectionId) return;
|
|
1203
|
+
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
1204
|
+
if (!this._usersManager.myself) return;
|
|
1205
|
+
const data = message.data;
|
|
1206
|
+
if (this._usersManager.myself.connectionId === connectionId) {
|
|
1207
|
+
this._sendMessage({
|
|
1208
|
+
type: "$getOthers",
|
|
1209
|
+
data: {}
|
|
1210
|
+
});
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
const added = this._usersManager.addConnection({
|
|
1214
|
+
connectionId,
|
|
1215
|
+
presence: data.presence,
|
|
1216
|
+
user: data.user
|
|
1217
|
+
});
|
|
1218
|
+
const other = this._usersManager.getOther(connectionId);
|
|
1219
|
+
const others = this._usersManager.getOthers();
|
|
1220
|
+
this._usersNotifier.other(added.clientId).next(other);
|
|
1221
|
+
this._stateNotifier.subjects.others.next(others);
|
|
1222
|
+
this._usersNotifier.others.next({
|
|
1223
|
+
others,
|
|
1224
|
+
event: {
|
|
1225
|
+
kind: "enter",
|
|
1226
|
+
user: added.data
|
|
1227
|
+
}
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
_heartbeat() {
|
|
1231
|
+
this._clearTimeout(this._timeouts.pong);
|
|
1232
|
+
this._timeouts.pong = setTimeout(this._reconnect.bind(this), PONG_TIMEOUT_MS);
|
|
1233
|
+
/**
|
|
1234
|
+
* !HACK
|
|
1235
|
+
* @description Send data as a stable string so that the server can react to this in a
|
|
1236
|
+
* consistent way.
|
|
1237
|
+
* @date July 8, 2024
|
|
1238
|
+
*/
|
|
1239
|
+
this._sendMessage("{\"type\":\"$ping\",\"data\":{}}");
|
|
1240
|
+
}
|
|
1241
|
+
_logDebug(...data) {
|
|
1242
|
+
if (typeof process === "undefined") return;
|
|
1243
|
+
if (process.env?.NODE_ENV === "production") return;
|
|
1244
|
+
if (this._debug) console.log(...data);
|
|
1245
|
+
}
|
|
1246
|
+
_observeCrdt() {
|
|
1247
|
+
this._subscriptions.observeCrdt?.();
|
|
1248
|
+
const unsubscribe = this._crdtManager.doc.subscribe((event) => {
|
|
1249
|
+
const origin = event.origin ?? null;
|
|
1250
|
+
this._emitSharedTypes();
|
|
1251
|
+
if (origin === ORIGIN_INITIALIZED || origin === ORIGIN_STORAGE_UPDATED) return;
|
|
1252
|
+
this._addToStorageStore(event.update);
|
|
1253
|
+
if (!this._state.webSocket) return;
|
|
1254
|
+
if (!this._state.connection.id) return;
|
|
1255
|
+
if (this._state.webSocket.readyState !== WebSocket.OPEN) return;
|
|
1256
|
+
this._sendMessage({
|
|
1257
|
+
type: "$updateStorage",
|
|
1258
|
+
data: {
|
|
1259
|
+
origin,
|
|
1260
|
+
update: event.update
|
|
1261
|
+
}
|
|
1262
|
+
});
|
|
1263
|
+
});
|
|
1264
|
+
this._subscriptions.observeCrdt = unsubscribe;
|
|
1265
|
+
}
|
|
1266
|
+
_onAuthorizationFail(error) {
|
|
1267
|
+
const authError = error instanceof Error ? error : /* @__PURE__ */ new Error("Authorization failed");
|
|
1268
|
+
this._listeners.onAuthorizationFail(authError);
|
|
1269
|
+
this._updateState((oldState) => {
|
|
1270
|
+
oldState.connection.state = ConnectionState$1.Unavailable;
|
|
1271
|
+
oldState.storage.state = StorageState.Unavailable;
|
|
1272
|
+
return oldState;
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
async _onClose(event) {
|
|
1276
|
+
this._listenerManager.subjects.close.next(event);
|
|
1277
|
+
this._logDebug("WebSocket closed");
|
|
1278
|
+
if (!!event.reason) this._logDebug(event.reason);
|
|
1279
|
+
const shouldRetry = [
|
|
1280
|
+
1001,
|
|
1281
|
+
1006,
|
|
1282
|
+
1011,
|
|
1283
|
+
1012,
|
|
1284
|
+
1013,
|
|
1285
|
+
1014
|
|
1286
|
+
].some((okToRetryCode) => event.code === okToRetryCode);
|
|
1287
|
+
this._updateState((oldState) => {
|
|
1288
|
+
oldState.authorization.token = null;
|
|
1289
|
+
oldState.connection.state = shouldRetry ? ConnectionState$1.Unavailable : ConnectionState$1.Closed;
|
|
1290
|
+
oldState.storage.state = oldState.storage.state === StorageState.Synced ? StorageState.Offline : StorageState.Unavailable;
|
|
1291
|
+
oldState.webSocket = null;
|
|
1292
|
+
return oldState;
|
|
1293
|
+
});
|
|
1294
|
+
if (shouldRetry) {
|
|
1295
|
+
await this._reconnect();
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
this._closeWs();
|
|
1299
|
+
this._clearTimeout(this._timeouts.reconnect);
|
|
1300
|
+
}
|
|
1301
|
+
/**
|
|
1302
|
+
* TODO
|
|
1303
|
+
* @description Handle websocket errors in a meaningful way, should they occur
|
|
1304
|
+
* @date August 19, 2022
|
|
1305
|
+
*/
|
|
1306
|
+
_onError(event) {
|
|
1307
|
+
this._listenerManager.subjects.error.next(event);
|
|
1308
|
+
}
|
|
1309
|
+
_onMessage(event) {
|
|
1310
|
+
this._listenerManager.subjects.message.next(event);
|
|
1311
|
+
const message = this._parseMessage(event);
|
|
1312
|
+
if (!message) return;
|
|
1313
|
+
if (typeof this._debug === "boolean" ? this._debug : this._debug.output.find((value) => value === message.type)) this._logDebug("WebSocket event received: ", message.type, message);
|
|
1314
|
+
this._eventNotifier.subject(message.type).next(message);
|
|
1315
|
+
switch (message.type) {
|
|
1316
|
+
case "$exit":
|
|
1317
|
+
this._handleExit(message);
|
|
1318
|
+
return;
|
|
1319
|
+
case "$othersReceived":
|
|
1320
|
+
this._handleReceiveOthers(message);
|
|
1321
|
+
return;
|
|
1322
|
+
case "$pong":
|
|
1323
|
+
this._clearTimeout(this._timeouts.pong);
|
|
1324
|
+
return;
|
|
1325
|
+
case "$presenceUpdated":
|
|
1326
|
+
this._handlePresenceUpdatedMessage(message);
|
|
1327
|
+
return;
|
|
1328
|
+
case "$registered":
|
|
1329
|
+
this._handleRegisteredMessage(message);
|
|
1330
|
+
return;
|
|
1331
|
+
case "$storageReceived":
|
|
1332
|
+
this._handleStorageReceivedMessage(message);
|
|
1333
|
+
return;
|
|
1334
|
+
case "$storageUpdated":
|
|
1335
|
+
this._handleStorageUpdatedMessage(message);
|
|
1336
|
+
return;
|
|
1337
|
+
case "$syncStateReceived":
|
|
1338
|
+
this._handleSyncStateReceived(message);
|
|
1339
|
+
return;
|
|
1340
|
+
case "$userJoined":
|
|
1341
|
+
this._handleUserJoinedMessage(message);
|
|
1342
|
+
return;
|
|
1343
|
+
default:
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
_onOpen(event) {
|
|
1347
|
+
this._listenerManager.subjects.open.next(event);
|
|
1348
|
+
if (this._state.connection.state !== ConnectionState$1.Connecting) return;
|
|
1349
|
+
this._logDebug("WebSocket connected");
|
|
1350
|
+
this._updateState((oldState) => {
|
|
1351
|
+
oldState.connection.state = ConnectionState$1.Open;
|
|
1352
|
+
return oldState;
|
|
1353
|
+
});
|
|
1354
|
+
this._clearInterval(this._intervals.heartbeat);
|
|
1355
|
+
this._intervals.heartbeat = setInterval(this._heartbeat.bind(this), HEARTBEAT_INTERVAL_MS);
|
|
1356
|
+
}
|
|
1357
|
+
async _onNavigatorOffline() {
|
|
1358
|
+
if (this._state.connection.state === ConnectionState$1.Closed) return;
|
|
1359
|
+
this._updateState((oldState) => {
|
|
1360
|
+
oldState.connection.state = ConnectionState$1.Unavailable;
|
|
1361
|
+
oldState.storage.state = oldState.storage.state === StorageState.Synced ? StorageState.Offline : StorageState.Unavailable;
|
|
1362
|
+
return oldState;
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
async _onNavigatorOnline() {
|
|
1366
|
+
if (this._state.connection.state !== ConnectionState$1.Unavailable) return;
|
|
1367
|
+
await this._reconnect();
|
|
1368
|
+
}
|
|
1369
|
+
async _onVisibilityChange() {
|
|
1370
|
+
if (typeof document === "undefined") return;
|
|
1371
|
+
if (document.visibilityState !== "visible") return;
|
|
1372
|
+
switch (this._state.connection.state) {
|
|
1373
|
+
case ConnectionState$1.Open:
|
|
1374
|
+
this._clearInterval(this._intervals.heartbeat);
|
|
1375
|
+
setInterval(this._heartbeat.bind(this), HEARTBEAT_INTERVAL_MS);
|
|
1376
|
+
return;
|
|
1377
|
+
case ConnectionState$1.Unavailable:
|
|
1378
|
+
await this._reconnect();
|
|
1379
|
+
return;
|
|
1380
|
+
default:
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
async _onWindowFocus() {
|
|
1384
|
+
return await this._onVisibilityChange();
|
|
1385
|
+
}
|
|
1386
|
+
_parseMessage(message) {
|
|
1387
|
+
/**
|
|
1388
|
+
* !HACK
|
|
1389
|
+
* @description We'll only handle stringified JSONs for now
|
|
1390
|
+
* @date August 9, 2022
|
|
1391
|
+
*/
|
|
1392
|
+
if (typeof message.data !== "string") return null;
|
|
1393
|
+
try {
|
|
1394
|
+
const config = JSON.parse(message.data);
|
|
1395
|
+
if (typeof config !== "object") return null;
|
|
1396
|
+
if (typeof config.type !== "string") return null;
|
|
1397
|
+
if (typeof config.data !== "object") return null;
|
|
1398
|
+
return config;
|
|
1399
|
+
} catch {
|
|
1400
|
+
return null;
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
_pollReconnect() {
|
|
1404
|
+
this._clearTimeout(this._timeouts.reconnect);
|
|
1405
|
+
const timeoutMs = typeof this._reconnectTimeoutMs === "number" ? this._reconnectTimeoutMs : this._reconnectTimeoutMs({ attempts: this._state.connection.attempts });
|
|
1406
|
+
const clampedMs = Math.max(Math.min(MIN_RECONNECT_TIMEOUT_MS, timeoutMs), MAX_RECONNECT_TIMEOUT_MS);
|
|
1407
|
+
this._timeouts.reconnect = setTimeout(this._reconnect.bind(this), clampedMs);
|
|
1408
|
+
}
|
|
1409
|
+
async _reconnect() {
|
|
1410
|
+
this._closeWs();
|
|
1411
|
+
/**
|
|
1412
|
+
* @description User has manually closed this connection. We're not going to retry, since
|
|
1413
|
+
* the user probably intends for this to stay closed. They will need to invoke connect
|
|
1414
|
+
* themselves.
|
|
1415
|
+
* @date April 28, 2025
|
|
1416
|
+
*/
|
|
1417
|
+
if (this._state.connection.state === ConnectionState$1.Closed) return;
|
|
1418
|
+
const metadata = this._lastMetadata;
|
|
1419
|
+
const params = typeof metadata === "undefined" ? [] : [{ metadata }];
|
|
1420
|
+
await this.connect(...params);
|
|
1421
|
+
}
|
|
1422
|
+
_sendMessage(data) {
|
|
1423
|
+
const webSocket = this._state.webSocket;
|
|
1424
|
+
if (webSocket?.readyState !== WebSocket.OPEN) return;
|
|
1425
|
+
const dataType = (() => {
|
|
1426
|
+
if (typeof data === "object") return data.type ?? null;
|
|
1427
|
+
if (typeof data !== "string") return null;
|
|
1428
|
+
try {
|
|
1429
|
+
return JSON.parse(data)?.type ?? null;
|
|
1430
|
+
} catch {
|
|
1431
|
+
return null;
|
|
1432
|
+
}
|
|
1433
|
+
})();
|
|
1434
|
+
if (typeof this._debug === "boolean" ? this._debug : !dataType ? false : this._debug.input.some((value) => value === data.type)) this._logDebug("WebSocket event sent: ", data.type, data);
|
|
1435
|
+
const message = typeof data === "string" ? data : JSON.stringify(data);
|
|
1436
|
+
webSocket.send(message);
|
|
1437
|
+
}
|
|
1438
|
+
_setMetadata(metadata) {
|
|
1439
|
+
const parsed = this.metadata ? this.metadata.parse(metadata) : metadata;
|
|
1440
|
+
this._lastMetadata = parsed;
|
|
1441
|
+
return parsed;
|
|
1442
|
+
}
|
|
1443
|
+
#_storage = new Proxy((key, callback) => this._crdtNotifier.subscribe(key, callback), { get: (fn, prop) => {
|
|
1444
|
+
if (!!prop) return (callback) => {
|
|
1445
|
+
return this._crdtNotifier.subcribeRoot(callback);
|
|
1446
|
+
};
|
|
1447
|
+
return (callback) => {
|
|
1448
|
+
return fn(prop, callback);
|
|
1449
|
+
};
|
|
1450
|
+
} });
|
|
1451
|
+
_updateState(updater) {
|
|
1452
|
+
let authorization;
|
|
1453
|
+
try {
|
|
1454
|
+
authorization = JSON.parse(JSON.stringify(this._state.authorization));
|
|
1455
|
+
} catch {
|
|
1456
|
+
throw new Error("User is not JSON serializable");
|
|
1457
|
+
}
|
|
1458
|
+
const newState = updater({
|
|
1459
|
+
...this._state,
|
|
1460
|
+
authorization,
|
|
1461
|
+
connection: JSON.parse(JSON.stringify(this._state.connection)),
|
|
1462
|
+
storage: JSON.parse(JSON.stringify(this._state.storage)),
|
|
1463
|
+
webSocket: this._state.webSocket
|
|
1464
|
+
});
|
|
1465
|
+
this._state = newState;
|
|
1466
|
+
this._logDebug("WebSocket new state: ", newState);
|
|
1467
|
+
this._stateNotifier.subjects.connection.next(newState);
|
|
1468
|
+
return newState;
|
|
1469
|
+
}
|
|
1543
1470
|
};
|
|
1544
|
-
__storage = new WeakMap();
|
|
1545
1471
|
|
|
1546
|
-
|
|
1472
|
+
//#endregion
|
|
1473
|
+
//#region src/PluvClient.ts
|
|
1547
1474
|
var PluvClient = class {
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
return new PluvProcedure();
|
|
1624
|
-
}
|
|
1625
|
-
router(events) {
|
|
1626
|
-
const invalidName = Object.keys(events).find((name) => name.includes("$"));
|
|
1627
|
-
if (typeof invalidName === "string") {
|
|
1628
|
-
throw new Error(`Invalid event name. Event names must not contain $: "${invalidName}"`);
|
|
1629
|
-
}
|
|
1630
|
-
return new PluvRouter(events);
|
|
1631
|
-
}
|
|
1632
|
-
_logDebug(...data) {
|
|
1633
|
-
var _a;
|
|
1634
|
-
if (typeof process === "undefined") return;
|
|
1635
|
-
if (((_a = process.env) == null ? void 0 : _a.NODE_ENV) === "production") return;
|
|
1636
|
-
if (this._debug) console.log(...data);
|
|
1637
|
-
}
|
|
1475
|
+
get _defs() {
|
|
1476
|
+
return { initialStorage: this._initialStorage };
|
|
1477
|
+
}
|
|
1478
|
+
get procedure() {
|
|
1479
|
+
return new PluvProcedure();
|
|
1480
|
+
}
|
|
1481
|
+
constructor(options) {
|
|
1482
|
+
this._publicKey = null;
|
|
1483
|
+
this._rooms = /* @__PURE__ */ new Map();
|
|
1484
|
+
this.createRoom = (room, options$1 = {}) => {
|
|
1485
|
+
const oldRoom = this.getRoom(room);
|
|
1486
|
+
if (oldRoom) return oldRoom;
|
|
1487
|
+
const newRoom = new PluvRoom(room, {
|
|
1488
|
+
addons: options$1.addons,
|
|
1489
|
+
authEndpoint: this._authEndpoint,
|
|
1490
|
+
debug: options$1.debug,
|
|
1491
|
+
initialPresence: options$1.initialPresence,
|
|
1492
|
+
initialStorage: options$1.initialStorage ?? this._initialStorage,
|
|
1493
|
+
limits: this._limits,
|
|
1494
|
+
metadata: this.metadata,
|
|
1495
|
+
onAuthorizationFail: options$1.onAuthorizationFail,
|
|
1496
|
+
presence: this._presence,
|
|
1497
|
+
publicKey: this._publicKey ?? void 0,
|
|
1498
|
+
reconnectTimeoutMs: options$1.reconnectTimeoutMs,
|
|
1499
|
+
router: options$1.router,
|
|
1500
|
+
wsEndpoint: this._wsEndpoint
|
|
1501
|
+
});
|
|
1502
|
+
this._rooms.set(room, newRoom);
|
|
1503
|
+
this._logDebug(`New room was created: ${room}`);
|
|
1504
|
+
return newRoom;
|
|
1505
|
+
};
|
|
1506
|
+
this.enter = async (room, ...args) => {
|
|
1507
|
+
const toEnter = typeof room === "string" ? this.getRoom(room) : room;
|
|
1508
|
+
if (!toEnter) throw new Error(`Could not find room: ${room}.`);
|
|
1509
|
+
this._rooms.set(toEnter.id, toEnter);
|
|
1510
|
+
await toEnter.connect(...args);
|
|
1511
|
+
this._logDebug(`Entered room: ${room}`);
|
|
1512
|
+
return toEnter;
|
|
1513
|
+
};
|
|
1514
|
+
this.getRoom = (room) => {
|
|
1515
|
+
return this._rooms.get(room) ?? null;
|
|
1516
|
+
};
|
|
1517
|
+
this.getRooms = () => {
|
|
1518
|
+
return Array.from(this._rooms.values());
|
|
1519
|
+
};
|
|
1520
|
+
this.leave = async (room) => {
|
|
1521
|
+
const toLeave = typeof room === "string" ? this.getRoom(room) : room;
|
|
1522
|
+
if (!toLeave) return;
|
|
1523
|
+
await toLeave.disconnect();
|
|
1524
|
+
this._rooms.delete(toLeave.id);
|
|
1525
|
+
this._logDebug(`Left and deleted room: ${room}`);
|
|
1526
|
+
};
|
|
1527
|
+
const { authEndpoint, debug = false, initialStorage, limits, metadata, presence, publicKey, wsEndpoint } = options;
|
|
1528
|
+
this.metadata = metadata;
|
|
1529
|
+
this._authEndpoint = authEndpoint;
|
|
1530
|
+
this._debug = debug;
|
|
1531
|
+
this._initialStorage = initialStorage;
|
|
1532
|
+
this._limits = {
|
|
1533
|
+
presenceMaxSize: MAX_PRESENCE_SIZE_BYTES,
|
|
1534
|
+
...limits
|
|
1535
|
+
};
|
|
1536
|
+
this._presence = presence;
|
|
1537
|
+
this._wsEndpoint = wsEndpoint;
|
|
1538
|
+
if (!!publicKey) this._publicKey = publicKey;
|
|
1539
|
+
}
|
|
1540
|
+
router(events) {
|
|
1541
|
+
const invalidName = Object.keys(events).find((name) => name.includes("$"));
|
|
1542
|
+
if (typeof invalidName === "string") throw new Error(`Invalid event name. Event names must not contain $: "${invalidName}"`);
|
|
1543
|
+
return new PluvRouter(events);
|
|
1544
|
+
}
|
|
1545
|
+
_logDebug(...data) {
|
|
1546
|
+
if (typeof process === "undefined") return;
|
|
1547
|
+
if (process.env?.NODE_ENV === "production") return;
|
|
1548
|
+
if (this._debug) console.log(...data);
|
|
1549
|
+
}
|
|
1638
1550
|
};
|
|
1639
1551
|
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1552
|
+
//#endregion
|
|
1553
|
+
//#region src/createClient.ts
|
|
1554
|
+
const createClient = (options) => {
|
|
1555
|
+
return new PluvClient(options);
|
|
1643
1556
|
};
|
|
1644
1557
|
|
|
1645
|
-
|
|
1646
|
-
|
|
1558
|
+
//#endregion
|
|
1559
|
+
//#region src/infer.ts
|
|
1560
|
+
const infer = (callback) => {
|
|
1561
|
+
return callback;
|
|
1562
|
+
};
|
|
1647
1563
|
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
var __storage2;
|
|
1564
|
+
//#endregion
|
|
1565
|
+
//#region src/MockedRoom.ts
|
|
1651
1566
|
var MockedRoom = class {
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
(_b = (_a = this._subscriptions).observeCrdt) == null ? void 0 : _b.call(_a);
|
|
1860
|
-
if (!this._crdtManager) return;
|
|
1861
|
-
const unsubscribe = this._crdtManager.doc.subscribe((event) => {
|
|
1862
|
-
var _a2;
|
|
1863
|
-
const origin = (_a2 = event.origin) != null ? _a2 : null;
|
|
1864
|
-
if (!this._crdtManager) return;
|
|
1865
|
-
if (origin === "$storageUpdated") return;
|
|
1866
|
-
const sharedTypes = this._crdtManager.doc.get();
|
|
1867
|
-
const storageRoot = Object.keys(sharedTypes).reduce(
|
|
1868
|
-
(acc, prop) => {
|
|
1869
|
-
if (!this._crdtManager) return acc;
|
|
1870
|
-
const serialized = this._crdtManager.doc.toJson(prop);
|
|
1871
|
-
this._crdtNotifier.subject(prop).next(serialized);
|
|
1872
|
-
return __spreadProps(__spreadValues({}, acc), { [prop]: serialized });
|
|
1873
|
-
},
|
|
1874
|
-
{}
|
|
1875
|
-
);
|
|
1876
|
-
this._crdtNotifier.rootSubject.next(storageRoot);
|
|
1877
|
-
});
|
|
1878
|
-
this._subscriptions.observeCrdt = unsubscribe;
|
|
1879
|
-
}
|
|
1880
|
-
_simulateEvent(event, data) {
|
|
1881
|
-
if (!this._events) return;
|
|
1882
|
-
const type = event.toString();
|
|
1883
|
-
const resolver = this._events[type];
|
|
1884
|
-
if (!resolver) return;
|
|
1885
|
-
const result = resolver(data);
|
|
1886
|
-
Object.keys(result).forEach((type2) => {
|
|
1887
|
-
const _type = type2.toString();
|
|
1888
|
-
const data2 = result[_type];
|
|
1889
|
-
if (!data2) return;
|
|
1890
|
-
this._eventNotifier.subject(_type).next(data2);
|
|
1891
|
-
});
|
|
1892
|
-
}
|
|
1567
|
+
constructor(room, options) {
|
|
1568
|
+
this._crdtNotifier = new CrdtNotifier();
|
|
1569
|
+
this._eventNotifier = new EventNotifier();
|
|
1570
|
+
this._usersNotifier = new UsersNotifier();
|
|
1571
|
+
this._state = {
|
|
1572
|
+
authorization: {
|
|
1573
|
+
token: null,
|
|
1574
|
+
user: null
|
|
1575
|
+
},
|
|
1576
|
+
connection: {
|
|
1577
|
+
attempts: 0,
|
|
1578
|
+
id: null,
|
|
1579
|
+
state: ConnectionState$1.Untouched
|
|
1580
|
+
},
|
|
1581
|
+
storage: { state: StorageState.Unavailable },
|
|
1582
|
+
webSocket: null
|
|
1583
|
+
};
|
|
1584
|
+
this._stateNotifier = new StateNotifier();
|
|
1585
|
+
this._subscriptions = { observeCrdt: null };
|
|
1586
|
+
this.broadcast = new Proxy(async (event, data) => {
|
|
1587
|
+
if (!this._state.webSocket) return;
|
|
1588
|
+
if (this._state.connection.state !== ConnectionState$1.Open) return;
|
|
1589
|
+
const type = event.toString();
|
|
1590
|
+
const procedure = this._router._defs.events[type];
|
|
1591
|
+
if (!procedure?.config.broadcast) {
|
|
1592
|
+
this._simulateEvent(type, data);
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
const myself = this._usersManager.myself;
|
|
1596
|
+
if (!myself) return;
|
|
1597
|
+
const parsed = procedure.config.input ? procedure.config.input.parse(data) : data;
|
|
1598
|
+
const context = {
|
|
1599
|
+
doc: this._crdtManager.doc,
|
|
1600
|
+
others: this._usersManager.getOthers(),
|
|
1601
|
+
room: this.id,
|
|
1602
|
+
user: myself
|
|
1603
|
+
};
|
|
1604
|
+
const output = await procedure.config.broadcast(parsed, context);
|
|
1605
|
+
Object.entries(output).forEach(([_type, _data]) => {
|
|
1606
|
+
this._simulateEvent(_type, _data);
|
|
1607
|
+
});
|
|
1608
|
+
}, { get(fn, prop) {
|
|
1609
|
+
return async (data) => {
|
|
1610
|
+
return await fn(prop, data);
|
|
1611
|
+
};
|
|
1612
|
+
} });
|
|
1613
|
+
this.canRedo = () => {
|
|
1614
|
+
return !!this._crdtManager.doc.canRedo();
|
|
1615
|
+
};
|
|
1616
|
+
this.canUndo = () => {
|
|
1617
|
+
return !!this._crdtManager.doc.canUndo();
|
|
1618
|
+
};
|
|
1619
|
+
this.getConnection = () => {
|
|
1620
|
+
return Object.freeze(JSON.parse(JSON.stringify(this._state.connection)));
|
|
1621
|
+
};
|
|
1622
|
+
this.getMyPresence = () => {
|
|
1623
|
+
return this._usersManager.myPresence;
|
|
1624
|
+
};
|
|
1625
|
+
this.getMyself = () => {
|
|
1626
|
+
return this._usersManager.myself;
|
|
1627
|
+
};
|
|
1628
|
+
this.getOther = (connectionId) => {
|
|
1629
|
+
return this._usersManager.getOther(connectionId);
|
|
1630
|
+
};
|
|
1631
|
+
this.getOthers = () => {
|
|
1632
|
+
return this._usersManager.getOthers();
|
|
1633
|
+
};
|
|
1634
|
+
this.getStorage = (type) => {
|
|
1635
|
+
const sharedType = this._crdtManager.get(type);
|
|
1636
|
+
if (typeof sharedType === "undefined") return null;
|
|
1637
|
+
return sharedType;
|
|
1638
|
+
};
|
|
1639
|
+
this.redo = () => {
|
|
1640
|
+
this._crdtManager.doc.redo();
|
|
1641
|
+
};
|
|
1642
|
+
this.storageRoot = (fn) => {
|
|
1643
|
+
return this._crdtNotifier.subcribeRoot(fn);
|
|
1644
|
+
};
|
|
1645
|
+
this.subscribe = new Proxy((name, callback) => {
|
|
1646
|
+
return this._stateNotifier.subscribe(name, callback);
|
|
1647
|
+
}, { get: (fn, prop) => {
|
|
1648
|
+
if (prop === "connection") return (callback) => {
|
|
1649
|
+
return fn("connection", callback);
|
|
1650
|
+
};
|
|
1651
|
+
if (prop === "myPresence") return (callback) => {
|
|
1652
|
+
return fn("my-presence", callback);
|
|
1653
|
+
};
|
|
1654
|
+
if (prop === "myself") return (callback) => {
|
|
1655
|
+
return fn("myself", callback);
|
|
1656
|
+
};
|
|
1657
|
+
if (prop === "others") return (callback) => {
|
|
1658
|
+
return this._usersNotifier.subscribeOthers(callback);
|
|
1659
|
+
};
|
|
1660
|
+
if (prop === "storageLoaded") return (callback) => {
|
|
1661
|
+
return fn("storage-loaded", callback);
|
|
1662
|
+
};
|
|
1663
|
+
if (prop === "event") return this._event;
|
|
1664
|
+
if (prop === "other") return this._other;
|
|
1665
|
+
if (prop === "storage") return this.#_storage;
|
|
1666
|
+
} });
|
|
1667
|
+
this.transact = (fn, origin) => {
|
|
1668
|
+
const _origin = origin ?? this._state.connection.id;
|
|
1669
|
+
const crdtManager = this._crdtManager;
|
|
1670
|
+
/**
|
|
1671
|
+
* !HACK
|
|
1672
|
+
* @description Don't transact anything if there is no origin, because
|
|
1673
|
+
* that means the user isn't connected yet. This will mean events are
|
|
1674
|
+
* lost unfortunately.
|
|
1675
|
+
* @date September 23, 2023
|
|
1676
|
+
*/
|
|
1677
|
+
if (typeof _origin !== "string") return;
|
|
1678
|
+
if (!crdtManager) return;
|
|
1679
|
+
crdtManager.doc.transact(() => {
|
|
1680
|
+
fn(crdtManager.doc.get());
|
|
1681
|
+
}, _origin);
|
|
1682
|
+
};
|
|
1683
|
+
this.undo = () => {
|
|
1684
|
+
this._crdtManager.doc.undo();
|
|
1685
|
+
};
|
|
1686
|
+
this.updateMyPresence = (presence$1) => {
|
|
1687
|
+
const newPresence = typeof presence$1 === "function" ? presence$1(this.getMyPresence()) : presence$1;
|
|
1688
|
+
this._usersManager.updateMyPresence(newPresence);
|
|
1689
|
+
const myPresence = this._usersManager.myPresence;
|
|
1690
|
+
const myself = this._usersManager.myself ?? null;
|
|
1691
|
+
this._stateNotifier.subjects["my-presence"].next(myPresence);
|
|
1692
|
+
if (!!myself) this._stateNotifier.subjects["myself"].next(myself);
|
|
1693
|
+
};
|
|
1694
|
+
this._event = new Proxy((event, callback) => this._eventNotifier.subscribe(event, callback), { get(fn, prop) {
|
|
1695
|
+
return (callback) => fn(prop, callback);
|
|
1696
|
+
} });
|
|
1697
|
+
this._other = (connectionId, callback) => {
|
|
1698
|
+
const clientId = this._usersManager.getClientId(connectionId);
|
|
1699
|
+
if (!clientId) return () => void 0;
|
|
1700
|
+
return this._usersNotifier.subscribeOther(clientId, callback);
|
|
1701
|
+
};
|
|
1702
|
+
const { events, initialPresence, initialStorage, limits, presence, router } = options;
|
|
1703
|
+
this.id = room;
|
|
1704
|
+
this._events = events;
|
|
1705
|
+
this._limits = {
|
|
1706
|
+
presenceMaxSize: MAX_PRESENCE_SIZE_BYTES,
|
|
1707
|
+
...limits
|
|
1708
|
+
};
|
|
1709
|
+
this._router = router ?? new PluvRouter({});
|
|
1710
|
+
this._usersManager = new UsersManager({
|
|
1711
|
+
initialPresence,
|
|
1712
|
+
limits: this._limits,
|
|
1713
|
+
presence
|
|
1714
|
+
});
|
|
1715
|
+
this._crdtManager = new CrdtManager({ initialStorage });
|
|
1716
|
+
this._observeCrdt();
|
|
1717
|
+
}
|
|
1718
|
+
addEventListener(kind, handler) {
|
|
1719
|
+
return () => void 0;
|
|
1720
|
+
}
|
|
1721
|
+
getDoc() {
|
|
1722
|
+
return this._crdtManager.doc;
|
|
1723
|
+
}
|
|
1724
|
+
getStorageJson(type) {
|
|
1725
|
+
if (this._state.connection.id === null) return null;
|
|
1726
|
+
if (typeof type === "undefined") return this._crdtManager.doc.toJson();
|
|
1727
|
+
return this._crdtManager.doc.toJson(type);
|
|
1728
|
+
}
|
|
1729
|
+
getStorageLoaded() {
|
|
1730
|
+
return true;
|
|
1731
|
+
}
|
|
1732
|
+
_observeCrdt() {
|
|
1733
|
+
this._subscriptions.observeCrdt?.();
|
|
1734
|
+
if (!this._crdtManager) return;
|
|
1735
|
+
const unsubscribe = this._crdtManager.doc.subscribe((event) => {
|
|
1736
|
+
const origin = event.origin ?? null;
|
|
1737
|
+
if (!this._crdtManager) return;
|
|
1738
|
+
if (origin === "$storageUpdated") return;
|
|
1739
|
+
const sharedTypes = this._crdtManager.doc.get();
|
|
1740
|
+
const storageRoot = Object.keys(sharedTypes).reduce((acc, prop) => {
|
|
1741
|
+
if (!this._crdtManager) return acc;
|
|
1742
|
+
const serialized = this._crdtManager.doc.toJson(prop);
|
|
1743
|
+
this._crdtNotifier.subject(prop).next(serialized);
|
|
1744
|
+
return {
|
|
1745
|
+
...acc,
|
|
1746
|
+
[prop]: serialized
|
|
1747
|
+
};
|
|
1748
|
+
}, {});
|
|
1749
|
+
this._crdtNotifier.rootSubject.next(storageRoot);
|
|
1750
|
+
});
|
|
1751
|
+
this._subscriptions.observeCrdt = unsubscribe;
|
|
1752
|
+
}
|
|
1753
|
+
_simulateEvent(event, data) {
|
|
1754
|
+
if (!this._events) return;
|
|
1755
|
+
const type = event.toString();
|
|
1756
|
+
const resolver = this._events[type];
|
|
1757
|
+
if (!resolver) return;
|
|
1758
|
+
const result = resolver(data);
|
|
1759
|
+
Object.keys(result).forEach((type$1) => {
|
|
1760
|
+
const _type = type$1.toString();
|
|
1761
|
+
const data$1 = result[_type];
|
|
1762
|
+
if (!data$1) return;
|
|
1763
|
+
this._eventNotifier.subject(_type).next(data$1);
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1766
|
+
#_storage = new Proxy((key, callback) => this._crdtNotifier.subscribe(key, callback), { get: (fn, prop) => {
|
|
1767
|
+
if (!!prop) return (callback) => {
|
|
1768
|
+
return this._crdtNotifier.subcribeRoot(callback);
|
|
1769
|
+
};
|
|
1770
|
+
return (callback) => {
|
|
1771
|
+
return fn(prop, callback);
|
|
1772
|
+
};
|
|
1773
|
+
} });
|
|
1893
1774
|
};
|
|
1894
|
-
__storage2 = new WeakMap();
|
|
1895
1775
|
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
};
|
|
1906
|
-
export {
|
|
1907
|
-
AbstractStorageStore,
|
|
1908
|
-
ConnectionState3 as ConnectionState,
|
|
1909
|
-
MockedRoom,
|
|
1910
|
-
PluvClient,
|
|
1911
|
-
PluvRoom,
|
|
1912
|
-
createClient,
|
|
1913
|
-
infer,
|
|
1914
|
-
register
|
|
1776
|
+
//#endregion
|
|
1777
|
+
//#region src/register.ts
|
|
1778
|
+
const register = (params) => {
|
|
1779
|
+
const { authEndpoint, publicKey } = params;
|
|
1780
|
+
const wsEndpoint = ({ room }) => `wss://connect.pluv.io/api/room?room=${room}`;
|
|
1781
|
+
return {
|
|
1782
|
+
authEndpoint,
|
|
1783
|
+
publicKey,
|
|
1784
|
+
wsEndpoint
|
|
1785
|
+
};
|
|
1915
1786
|
};
|
|
1787
|
+
|
|
1788
|
+
//#endregion
|
|
1789
|
+
export { AbstractStorageStore, ConnectionState, MockedRoom, PluvClient, PluvRoom, createClient, infer, register };
|
|
1790
|
+
//# sourceMappingURL=index.mjs.map
|