@pluv/client 0.26.0 → 0.27.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 +9 -9
- package/CHANGELOG.md +125 -0
- package/dist/index.d.mts +109 -32
- package/dist/index.d.ts +109 -32
- package/dist/index.js +153 -26
- package/dist/index.mjs +153 -26
- package/package.json +7 -7
- package/src/AbstractRoom.ts +6 -4
- package/src/MockedRoom.ts +115 -27
- package/src/PluvClient.ts +71 -20
- package/src/PluvProcedure.ts +81 -0
- package/src/PluvRoom.ts +82 -30
- package/src/PluvRouter.ts +70 -0
- package/src/createClient.ts +10 -4
- package/src/enums.ts +7 -0
- package/src/index.ts +5 -9
- package/src/types.ts +43 -7
- package/src/utils/identity.ts +1 -0
- package/src/utils/index.ts +1 -0
package/dist/index.mjs
CHANGED
|
@@ -52,6 +52,41 @@ var AbstractStorageStore = class {
|
|
|
52
52
|
}
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
+
// src/PluvProcedure.ts
|
|
56
|
+
var PluvProcedure = class _PluvProcedure {
|
|
57
|
+
constructor(config = {}) {
|
|
58
|
+
this._broadcast = null;
|
|
59
|
+
this._input = null;
|
|
60
|
+
const { broadcast, input } = config;
|
|
61
|
+
this._broadcast = broadcast != null ? broadcast : null;
|
|
62
|
+
this._input = input != null ? input : null;
|
|
63
|
+
}
|
|
64
|
+
get config() {
|
|
65
|
+
var _a, _b, _c;
|
|
66
|
+
return {
|
|
67
|
+
broadcast: (_b = (_a = this._broadcast) == null ? void 0 : _a.bind(this)) != null ? _b : null,
|
|
68
|
+
input: (_c = this._input) != null ? _c : null,
|
|
69
|
+
resolver: this._resolver()
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
broadcast(resolver) {
|
|
73
|
+
const { broadcast } = this.config;
|
|
74
|
+
if (!!broadcast) throw new Error("Broadcast was already defined for this procedure");
|
|
75
|
+
return new _PluvProcedure(__spreadProps(__spreadValues({}, this.config), {
|
|
76
|
+
broadcast: resolver
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
input(input) {
|
|
80
|
+
return new _PluvProcedure({ input });
|
|
81
|
+
}
|
|
82
|
+
_resolver() {
|
|
83
|
+
return (data, context) => {
|
|
84
|
+
var _a;
|
|
85
|
+
return (_a = this._broadcast) == null ? void 0 : _a.call(this, data, context);
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
55
90
|
// src/CrdtManager.ts
|
|
56
91
|
import { noop } from "@pluv/crdt";
|
|
57
92
|
var CrdtManager = class {
|
|
@@ -179,6 +214,21 @@ var OtherNotifier = class {
|
|
|
179
214
|
}
|
|
180
215
|
};
|
|
181
216
|
|
|
217
|
+
// src/PluvRouter.ts
|
|
218
|
+
var PluvRouter = class _PluvRouter {
|
|
219
|
+
constructor(events) {
|
|
220
|
+
this._defs = { events: {} };
|
|
221
|
+
this._defs = { events };
|
|
222
|
+
}
|
|
223
|
+
static merge(...routers) {
|
|
224
|
+
const events = Object.assign(/* @__PURE__ */ Object.create(null), ...routers.map((router) => router._defs.events));
|
|
225
|
+
if (Object.keys(events).some((key) => key.startsWith("$"))) {
|
|
226
|
+
throw new Error('Procedures may not start with "$"');
|
|
227
|
+
}
|
|
228
|
+
return new _PluvRouter(events);
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
182
232
|
// src/StateNotifier.ts
|
|
183
233
|
import { makeSubject as makeSubject4, subscribe as subscribe4 } from "wonka";
|
|
184
234
|
var StateNotifier = class {
|
|
@@ -317,7 +367,7 @@ var UsersManager = class {
|
|
|
317
367
|
}
|
|
318
368
|
};
|
|
319
369
|
|
|
320
|
-
// src/
|
|
370
|
+
// src/enums.ts
|
|
321
371
|
var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
|
|
322
372
|
ConnectionState2["Closed"] = "Closed";
|
|
323
373
|
ConnectionState2["Connecting"] = "Connecting";
|
|
@@ -367,6 +417,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
367
417
|
metadata,
|
|
368
418
|
onAuthorizationFail,
|
|
369
419
|
presence,
|
|
420
|
+
router,
|
|
370
421
|
wsEndpoint
|
|
371
422
|
} = options;
|
|
372
423
|
super(room);
|
|
@@ -400,17 +451,37 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
400
451
|
this._windowListeners = null;
|
|
401
452
|
this._wsListeners = null;
|
|
402
453
|
this.broadcast = new Proxy(
|
|
403
|
-
(event, data) => {
|
|
454
|
+
(event, data) => __async(this, null, function* () {
|
|
404
455
|
if (!this._state.webSocket) return;
|
|
405
456
|
if (this._state.connection.state !== "Open" /* Open */) return;
|
|
406
457
|
const type = event.toString();
|
|
407
|
-
this.
|
|
408
|
-
|
|
458
|
+
const procedure = this._router._defs.events[type];
|
|
459
|
+
if (!(procedure == null ? void 0 : procedure.config.broadcast)) {
|
|
460
|
+
this._sendMessage({ data, type });
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
const myself = this._usersManager.myself;
|
|
464
|
+
if (!myself) return;
|
|
465
|
+
const parsed = procedure.config.input ? procedure.config.input.parse(data) : data;
|
|
466
|
+
const context = {
|
|
467
|
+
doc: this._crdtManager.doc,
|
|
468
|
+
others: this._usersManager.getOthers(),
|
|
469
|
+
room: this.id,
|
|
470
|
+
user: myself
|
|
471
|
+
};
|
|
472
|
+
const output = yield procedure.config.broadcast(
|
|
473
|
+
parsed,
|
|
474
|
+
context
|
|
475
|
+
);
|
|
476
|
+
Object.entries(output).forEach(([_type, _data]) => {
|
|
477
|
+
this._sendMessage({ data: _data, type: _type });
|
|
478
|
+
});
|
|
479
|
+
}),
|
|
409
480
|
{
|
|
410
481
|
get(fn, prop) {
|
|
411
|
-
return (data) => {
|
|
412
|
-
return fn(prop, data);
|
|
413
|
-
};
|
|
482
|
+
return (data) => __async(this, null, function* () {
|
|
483
|
+
return yield fn(prop, data);
|
|
484
|
+
});
|
|
414
485
|
}
|
|
415
486
|
}
|
|
416
487
|
);
|
|
@@ -485,7 +556,10 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
485
556
|
const myself = (_a = this._usersManager.myself) != null ? _a : null;
|
|
486
557
|
this._stateNotifier.subjects["my-presence"].next(myPresence);
|
|
487
558
|
!!myself && this._stateNotifier.subjects["myself"].next(myself);
|
|
488
|
-
this.broadcast(
|
|
559
|
+
this.broadcast(
|
|
560
|
+
"$UPDATE_PRESENCE",
|
|
561
|
+
{ presence: newPresence }
|
|
562
|
+
);
|
|
489
563
|
};
|
|
490
564
|
this._flattenStorageStore = debounce(
|
|
491
565
|
() => __async(this, null, function* () {
|
|
@@ -517,6 +591,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
517
591
|
onAuthorizationFail == null ? void 0 : onAuthorizationFail(error);
|
|
518
592
|
}
|
|
519
593
|
};
|
|
594
|
+
this._router = router != null ? router : new PluvRouter({});
|
|
520
595
|
this._usersManager = new UsersManager({ initialPresence, presence });
|
|
521
596
|
this._crdtManager = new CrdtManager({ initialStorage });
|
|
522
597
|
}
|
|
@@ -1085,9 +1160,11 @@ var PluvClient = class {
|
|
|
1085
1160
|
authEndpoint: this._authEndpoint,
|
|
1086
1161
|
debug: options.debug,
|
|
1087
1162
|
initialPresence: options.initialPresence,
|
|
1088
|
-
initialStorage:
|
|
1163
|
+
initialStorage: this._initialStorage,
|
|
1089
1164
|
metadata: options.metadata,
|
|
1090
1165
|
onAuthorizationFail: options.onAuthorizationFail,
|
|
1166
|
+
presence: this._presence,
|
|
1167
|
+
router: options.router,
|
|
1091
1168
|
wsEndpoint: this._wsEndpoint
|
|
1092
1169
|
});
|
|
1093
1170
|
this._rooms.set(room, newRoom);
|
|
@@ -1116,11 +1193,19 @@ var PluvClient = class {
|
|
|
1116
1193
|
this._rooms.delete(toLeave.id);
|
|
1117
1194
|
this._logDebug(`Left and deleted room: ${room}`);
|
|
1118
1195
|
});
|
|
1119
|
-
const { authEndpoint, debug = false, wsEndpoint } = options;
|
|
1196
|
+
const { authEndpoint, debug = false, initialStorage, presence, wsEndpoint } = options;
|
|
1120
1197
|
this._authEndpoint = authEndpoint;
|
|
1121
1198
|
this._debug = debug;
|
|
1199
|
+
this._initialStorage = initialStorage;
|
|
1200
|
+
this._presence = presence;
|
|
1122
1201
|
this._wsEndpoint = wsEndpoint;
|
|
1123
1202
|
}
|
|
1203
|
+
get procedure() {
|
|
1204
|
+
return new PluvProcedure();
|
|
1205
|
+
}
|
|
1206
|
+
router(events) {
|
|
1207
|
+
return new PluvRouter(events);
|
|
1208
|
+
}
|
|
1124
1209
|
_logDebug(...data) {
|
|
1125
1210
|
var _a;
|
|
1126
1211
|
if (((_a = process == null ? void 0 : process.env) == null ? void 0 : _a.NODE_ENV) === "production") return;
|
|
@@ -1136,7 +1221,7 @@ var createClient = (options) => {
|
|
|
1136
1221
|
// src/MockedRoom.ts
|
|
1137
1222
|
var MockedRoom = class extends AbstractRoom {
|
|
1138
1223
|
constructor(room, options) {
|
|
1139
|
-
const { events, initialPresence, initialStorage, presence } = options;
|
|
1224
|
+
const { events, initialPresence, initialStorage, presence, router } = options;
|
|
1140
1225
|
super(room);
|
|
1141
1226
|
this._crdtNotifier = new CrdtNotifier();
|
|
1142
1227
|
this._eventNotifier = new EventNotifier();
|
|
@@ -1157,15 +1242,55 @@ var MockedRoom = class extends AbstractRoom {
|
|
|
1157
1242
|
this._subscriptions = {
|
|
1158
1243
|
observeCrdt: null
|
|
1159
1244
|
};
|
|
1245
|
+
this.broadcast = new Proxy(
|
|
1246
|
+
(event, data) => __async(this, null, function* () {
|
|
1247
|
+
if (!this._state.webSocket) return;
|
|
1248
|
+
if (this._state.connection.state !== "Open" /* Open */) return;
|
|
1249
|
+
const type = event.toString();
|
|
1250
|
+
const procedure = this._router._defs.events[type];
|
|
1251
|
+
if (!(procedure == null ? void 0 : procedure.config.broadcast)) {
|
|
1252
|
+
this._simulateEvent(type, data);
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
const myself = this._usersManager.myself;
|
|
1256
|
+
if (!myself) return;
|
|
1257
|
+
const parsed = procedure.config.input ? procedure.config.input.parse(data) : data;
|
|
1258
|
+
const context = {
|
|
1259
|
+
doc: this._crdtManager.doc,
|
|
1260
|
+
others: this._usersManager.getOthers(),
|
|
1261
|
+
room: this.id,
|
|
1262
|
+
user: myself
|
|
1263
|
+
};
|
|
1264
|
+
const output = yield procedure.config.broadcast(
|
|
1265
|
+
parsed,
|
|
1266
|
+
context
|
|
1267
|
+
);
|
|
1268
|
+
Object.entries(output).forEach(([_type, _data]) => {
|
|
1269
|
+
this._simulateEvent(_type, _data);
|
|
1270
|
+
});
|
|
1271
|
+
}),
|
|
1272
|
+
{
|
|
1273
|
+
get(fn, prop) {
|
|
1274
|
+
return (data) => __async(this, null, function* () {
|
|
1275
|
+
return yield fn(prop, data);
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
);
|
|
1160
1280
|
this.canRedo = () => {
|
|
1161
1281
|
return !!this._crdtManager.doc.canRedo();
|
|
1162
1282
|
};
|
|
1163
1283
|
this.canUndo = () => {
|
|
1164
1284
|
return !!this._crdtManager.doc.canUndo();
|
|
1165
1285
|
};
|
|
1166
|
-
this.event = (
|
|
1167
|
-
|
|
1168
|
-
|
|
1286
|
+
this.event = new Proxy(
|
|
1287
|
+
(event, callback) => this._eventNotifier.subscribe(event, callback),
|
|
1288
|
+
{
|
|
1289
|
+
get(fn, prop) {
|
|
1290
|
+
return (callback) => fn(prop, callback);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
);
|
|
1169
1294
|
this.getConnection = () => {
|
|
1170
1295
|
return Object.freeze(JSON.parse(JSON.stringify(this._state.connection)));
|
|
1171
1296
|
};
|
|
@@ -1224,6 +1349,7 @@ var MockedRoom = class extends AbstractRoom {
|
|
|
1224
1349
|
!!myself && this._stateNotifier.subjects["myself"].next(myself);
|
|
1225
1350
|
};
|
|
1226
1351
|
this._events = events;
|
|
1352
|
+
this._router = router != null ? router : new PluvRouter({});
|
|
1227
1353
|
this._usersManager = new UsersManager({
|
|
1228
1354
|
initialPresence,
|
|
1229
1355
|
presence
|
|
@@ -1236,18 +1362,6 @@ var MockedRoom = class extends AbstractRoom {
|
|
|
1236
1362
|
get storageLoaded() {
|
|
1237
1363
|
return true;
|
|
1238
1364
|
}
|
|
1239
|
-
broadcast(event, data) {
|
|
1240
|
-
if (!this._events) return;
|
|
1241
|
-
const resolver = this._events[event];
|
|
1242
|
-
if (!resolver) return;
|
|
1243
|
-
const result = resolver(data);
|
|
1244
|
-
Object.keys(result).forEach((type) => {
|
|
1245
|
-
const _type = type.toString();
|
|
1246
|
-
const data2 = result[_type];
|
|
1247
|
-
if (!data2) return;
|
|
1248
|
-
this._eventNotifier.subject(_type).next(data2);
|
|
1249
|
-
});
|
|
1250
|
-
}
|
|
1251
1365
|
getDoc() {
|
|
1252
1366
|
return this._crdtManager.doc;
|
|
1253
1367
|
}
|
|
@@ -1279,6 +1393,19 @@ var MockedRoom = class extends AbstractRoom {
|
|
|
1279
1393
|
});
|
|
1280
1394
|
this._subscriptions.observeCrdt = unsubscribe;
|
|
1281
1395
|
}
|
|
1396
|
+
_simulateEvent(event, data) {
|
|
1397
|
+
if (!this._events) return;
|
|
1398
|
+
const type = event.toString();
|
|
1399
|
+
const resolver = this._events[type];
|
|
1400
|
+
if (!resolver) return;
|
|
1401
|
+
const result = resolver(data);
|
|
1402
|
+
Object.keys(result).forEach((type2) => {
|
|
1403
|
+
const _type = type2.toString();
|
|
1404
|
+
const data2 = result[_type];
|
|
1405
|
+
if (!data2) return;
|
|
1406
|
+
this._eventNotifier.subject(_type).next(data2);
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1282
1409
|
};
|
|
1283
1410
|
export {
|
|
1284
1411
|
AbstractRoom,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pluv/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "@pluv/io frontend client",
|
|
5
5
|
"author": "leedavidcs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,15 +18,15 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"wonka": "^6.3.4",
|
|
21
|
-
"@pluv/crdt": "^0.
|
|
22
|
-
"@pluv/types": "^0.
|
|
21
|
+
"@pluv/crdt": "^0.27.0",
|
|
22
|
+
"@pluv/types": "^0.27.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"eslint": "^8.57.0",
|
|
26
|
-
"tsup": "^8.
|
|
27
|
-
"typescript": "^5.
|
|
28
|
-
"@pluv/tsconfig": "^0.
|
|
29
|
-
"eslint-config-pluv": "^0.
|
|
26
|
+
"tsup": "^8.3.0",
|
|
27
|
+
"typescript": "^5.6.2",
|
|
28
|
+
"@pluv/tsconfig": "^0.27.0",
|
|
29
|
+
"eslint-config-pluv": "^0.27.0"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "tsup src/index.ts --format esm,cjs --dts",
|
package/src/AbstractRoom.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { AbstractCrdtDoc, CrdtType, InferCrdtJson } from "@pluv/crdt";
|
|
|
2
2
|
import type { IOLike, Id, InferIOInput, InferIOOutput, JsonObject } from "@pluv/types";
|
|
3
3
|
import type { EventNotifierSubscriptionCallback } from "./EventNotifier";
|
|
4
4
|
import type { OtherNotifierSubscriptionCallback } from "./OtherNotifier";
|
|
5
|
+
import type { MergeEvents, PluvRouterEventConfig } from "./PluvRouter";
|
|
5
6
|
import type { StateNotifierSubjects, SubscriptionCallback } from "./StateNotifier";
|
|
6
7
|
import type { UpdateMyPresenceAction, UserInfo, WebSocketConnection } from "./types";
|
|
7
8
|
|
|
@@ -9,6 +10,7 @@ export abstract class AbstractRoom<
|
|
|
9
10
|
TIO extends IOLike,
|
|
10
11
|
TPresence extends JsonObject = {},
|
|
11
12
|
TStorage extends Record<string, CrdtType<any, any>> = {},
|
|
13
|
+
TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {},
|
|
12
14
|
> {
|
|
13
15
|
public readonly id: string;
|
|
14
16
|
|
|
@@ -18,9 +20,9 @@ export abstract class AbstractRoom<
|
|
|
18
20
|
|
|
19
21
|
public abstract storageLoaded: boolean;
|
|
20
22
|
|
|
21
|
-
public abstract broadcast<TEvent extends keyof InferIOInput<TIO
|
|
23
|
+
public abstract broadcast<TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(
|
|
22
24
|
event: TEvent,
|
|
23
|
-
data: Id<InferIOInput<TIO
|
|
25
|
+
data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>,
|
|
24
26
|
): void;
|
|
25
27
|
|
|
26
28
|
public abstract canRedo(): boolean;
|
|
@@ -29,9 +31,9 @@ export abstract class AbstractRoom<
|
|
|
29
31
|
|
|
30
32
|
public abstract getDoc(): AbstractCrdtDoc<TStorage>;
|
|
31
33
|
|
|
32
|
-
public abstract event<TEvent extends keyof InferIOOutput<TIO
|
|
34
|
+
public abstract event<TEvent extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(
|
|
33
35
|
event: TEvent,
|
|
34
|
-
callback: EventNotifierSubscriptionCallback<
|
|
36
|
+
callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, TEvent>,
|
|
35
37
|
): () => void;
|
|
36
38
|
|
|
37
39
|
public abstract getConnection(): WebSocketConnection;
|
package/src/MockedRoom.ts
CHANGED
|
@@ -8,18 +8,23 @@ import type { EventNotifierSubscriptionCallback } from "./EventNotifier";
|
|
|
8
8
|
import { EventNotifier } from "./EventNotifier";
|
|
9
9
|
import type { OtherNotifierSubscriptionCallback } from "./OtherNotifier";
|
|
10
10
|
import { OtherNotifier } from "./OtherNotifier";
|
|
11
|
+
import { PluvProcedure } from "./PluvProcedure";
|
|
12
|
+
import type { MergeEvents, PluvRouterEventConfig } from "./PluvRouter";
|
|
13
|
+
import { PluvRouter } from "./PluvRouter";
|
|
11
14
|
import type { StateNotifierSubjects, SubscriptionCallback } from "./StateNotifier";
|
|
12
15
|
import { StateNotifier } from "./StateNotifier";
|
|
13
16
|
import type { UsersManagerConfig } from "./UsersManager";
|
|
14
17
|
import { UsersManager } from "./UsersManager";
|
|
18
|
+
import { ConnectionState } from "./enums";
|
|
15
19
|
import type {
|
|
20
|
+
EventResolver,
|
|
21
|
+
EventResolverContext,
|
|
16
22
|
InternalSubscriptions,
|
|
17
23
|
UpdateMyPresenceAction,
|
|
18
24
|
UserInfo,
|
|
19
25
|
WebSocketConnection,
|
|
20
26
|
WebSocketState,
|
|
21
27
|
} from "./types";
|
|
22
|
-
import { ConnectionState } from "./types";
|
|
23
28
|
|
|
24
29
|
export type MockedRoomEvents<TIO extends IOLike> = Partial<{
|
|
25
30
|
[P in keyof InferIOInput<TIO>]: (data: Id<InferIOInput<TIO>[P]>) => Partial<InferIOOutput<TIO>>;
|
|
@@ -29,19 +34,25 @@ export type MockedRoomConfig<
|
|
|
29
34
|
TIO extends IOLike,
|
|
30
35
|
TPresence extends JsonObject = {},
|
|
31
36
|
TStorage extends Record<string, CrdtType<any, any>> = {},
|
|
32
|
-
|
|
37
|
+
TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {},
|
|
38
|
+
> = {
|
|
39
|
+
events?: MockedRoomEvents<MergeEvents<TEvents, TIO>>;
|
|
40
|
+
router?: PluvRouter<TIO, TPresence, TStorage, TEvents>;
|
|
41
|
+
} & Pick<CrdtManagerOptions<TStorage>, "initialStorage"> &
|
|
33
42
|
UsersManagerConfig<TPresence>;
|
|
34
43
|
|
|
35
44
|
export class MockedRoom<
|
|
36
45
|
TIO extends IOLike,
|
|
37
46
|
TPresence extends JsonObject = {},
|
|
38
47
|
TStorage extends Record<string, CrdtType<any, any>> = {},
|
|
48
|
+
TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {},
|
|
39
49
|
> extends AbstractRoom<TIO, TPresence, TStorage> {
|
|
40
50
|
private _crdtManager: CrdtManager<TStorage>;
|
|
41
51
|
private _crdtNotifier = new CrdtNotifier<TStorage>();
|
|
42
|
-
private _eventNotifier = new EventNotifier<TIO
|
|
43
|
-
private _events?: MockedRoomEvents<TIO
|
|
52
|
+
private _eventNotifier = new EventNotifier<MergeEvents<TEvents, TIO>>();
|
|
53
|
+
private _events?: MockedRoomEvents<MergeEvents<TEvents, TIO>>;
|
|
44
54
|
private _otherNotifier = new OtherNotifier<TIO>();
|
|
55
|
+
private _router: PluvRouter<TIO, TPresence, TStorage, TEvents>;
|
|
45
56
|
private _state: WebSocketState<TIO> = {
|
|
46
57
|
authorization: {
|
|
47
58
|
token: null,
|
|
@@ -60,13 +71,14 @@ export class MockedRoom<
|
|
|
60
71
|
};
|
|
61
72
|
private _usersManager: UsersManager<TIO, TPresence>;
|
|
62
73
|
|
|
63
|
-
constructor(room: string, options: MockedRoomConfig<TIO, TPresence, TStorage>) {
|
|
64
|
-
const { events, initialPresence, initialStorage, presence } = options;
|
|
74
|
+
constructor(room: string, options: MockedRoomConfig<TIO, TPresence, TStorage, TEvents>) {
|
|
75
|
+
const { events, initialPresence, initialStorage, presence, router } = options;
|
|
65
76
|
|
|
66
77
|
super(room);
|
|
67
78
|
|
|
68
79
|
this._events = events;
|
|
69
80
|
|
|
81
|
+
this._router = router ?? (new PluvRouter({}) as PluvRouter<TIO, TPresence, TStorage, TEvents>);
|
|
70
82
|
this._usersManager = new UsersManager<TIO, TPresence>({
|
|
71
83
|
initialPresence,
|
|
72
84
|
presence,
|
|
@@ -83,24 +95,64 @@ export class MockedRoom<
|
|
|
83
95
|
return true;
|
|
84
96
|
}
|
|
85
97
|
|
|
86
|
-
public broadcast
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
public broadcast = new Proxy(
|
|
99
|
+
async <TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(
|
|
100
|
+
event: TEvent,
|
|
101
|
+
data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>,
|
|
102
|
+
): Promise<void> => {
|
|
103
|
+
if (!this._state.webSocket) return;
|
|
104
|
+
if (this._state.connection.state !== ConnectionState.Open) return;
|
|
105
|
+
|
|
106
|
+
const type = event.toString();
|
|
107
|
+
|
|
108
|
+
const procedure = this._router._defs.events[type] as PluvProcedure<
|
|
109
|
+
TIO,
|
|
110
|
+
any,
|
|
111
|
+
any,
|
|
112
|
+
TPresence,
|
|
113
|
+
TStorage
|
|
114
|
+
> | null;
|
|
115
|
+
|
|
116
|
+
if (!procedure?.config.broadcast) {
|
|
117
|
+
this._simulateEvent(type as TEvent, data);
|
|
118
|
+
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const myself = this._usersManager.myself;
|
|
123
|
+
|
|
124
|
+
if (!myself) return;
|
|
125
|
+
|
|
126
|
+
const parsed = procedure.config.input ? procedure.config.input.parse(data) : data;
|
|
127
|
+
const context: EventResolverContext<TIO, TPresence, TStorage> = {
|
|
128
|
+
doc: this._crdtManager.doc,
|
|
129
|
+
others: this._usersManager.getOthers(),
|
|
130
|
+
room: this.id,
|
|
131
|
+
user: myself,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const output = await (procedure.config.broadcast as EventResolver<TIO, any, any, TPresence, TStorage>)(
|
|
135
|
+
parsed,
|
|
136
|
+
context,
|
|
137
|
+
);
|
|
100
138
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
139
|
+
Object.entries(output).forEach(([_type, _data]) => {
|
|
140
|
+
this._simulateEvent(_type as TEvent, _data as any);
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
get(fn, prop) {
|
|
145
|
+
return async (data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[any]>): Promise<void> => {
|
|
146
|
+
return await fn(prop, data);
|
|
147
|
+
};
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
) as (<TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(
|
|
151
|
+
event: TEvent,
|
|
152
|
+
data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>,
|
|
153
|
+
) => Promise<void>) & {
|
|
154
|
+
[event in keyof InferIOInput<TIO>]: (data: Id<InferIOInput<TIO>[event]>) => Promise<void>;
|
|
155
|
+
};
|
|
104
156
|
|
|
105
157
|
public canRedo = (): boolean => {
|
|
106
158
|
return !!this._crdtManager.doc.canRedo();
|
|
@@ -110,11 +162,24 @@ export class MockedRoom<
|
|
|
110
162
|
return !!this._crdtManager.doc.canUndo();
|
|
111
163
|
};
|
|
112
164
|
|
|
113
|
-
public event =
|
|
165
|
+
public event = new Proxy(
|
|
166
|
+
<TEvent extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(
|
|
167
|
+
event: TEvent,
|
|
168
|
+
callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, any>,
|
|
169
|
+
): (() => void) => this._eventNotifier.subscribe(event, callback),
|
|
170
|
+
{
|
|
171
|
+
get(fn, prop) {
|
|
172
|
+
return (callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, any>): (() => void) =>
|
|
173
|
+
fn(prop as any, callback);
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
) as (<TEvent extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(
|
|
114
177
|
event: TEvent,
|
|
115
|
-
callback: EventNotifierSubscriptionCallback<
|
|
116
|
-
)
|
|
117
|
-
|
|
178
|
+
callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, TEvent>,
|
|
179
|
+
) => () => void) & {
|
|
180
|
+
[event in keyof InferIOOutput<MergeEvents<TEvents, TIO>>]: (
|
|
181
|
+
callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, any>,
|
|
182
|
+
) => () => void;
|
|
118
183
|
};
|
|
119
184
|
|
|
120
185
|
public getConnection = (): WebSocketConnection => {
|
|
@@ -260,4 +325,27 @@ export class MockedRoom<
|
|
|
260
325
|
|
|
261
326
|
this._subscriptions.observeCrdt = unsubscribe;
|
|
262
327
|
}
|
|
328
|
+
|
|
329
|
+
private _simulateEvent<TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(
|
|
330
|
+
event: TEvent,
|
|
331
|
+
data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>,
|
|
332
|
+
) {
|
|
333
|
+
if (!this._events) return;
|
|
334
|
+
|
|
335
|
+
const type = event.toString();
|
|
336
|
+
const resolver = this._events[type as TEvent];
|
|
337
|
+
|
|
338
|
+
if (!resolver) return;
|
|
339
|
+
|
|
340
|
+
const result = resolver(data);
|
|
341
|
+
|
|
342
|
+
Object.keys(result).forEach((type) => {
|
|
343
|
+
const _type = type.toString() as keyof Partial<InferIOOutput<TIO>>;
|
|
344
|
+
const data = result[_type] as any;
|
|
345
|
+
|
|
346
|
+
if (!data) return;
|
|
347
|
+
|
|
348
|
+
this._eventNotifier.subject(_type).next(data);
|
|
349
|
+
});
|
|
350
|
+
}
|
|
263
351
|
}
|