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