@pluv/io 0.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/.eslintrc.js +4 -0
- package/.turbo/turbo-build.log +17 -0
- package/dist/index.d.ts +257 -0
- package/dist/index.js +891 -0
- package/dist/index.mjs +857 -0
- package/package.json +34 -0
- package/src/AbstractPersistance.ts +31 -0
- package/src/AbstractPlatform.ts +40 -0
- package/src/AbstractPubSub.ts +23 -0
- package/src/AbstractWebSocket.ts +117 -0
- package/src/IORoom.ts +630 -0
- package/src/Persistance.ts +77 -0
- package/src/PluvIO.ts +322 -0
- package/src/PubSub.ts +79 -0
- package/src/authorize.ts +81 -0
- package/src/createIO.ts +51 -0
- package/src/index.ts +34 -0
- package/src/types.ts +93 -0
- package/tsconfig.json +5 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,891 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __defProps = Object.defineProperties;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
9
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
10
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
11
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
12
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
13
|
+
var __spreadValues = (a, b) => {
|
|
14
|
+
for (var prop in b || (b = {}))
|
|
15
|
+
if (__hasOwnProp.call(b, prop))
|
|
16
|
+
__defNormalProp(a, prop, b[prop]);
|
|
17
|
+
if (__getOwnPropSymbols)
|
|
18
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
19
|
+
if (__propIsEnum.call(b, prop))
|
|
20
|
+
__defNormalProp(a, prop, b[prop]);
|
|
21
|
+
}
|
|
22
|
+
return a;
|
|
23
|
+
};
|
|
24
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
25
|
+
var __objRest = (source, exclude) => {
|
|
26
|
+
var target = {};
|
|
27
|
+
for (var prop in source)
|
|
28
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
29
|
+
target[prop] = source[prop];
|
|
30
|
+
if (source != null && __getOwnPropSymbols)
|
|
31
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
32
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
33
|
+
target[prop] = source[prop];
|
|
34
|
+
}
|
|
35
|
+
return target;
|
|
36
|
+
};
|
|
37
|
+
var __export = (target, all) => {
|
|
38
|
+
for (var name in all)
|
|
39
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
40
|
+
};
|
|
41
|
+
var __copyProps = (to, from, except, desc) => {
|
|
42
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
43
|
+
for (let key of __getOwnPropNames(from))
|
|
44
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
45
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
46
|
+
}
|
|
47
|
+
return to;
|
|
48
|
+
};
|
|
49
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
50
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
51
|
+
mod
|
|
52
|
+
));
|
|
53
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
54
|
+
var __async = (__this, __arguments, generator) => {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
var fulfilled = (value) => {
|
|
57
|
+
try {
|
|
58
|
+
step(generator.next(value));
|
|
59
|
+
} catch (e) {
|
|
60
|
+
reject(e);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var rejected = (value) => {
|
|
64
|
+
try {
|
|
65
|
+
step(generator.throw(value));
|
|
66
|
+
} catch (e) {
|
|
67
|
+
reject(e);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
71
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// src/index.ts
|
|
76
|
+
var src_exports = {};
|
|
77
|
+
__export(src_exports, {
|
|
78
|
+
AbstractPersistance: () => AbstractPersistance,
|
|
79
|
+
AbstractPlatform: () => AbstractPlatform,
|
|
80
|
+
AbstractPubSub: () => AbstractPubSub,
|
|
81
|
+
AbstractWebSocket: () => AbstractWebSocket,
|
|
82
|
+
createIO: () => createIO
|
|
83
|
+
});
|
|
84
|
+
module.exports = __toCommonJS(src_exports);
|
|
85
|
+
|
|
86
|
+
// src/AbstractPersistance.ts
|
|
87
|
+
var AbstractPersistance = class {
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// src/Persistance.ts
|
|
91
|
+
var Persistance = class extends AbstractPersistance {
|
|
92
|
+
constructor() {
|
|
93
|
+
super(...arguments);
|
|
94
|
+
this._storages = /* @__PURE__ */ new Map();
|
|
95
|
+
this._users = /* @__PURE__ */ new Map();
|
|
96
|
+
}
|
|
97
|
+
addUser(room, connectionId, user) {
|
|
98
|
+
var _a;
|
|
99
|
+
const users = (_a = this._users.get(room)) != null ? _a : /* @__PURE__ */ new Map();
|
|
100
|
+
this._users.set(room, users);
|
|
101
|
+
users.set(connectionId, user);
|
|
102
|
+
return Promise.resolve();
|
|
103
|
+
}
|
|
104
|
+
deleteStorageState(room) {
|
|
105
|
+
this._storages.delete(room);
|
|
106
|
+
return Promise.resolve();
|
|
107
|
+
}
|
|
108
|
+
deleteUser(room, connectionId) {
|
|
109
|
+
return __async(this, null, function* () {
|
|
110
|
+
const users = this._users.get(room);
|
|
111
|
+
if (!users)
|
|
112
|
+
return;
|
|
113
|
+
users.delete(connectionId);
|
|
114
|
+
if (!users.size) {
|
|
115
|
+
yield this.deleteUsers(room);
|
|
116
|
+
yield this.deleteStorageState(room);
|
|
117
|
+
}
|
|
118
|
+
return Promise.resolve();
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
deleteUsers(room) {
|
|
122
|
+
this._users.delete(room);
|
|
123
|
+
return Promise.resolve();
|
|
124
|
+
}
|
|
125
|
+
getStorageState(room) {
|
|
126
|
+
var _a;
|
|
127
|
+
const storage = (_a = this._storages.get(room)) != null ? _a : null;
|
|
128
|
+
return Promise.resolve(storage);
|
|
129
|
+
}
|
|
130
|
+
getUser(room, connectionId) {
|
|
131
|
+
var _a;
|
|
132
|
+
const user = (_a = this._users.get(room)) == null ? void 0 : _a.get(connectionId);
|
|
133
|
+
return Promise.resolve(user != null ? user : null);
|
|
134
|
+
}
|
|
135
|
+
getUsers(room) {
|
|
136
|
+
const users = this._users.get(room);
|
|
137
|
+
if (!users)
|
|
138
|
+
return Promise.resolve([]);
|
|
139
|
+
return Promise.resolve(Array.from(users.values()));
|
|
140
|
+
}
|
|
141
|
+
setStorageState(room, state) {
|
|
142
|
+
this._storages.set(room, state);
|
|
143
|
+
return Promise.resolve();
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// src/PubSub.ts
|
|
148
|
+
var import_wonka = require("wonka");
|
|
149
|
+
|
|
150
|
+
// src/AbstractPubSub.ts
|
|
151
|
+
var AbstractPubSub = class {
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// src/PubSub.ts
|
|
155
|
+
var PubSub = class extends AbstractPubSub {
|
|
156
|
+
constructor() {
|
|
157
|
+
super(...arguments);
|
|
158
|
+
this._currentSubscriptionId = 0;
|
|
159
|
+
this._subjects = /* @__PURE__ */ new Map();
|
|
160
|
+
this._subscriptionsRefs = /* @__PURE__ */ new Map();
|
|
161
|
+
this._unsubscriptions = /* @__PURE__ */ new Map();
|
|
162
|
+
}
|
|
163
|
+
publish(roomName, payload) {
|
|
164
|
+
return __async(this, null, function* () {
|
|
165
|
+
const subject = this._subjects.get(roomName);
|
|
166
|
+
if (!subject)
|
|
167
|
+
return;
|
|
168
|
+
Promise.resolve(subject.next(payload));
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
subscribe(roomName, onMessage) {
|
|
172
|
+
var _a;
|
|
173
|
+
const id = this._currentSubscriptionId++;
|
|
174
|
+
const refs = (_a = this._subscriptionsRefs.get(roomName)) != null ? _a : [];
|
|
175
|
+
const subject = (0, import_wonka.makeSubject)();
|
|
176
|
+
this._subjects.set(roomName, subject);
|
|
177
|
+
this._subscriptionsRefs.set(roomName, [...refs, id]);
|
|
178
|
+
const unsubscribe = (0, import_wonka.subscribe)(onMessage)(subject.source).unsubscribe;
|
|
179
|
+
this._unsubscriptions.set(id, [roomName, unsubscribe]);
|
|
180
|
+
return Promise.resolve(id);
|
|
181
|
+
}
|
|
182
|
+
unsubscribe(subscriptionId) {
|
|
183
|
+
var _a;
|
|
184
|
+
const [roomName, unsubscribe] = (_a = this._unsubscriptions.get(subscriptionId)) != null ? _a : [];
|
|
185
|
+
if (!roomName || !unsubscribe)
|
|
186
|
+
return;
|
|
187
|
+
const refs = this._subscriptionsRefs.get(roomName);
|
|
188
|
+
if (!refs)
|
|
189
|
+
return;
|
|
190
|
+
if (refs.length === 1) {
|
|
191
|
+
this._subjects.delete(roomName);
|
|
192
|
+
this._subscriptionsRefs.delete(roomName);
|
|
193
|
+
this._unsubscriptions.delete(subscriptionId);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const index = refs.indexOf(subscriptionId);
|
|
197
|
+
this._subscriptionsRefs.set(
|
|
198
|
+
roomName,
|
|
199
|
+
index === -1 ? refs : [...refs.slice(0, index), ...refs.slice(index + 1)]
|
|
200
|
+
);
|
|
201
|
+
unsubscribe();
|
|
202
|
+
this._unsubscriptions.delete(subscriptionId);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// src/AbstractPlatform.ts
|
|
207
|
+
var AbstractPlatform = class {
|
|
208
|
+
constructor(config = {}) {
|
|
209
|
+
const { persistance, pubSub } = config;
|
|
210
|
+
this.persistance = persistance != null ? persistance : new Persistance();
|
|
211
|
+
this.pubSub = pubSub != null ? pubSub : new PubSub();
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// src/AbstractWebSocket.ts
|
|
216
|
+
var AbstractWebSocket = class {
|
|
217
|
+
constructor(config) {
|
|
218
|
+
this.CONNECTING = 0;
|
|
219
|
+
this.OPEN = 1;
|
|
220
|
+
this.CLOSING = 2;
|
|
221
|
+
this.CLOSED = 3;
|
|
222
|
+
const { room } = config;
|
|
223
|
+
this.room = room;
|
|
224
|
+
}
|
|
225
|
+
handleError(params) {
|
|
226
|
+
var _a, _b, _c;
|
|
227
|
+
const {
|
|
228
|
+
error,
|
|
229
|
+
message = error instanceof Error ? error.message : "Unexpected error",
|
|
230
|
+
session
|
|
231
|
+
} = params;
|
|
232
|
+
const stack = error instanceof Error ? (_a = error.stack) != null ? _a : null : null;
|
|
233
|
+
return this.sendMessage({
|
|
234
|
+
connectionId: (_b = session == null ? void 0 : session.id) != null ? _b : null,
|
|
235
|
+
data: { message, stack },
|
|
236
|
+
room: this.room,
|
|
237
|
+
type: "$ERROR",
|
|
238
|
+
user: (_c = session == null ? void 0 : session.user) != null ? _c : null
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
sendMessage(data) {
|
|
242
|
+
return this.send(JSON.stringify(data));
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// src/PluvIO.ts
|
|
247
|
+
var import_chalk2 = __toESM(require("chalk"));
|
|
248
|
+
|
|
249
|
+
// src/authorize.ts
|
|
250
|
+
var import_hkdf = __toESM(require("@panva/hkdf"));
|
|
251
|
+
var import_jose = require("jose");
|
|
252
|
+
var DEFAULT_MAX_AGE_MS = 6e4;
|
|
253
|
+
var getEncryptionKey = (secret) => __async(void 0, null, function* () {
|
|
254
|
+
return yield (0, import_hkdf.default)(
|
|
255
|
+
"sha256",
|
|
256
|
+
secret,
|
|
257
|
+
"",
|
|
258
|
+
"Pluv.io Generated Encryption Key",
|
|
259
|
+
32
|
|
260
|
+
);
|
|
261
|
+
});
|
|
262
|
+
var now = () => Date.now() / 1e3 | 0;
|
|
263
|
+
var authorize = (params) => {
|
|
264
|
+
const { platform, secret } = params;
|
|
265
|
+
const encode = (encodeParams) => __async(void 0, null, function* () {
|
|
266
|
+
const { maxAge = DEFAULT_MAX_AGE_MS, room, user } = encodeParams;
|
|
267
|
+
const encryptionSecret = yield getEncryptionKey(secret);
|
|
268
|
+
const token = yield new import_jose.EncryptJWT({
|
|
269
|
+
room,
|
|
270
|
+
sub: room ? `${room}|${user.id}` : user.id,
|
|
271
|
+
user
|
|
272
|
+
}).setProtectedHeader({ alg: "dir", enc: "A256GCM" }).setIssuedAt().setExpirationTime(now() + maxAge).setJti(platform.randomUUID()).encrypt(encryptionSecret);
|
|
273
|
+
return token.toString();
|
|
274
|
+
});
|
|
275
|
+
const decode = (jwt) => __async(void 0, null, function* () {
|
|
276
|
+
const encryptionSecret = yield getEncryptionKey(secret);
|
|
277
|
+
const { payload } = yield (0, import_jose.jwtDecrypt)(jwt, encryptionSecret, {
|
|
278
|
+
clockTolerance: 15
|
|
279
|
+
});
|
|
280
|
+
return payload != null ? payload : null;
|
|
281
|
+
});
|
|
282
|
+
return { decode, encode };
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// src/IORoom.ts
|
|
286
|
+
var import_crdt_yjs = require("@pluv/crdt-yjs");
|
|
287
|
+
var import_chalk = __toESM(require("chalk"));
|
|
288
|
+
var PING_TIMEOUT_MS = 3e4;
|
|
289
|
+
var IORoom = class {
|
|
290
|
+
constructor(room, config) {
|
|
291
|
+
this._doc = (0, import_crdt_yjs.doc)();
|
|
292
|
+
this._sessions = /* @__PURE__ */ new Map();
|
|
293
|
+
this._uninitialize = null;
|
|
294
|
+
this._authorize = null;
|
|
295
|
+
const { authorize: authorize2, context, debug, events, onDestroy, platform } = config;
|
|
296
|
+
this._context = context;
|
|
297
|
+
this._debug = debug;
|
|
298
|
+
this._events = events;
|
|
299
|
+
this._platform = platform;
|
|
300
|
+
this.room = room;
|
|
301
|
+
this._listeners = {
|
|
302
|
+
onDestroy: () => onDestroy == null ? void 0 : onDestroy()
|
|
303
|
+
};
|
|
304
|
+
if (authorize2)
|
|
305
|
+
this._authorize = authorize2;
|
|
306
|
+
}
|
|
307
|
+
broadcast(message) {
|
|
308
|
+
return Promise.resolve(this._broadcast({ message }));
|
|
309
|
+
}
|
|
310
|
+
register(webSocket, options) {
|
|
311
|
+
return __async(this, null, function* () {
|
|
312
|
+
var _a;
|
|
313
|
+
if (!this._uninitialize)
|
|
314
|
+
yield this._initialize();
|
|
315
|
+
const token = options == null ? void 0 : options.token;
|
|
316
|
+
const sessionId = this._platform.randomUUID();
|
|
317
|
+
const pluvWs = this._platform.convertWebSocket(webSocket, {
|
|
318
|
+
room: this.room
|
|
319
|
+
});
|
|
320
|
+
this._logDebug(
|
|
321
|
+
`${import_chalk.default.blue(
|
|
322
|
+
`Registering connection for room ${this.room}:`
|
|
323
|
+
)} ${sessionId}`
|
|
324
|
+
);
|
|
325
|
+
const uninitializeWs = yield pluvWs.initialize();
|
|
326
|
+
const user = yield this._getAuthorizedUser(token);
|
|
327
|
+
const isUnauthorized = !!((_a = this._authorize) == null ? void 0 : _a.required) && !user;
|
|
328
|
+
const isTokenInUse = !!user && Array.from(this._sessions.values()).some(
|
|
329
|
+
(session2) => {
|
|
330
|
+
var _a2;
|
|
331
|
+
return ((_a2 = session2.user) == null ? void 0 : _a2.id) === user.id;
|
|
332
|
+
}
|
|
333
|
+
);
|
|
334
|
+
if (isUnauthorized || isTokenInUse) {
|
|
335
|
+
this._logDebug(
|
|
336
|
+
`${import_chalk.default.blue(
|
|
337
|
+
"Authorization failed for connection:"
|
|
338
|
+
)} ${sessionId}`
|
|
339
|
+
);
|
|
340
|
+
pluvWs.handleError({ error: new Error("Not authorized") });
|
|
341
|
+
pluvWs.close(3e3, "WebSocket unauthorized.");
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const currentTime = new Date().getTime();
|
|
345
|
+
const session = {
|
|
346
|
+
id: sessionId,
|
|
347
|
+
presence: null,
|
|
348
|
+
quit: false,
|
|
349
|
+
room: this.room,
|
|
350
|
+
timers: {
|
|
351
|
+
ping: currentTime
|
|
352
|
+
},
|
|
353
|
+
webSocket: pluvWs,
|
|
354
|
+
user
|
|
355
|
+
};
|
|
356
|
+
this._sessions.set(session.id, session);
|
|
357
|
+
yield this._platform.persistance.addUser(
|
|
358
|
+
this.room,
|
|
359
|
+
sessionId,
|
|
360
|
+
user != null ? user : {}
|
|
361
|
+
);
|
|
362
|
+
this._emitRegistered(session);
|
|
363
|
+
this._emitStorageReceived(session);
|
|
364
|
+
this._emitUserJoined(session);
|
|
365
|
+
const context = {
|
|
366
|
+
context: this._context,
|
|
367
|
+
doc: this._doc,
|
|
368
|
+
room: this.room,
|
|
369
|
+
session,
|
|
370
|
+
sessions: this._sessions
|
|
371
|
+
};
|
|
372
|
+
pluvWs.addEventListener("message", (event) => {
|
|
373
|
+
if (!this._uninitialize)
|
|
374
|
+
return;
|
|
375
|
+
if (session.quit) {
|
|
376
|
+
session.webSocket.close(1011, "WebSocket broken.");
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const message = this._parseMessage(event);
|
|
380
|
+
if (!message)
|
|
381
|
+
return;
|
|
382
|
+
const eventConfig = this._getEventConfig(message);
|
|
383
|
+
if (!eventConfig)
|
|
384
|
+
return;
|
|
385
|
+
this._resolveMessage(message, context).then((output) => {
|
|
386
|
+
var _a2, _b;
|
|
387
|
+
if (!output)
|
|
388
|
+
return;
|
|
389
|
+
const eventType = (_b = (_a2 = eventConfig.options) == null ? void 0 : _a2.type) != null ? _b : "broadcast";
|
|
390
|
+
switch (eventType) {
|
|
391
|
+
case "self": {
|
|
392
|
+
Object.entries(output).forEach(([type, data]) => {
|
|
393
|
+
this._sendSelfMessage({ data, type }, session);
|
|
394
|
+
});
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
case "sync": {
|
|
398
|
+
this._platform.pubSub.publish(this.room, __spreadValues({
|
|
399
|
+
connectionId: session.id,
|
|
400
|
+
options: eventConfig.options,
|
|
401
|
+
room: this.room,
|
|
402
|
+
user: session.user
|
|
403
|
+
}, message));
|
|
404
|
+
Object.entries(output).forEach(([type, data]) => {
|
|
405
|
+
this._sendSelfMessage({ data, type }, session);
|
|
406
|
+
});
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
case "broadcast":
|
|
410
|
+
default: {
|
|
411
|
+
Object.entries(output).forEach(([type, data]) => {
|
|
412
|
+
this._broadcast({
|
|
413
|
+
message: { data, type },
|
|
414
|
+
senderId: session.id
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
const closeHandler = () => {
|
|
422
|
+
if (!this._uninitialize)
|
|
423
|
+
return;
|
|
424
|
+
session.quit = true;
|
|
425
|
+
this._logDebug(
|
|
426
|
+
`${import_chalk.default.blue(
|
|
427
|
+
`(Unregistering connection for room ${this.room}:`
|
|
428
|
+
)} ${sessionId}`
|
|
429
|
+
);
|
|
430
|
+
this._sessions.delete(session.id);
|
|
431
|
+
this._platform.persistance.deleteUser(this.room, session.id);
|
|
432
|
+
this._broadcast({
|
|
433
|
+
message: {
|
|
434
|
+
type: "$EXIT",
|
|
435
|
+
data: { sessionId: session.id }
|
|
436
|
+
},
|
|
437
|
+
senderId: session.id
|
|
438
|
+
});
|
|
439
|
+
uninitializeWs();
|
|
440
|
+
const size2 = this._getSessionsSize();
|
|
441
|
+
this._logDebug(
|
|
442
|
+
`${import_chalk.default.blue(
|
|
443
|
+
`Unregistered connection for room ${this.room}:`
|
|
444
|
+
)} ${sessionId}`
|
|
445
|
+
);
|
|
446
|
+
this._logDebug(`${import_chalk.default.blue(`Room ${this.room} size:`)} ${size2}`);
|
|
447
|
+
if (size2)
|
|
448
|
+
return;
|
|
449
|
+
this._uninitialize();
|
|
450
|
+
};
|
|
451
|
+
pluvWs.addEventListener("close", closeHandler);
|
|
452
|
+
pluvWs.addEventListener("error", closeHandler);
|
|
453
|
+
this._logDebug(
|
|
454
|
+
`${import_chalk.default.blue(
|
|
455
|
+
`Registered connection for room ${this.room}:`
|
|
456
|
+
)} ${sessionId}`
|
|
457
|
+
);
|
|
458
|
+
const size = this._getSessionsSize();
|
|
459
|
+
this._logDebug(`${import_chalk.default.blue(`Room ${this.room} size:`)} ${size}`);
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
_broadcast(params) {
|
|
463
|
+
var _a;
|
|
464
|
+
const { message, senderId } = params;
|
|
465
|
+
const currentTime = new Date().getTime();
|
|
466
|
+
const sender = senderId ? this._sessions.get(senderId) : null;
|
|
467
|
+
const quitters = Array.from(this._sessions.values()).filter(
|
|
468
|
+
(session) => session.quit || currentTime - session.timers.ping > PING_TIMEOUT_MS
|
|
469
|
+
);
|
|
470
|
+
quitters.forEach((session) => {
|
|
471
|
+
this._sessions.delete(session.id);
|
|
472
|
+
});
|
|
473
|
+
this._emitQuitters(quitters);
|
|
474
|
+
this._platform.pubSub.publish(this.room, __spreadValues({
|
|
475
|
+
connectionId: senderId != null ? senderId : null,
|
|
476
|
+
room: this.room,
|
|
477
|
+
user: (_a = sender == null ? void 0 : sender.user) != null ? _a : null
|
|
478
|
+
}, message));
|
|
479
|
+
}
|
|
480
|
+
_emitQuitters(quitters) {
|
|
481
|
+
quitters.forEach((quitter) => {
|
|
482
|
+
this._broadcast({
|
|
483
|
+
message: {
|
|
484
|
+
type: "$EXIT",
|
|
485
|
+
data: { sessionId: quitter.id }
|
|
486
|
+
},
|
|
487
|
+
senderId: quitter.id
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
_emitRegistered(session) {
|
|
492
|
+
this._sendSelfMessage(
|
|
493
|
+
{
|
|
494
|
+
type: "$REGISTERED",
|
|
495
|
+
data: { sessionId: session.id }
|
|
496
|
+
},
|
|
497
|
+
session
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
_emitStorageReceived(session) {
|
|
501
|
+
this._platform.persistance.getStorageState(this.room).then((_state) => {
|
|
502
|
+
const state = _state != null ? _state : this._doc.encodeStateAsUpdate();
|
|
503
|
+
this._sendSelfMessage(
|
|
504
|
+
{
|
|
505
|
+
type: "$STORAGE_RECEIVED",
|
|
506
|
+
data: { state }
|
|
507
|
+
},
|
|
508
|
+
session
|
|
509
|
+
);
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
_emitUserJoined(session) {
|
|
513
|
+
const user = session.user;
|
|
514
|
+
if (!user)
|
|
515
|
+
return;
|
|
516
|
+
this._broadcast({
|
|
517
|
+
message: {
|
|
518
|
+
type: "$USER_JOINED",
|
|
519
|
+
data: {
|
|
520
|
+
connectionId: session.id,
|
|
521
|
+
user
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
senderId: session.id
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
_getAuthorizedUser(token) {
|
|
528
|
+
return __async(this, null, function* () {
|
|
529
|
+
var _a;
|
|
530
|
+
if (!this._authorize)
|
|
531
|
+
return null;
|
|
532
|
+
if (!token)
|
|
533
|
+
return null;
|
|
534
|
+
const payload = yield authorize({
|
|
535
|
+
platform: this._platform,
|
|
536
|
+
secret: this._authorize.secret
|
|
537
|
+
}).decode(token);
|
|
538
|
+
if (!payload) {
|
|
539
|
+
this._logDebug(import_chalk.default.blue("Could not decode token:"));
|
|
540
|
+
this._logDebug(token);
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
if (payload.room !== this.room) {
|
|
544
|
+
this._logDebug(
|
|
545
|
+
import_chalk.default.blue(`Token is not authorized for room ${this.room}:`)
|
|
546
|
+
);
|
|
547
|
+
this._logDebug(import_chalk.default.blue("Received:"), payload.room);
|
|
548
|
+
this._logDebug(token);
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
try {
|
|
552
|
+
return (_a = this._authorize.user.parse(payload.user)) != null ? _a : null;
|
|
553
|
+
} catch (e) {
|
|
554
|
+
this._logDebug(`${import_chalk.default.blue("Token fails validation:")} ${token}`);
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
_getSessionsSize() {
|
|
560
|
+
const currentTime = new Date().getTime();
|
|
561
|
+
return Array.from(this._sessions.values()).reduce((count, session) => {
|
|
562
|
+
if (session.quit)
|
|
563
|
+
return count;
|
|
564
|
+
return currentTime - session.timers.ping > PING_TIMEOUT_MS ? count : count + 1;
|
|
565
|
+
}, 0);
|
|
566
|
+
}
|
|
567
|
+
_getEventConfig(message) {
|
|
568
|
+
var _a;
|
|
569
|
+
return (_a = this._events[message.type]) != null ? _a : null;
|
|
570
|
+
}
|
|
571
|
+
_initialize() {
|
|
572
|
+
return __async(this, null, function* () {
|
|
573
|
+
this._logDebug(`${import_chalk.default.blue(`Initializing room ${this.room}:`)}`);
|
|
574
|
+
if (!!this._uninitialize) {
|
|
575
|
+
yield this._uninitialize();
|
|
576
|
+
this._uninitialize = null;
|
|
577
|
+
}
|
|
578
|
+
const pubSubId = yield this._platform.pubSub.subscribe(
|
|
579
|
+
this.room,
|
|
580
|
+
(_a) => {
|
|
581
|
+
var _b = _a, { options = {} } = _b, message = __objRest(_b, ["options"]);
|
|
582
|
+
const session = {
|
|
583
|
+
id: message.connectionId,
|
|
584
|
+
user: message.user
|
|
585
|
+
};
|
|
586
|
+
this._sendMessage(message, session, options);
|
|
587
|
+
}
|
|
588
|
+
);
|
|
589
|
+
this._uninitialize = () => __async(this, null, function* () {
|
|
590
|
+
this._platform.pubSub.unsubscribe(pubSubId);
|
|
591
|
+
this._doc.destroy();
|
|
592
|
+
this._doc = (0, import_crdt_yjs.doc)();
|
|
593
|
+
this._listeners.onDestroy();
|
|
594
|
+
});
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
_logDebug(...data) {
|
|
598
|
+
this._debug && console.log(...data);
|
|
599
|
+
}
|
|
600
|
+
_parseMessage(message) {
|
|
601
|
+
const parsed = this._platform.parseData(message.data);
|
|
602
|
+
try {
|
|
603
|
+
if (typeof parsed !== "object")
|
|
604
|
+
return null;
|
|
605
|
+
if (typeof parsed.type !== "string")
|
|
606
|
+
return null;
|
|
607
|
+
if (typeof parsed.data !== "object")
|
|
608
|
+
return null;
|
|
609
|
+
return parsed;
|
|
610
|
+
} catch (e) {
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
_resolveMessage(message, context) {
|
|
615
|
+
return __async(this, null, function* () {
|
|
616
|
+
const { session } = context;
|
|
617
|
+
const eventConfig = this._getEventConfig(message);
|
|
618
|
+
if (!eventConfig)
|
|
619
|
+
return null;
|
|
620
|
+
let input;
|
|
621
|
+
try {
|
|
622
|
+
input = eventConfig.input ? eventConfig.input.parse(message.data) : message.data;
|
|
623
|
+
} catch (error) {
|
|
624
|
+
session == null ? void 0 : session.webSocket.handleError({
|
|
625
|
+
error,
|
|
626
|
+
message: "Invalid input",
|
|
627
|
+
session
|
|
628
|
+
});
|
|
629
|
+
return null;
|
|
630
|
+
}
|
|
631
|
+
const output = yield eventConfig.resolver(input, context);
|
|
632
|
+
if (!output)
|
|
633
|
+
return null;
|
|
634
|
+
return output;
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
_sendMessage(message, sender, options = {}) {
|
|
638
|
+
switch (options.type) {
|
|
639
|
+
case "self": {
|
|
640
|
+
this._sendSelfMessage(message, sender);
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
case "sync": {
|
|
644
|
+
this._sendSyncMessage(message, sender);
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
case "broadcast":
|
|
648
|
+
default: {
|
|
649
|
+
const sessionIds = options.sessionIds;
|
|
650
|
+
this._sendBroadcastMessage(message, sender, sessionIds);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
_sendBroadcastMessage(message, sender, sessionIds) {
|
|
655
|
+
var _a, _b;
|
|
656
|
+
const senderId = (_a = sender == null ? void 0 : sender.id) != null ? _a : null;
|
|
657
|
+
const sessions = (_b = sessionIds == null ? void 0 : sessionIds.reduce((dict, id) => {
|
|
658
|
+
const _session = this._sessions.get(id);
|
|
659
|
+
return _session ? dict.set(id, _session) : dict;
|
|
660
|
+
}, /* @__PURE__ */ new Map())) != null ? _b : this._sessions;
|
|
661
|
+
Array.from(sessions.values()).forEach((_session) => {
|
|
662
|
+
_session.webSocket.sendMessage(__spreadValues({
|
|
663
|
+
connectionId: senderId,
|
|
664
|
+
room: this.room,
|
|
665
|
+
user: _session.user
|
|
666
|
+
}, message));
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
_sendSelfMessage(message, sender) {
|
|
670
|
+
const senderId = sender == null ? void 0 : sender.id;
|
|
671
|
+
if (!senderId)
|
|
672
|
+
return;
|
|
673
|
+
const session = this._sessions.get(senderId);
|
|
674
|
+
if (!session)
|
|
675
|
+
return;
|
|
676
|
+
session.webSocket.sendMessage(__spreadValues({
|
|
677
|
+
connectionId: sender.id,
|
|
678
|
+
room: this.room,
|
|
679
|
+
user: sender.user
|
|
680
|
+
}, message));
|
|
681
|
+
}
|
|
682
|
+
_sendSyncMessage(message, sender) {
|
|
683
|
+
const senderId = sender == null ? void 0 : sender.id;
|
|
684
|
+
if (!senderId)
|
|
685
|
+
return;
|
|
686
|
+
this._resolveMessage(message, {
|
|
687
|
+
context: this._context,
|
|
688
|
+
doc: this._doc,
|
|
689
|
+
room: this.room,
|
|
690
|
+
session: null,
|
|
691
|
+
sessions: this._sessions
|
|
692
|
+
}).then((output) => {
|
|
693
|
+
if (!output)
|
|
694
|
+
return;
|
|
695
|
+
Object.keys(output).forEach((type) => {
|
|
696
|
+
var _a, _b;
|
|
697
|
+
const data = (_a = output[type]) != null ? _a : {};
|
|
698
|
+
this._platform.pubSub.publish(this.room, {
|
|
699
|
+
connectionId: senderId,
|
|
700
|
+
data,
|
|
701
|
+
options: { type: "self" },
|
|
702
|
+
room: this.room,
|
|
703
|
+
type,
|
|
704
|
+
user: (_b = sender.user) != null ? _b : null
|
|
705
|
+
});
|
|
706
|
+
});
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
// src/PluvIO.ts
|
|
712
|
+
var PluvIO = class {
|
|
713
|
+
constructor(options) {
|
|
714
|
+
this._authorize = null;
|
|
715
|
+
this._events = {
|
|
716
|
+
$PING: {
|
|
717
|
+
resolver: (data, { session }) => {
|
|
718
|
+
if (!session)
|
|
719
|
+
return {};
|
|
720
|
+
const currentTime = new Date().getTime();
|
|
721
|
+
session.timers.ping = currentTime;
|
|
722
|
+
return { $PONG: {} };
|
|
723
|
+
},
|
|
724
|
+
options: { type: "self" }
|
|
725
|
+
},
|
|
726
|
+
$GET_OTHERS: {
|
|
727
|
+
resolver: (data, { session, sessions }) => {
|
|
728
|
+
const others = Array.from(sessions.entries()).filter(([, wsSession]) => wsSession.id !== (session == null ? void 0 : session.id)).reduce(
|
|
729
|
+
(acc, [connectionId, { presence, user }]) => __spreadProps(__spreadValues({}, acc), {
|
|
730
|
+
[connectionId]: {
|
|
731
|
+
connectionId,
|
|
732
|
+
presence,
|
|
733
|
+
room: this.room,
|
|
734
|
+
user
|
|
735
|
+
}
|
|
736
|
+
}),
|
|
737
|
+
{}
|
|
738
|
+
);
|
|
739
|
+
return { $OTHERS_RECEIVED: { others } };
|
|
740
|
+
},
|
|
741
|
+
options: { type: "sync" }
|
|
742
|
+
},
|
|
743
|
+
$UPDATE_PRESENCE: {
|
|
744
|
+
resolver: ({ presence }, { session }) => {
|
|
745
|
+
if (!session)
|
|
746
|
+
return {};
|
|
747
|
+
session.presence = Object.assign(
|
|
748
|
+
/* @__PURE__ */ Object.create(null),
|
|
749
|
+
session.presence,
|
|
750
|
+
presence
|
|
751
|
+
);
|
|
752
|
+
return { $PRESENCE_UPDATED: { presence } };
|
|
753
|
+
}
|
|
754
|
+
},
|
|
755
|
+
$UPDATE_STORAGE: {
|
|
756
|
+
resolver: ({ origin, update }, { doc: doc2, room }) => {
|
|
757
|
+
if (origin === "$STORAGE_RECEIVED" && Object.keys(doc2.toJSON()).length) {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const updated = doc2.applyUpdate(update);
|
|
761
|
+
const state = updated.encodeStateAsUpdate();
|
|
762
|
+
this._platform.persistance.setStorageState(room, state);
|
|
763
|
+
return { $STORAGE_UPDATED: { state } };
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
this.room = null;
|
|
768
|
+
this._context = {};
|
|
769
|
+
this._rooms = /* @__PURE__ */ new Map();
|
|
770
|
+
const {
|
|
771
|
+
authorize: authorize2,
|
|
772
|
+
context,
|
|
773
|
+
debug = false,
|
|
774
|
+
events,
|
|
775
|
+
platform,
|
|
776
|
+
room
|
|
777
|
+
} = options;
|
|
778
|
+
this._debug = debug;
|
|
779
|
+
this._platform = platform;
|
|
780
|
+
if (authorize2)
|
|
781
|
+
this._authorize = authorize2;
|
|
782
|
+
if (context)
|
|
783
|
+
this._context = context;
|
|
784
|
+
if (events)
|
|
785
|
+
this._events = events;
|
|
786
|
+
if (room)
|
|
787
|
+
this.room = room;
|
|
788
|
+
}
|
|
789
|
+
createToken(params) {
|
|
790
|
+
return __async(this, null, function* () {
|
|
791
|
+
if (!this._authorize) {
|
|
792
|
+
throw new Error(
|
|
793
|
+
"IO does not specify authorize during initialization."
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
return yield authorize({
|
|
797
|
+
platform: this._platform,
|
|
798
|
+
secret: this._authorize.secret
|
|
799
|
+
}).encode(params);
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
event(event, config) {
|
|
803
|
+
if (event.startsWith("$")) {
|
|
804
|
+
throw new Error('Event name must not start with "$".');
|
|
805
|
+
}
|
|
806
|
+
const newEvent = {
|
|
807
|
+
[event]: config
|
|
808
|
+
};
|
|
809
|
+
return PluvIO.merge(
|
|
810
|
+
this,
|
|
811
|
+
new PluvIO({
|
|
812
|
+
events: newEvent,
|
|
813
|
+
platform: this._platform
|
|
814
|
+
})
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
getRoom(room) {
|
|
818
|
+
var _a;
|
|
819
|
+
if (this.room) {
|
|
820
|
+
throw new Error("IO cannot getRoom from within a room.");
|
|
821
|
+
}
|
|
822
|
+
if (!/^[a-z0-9]+[a-z0-9\-_]+[a-z0-9]+$/i.test(room)) {
|
|
823
|
+
throw new Error("Unsupported room name");
|
|
824
|
+
}
|
|
825
|
+
const platform = this._platform;
|
|
826
|
+
const oldRoom = this._rooms.get(room);
|
|
827
|
+
if (oldRoom)
|
|
828
|
+
return oldRoom;
|
|
829
|
+
const newRoom = new IORoom(room, {
|
|
830
|
+
authorize: (_a = this._authorize) != null ? _a : void 0,
|
|
831
|
+
context: this._context,
|
|
832
|
+
debug: this._debug,
|
|
833
|
+
events: this._events,
|
|
834
|
+
onDestroy: () => {
|
|
835
|
+
this._logDebug(`${import_chalk2.default.blue("Deleting empty room:")} ${room}`);
|
|
836
|
+
this._rooms.delete(room);
|
|
837
|
+
if (this._debug) {
|
|
838
|
+
const rooms = Array.from(this._rooms.keys());
|
|
839
|
+
this._logDebug(`${import_chalk2.default.blue("Deleted room:")} ${room}`);
|
|
840
|
+
this._logDebug(
|
|
841
|
+
`${import_chalk2.default.blue("Rooms available:")} ${rooms.join(", ")}`
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
},
|
|
845
|
+
platform
|
|
846
|
+
});
|
|
847
|
+
this._rooms.set(room, newRoom);
|
|
848
|
+
if (this._debug) {
|
|
849
|
+
const rooms = Array.from(this._rooms.keys());
|
|
850
|
+
this._logDebug(`${import_chalk2.default.blue("Created room:")} ${room}`);
|
|
851
|
+
this._logDebug(
|
|
852
|
+
`${import_chalk2.default.blue("Rooms available:")} ${rooms.join(", ")}`
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
return newRoom;
|
|
856
|
+
}
|
|
857
|
+
static merge(first, second) {
|
|
858
|
+
var _a, _b;
|
|
859
|
+
const authorize2 = (_a = first._authorize) != null ? _a : void 0;
|
|
860
|
+
const debug = first._debug;
|
|
861
|
+
const platform = first._platform;
|
|
862
|
+
const room = (_b = first.room) != null ? _b : void 0;
|
|
863
|
+
const events1 = first._events;
|
|
864
|
+
const events2 = second._events;
|
|
865
|
+
const events = Object.assign(/* @__PURE__ */ Object.create(null), events1, events2);
|
|
866
|
+
return new PluvIO({
|
|
867
|
+
authorize: authorize2,
|
|
868
|
+
debug,
|
|
869
|
+
events,
|
|
870
|
+
platform,
|
|
871
|
+
room
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
_logDebug(...data) {
|
|
875
|
+
this._debug && console.log(...data);
|
|
876
|
+
}
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
// src/createIO.ts
|
|
880
|
+
var createIO = (params) => {
|
|
881
|
+
const { authorize: authorize2, context, debug, platform } = params;
|
|
882
|
+
return new PluvIO({ authorize: authorize2, context, debug, platform });
|
|
883
|
+
};
|
|
884
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
885
|
+
0 && (module.exports = {
|
|
886
|
+
AbstractPersistance,
|
|
887
|
+
AbstractPlatform,
|
|
888
|
+
AbstractPubSub,
|
|
889
|
+
AbstractWebSocket,
|
|
890
|
+
createIO
|
|
891
|
+
});
|