@pluv/platform-pluv 3.2.2 → 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/dist/index.js DELETED
@@ -1,500 +0,0 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
- var __async = (__this, __arguments, generator) => {
30
- return new Promise((resolve, reject) => {
31
- var fulfilled = (value) => {
32
- try {
33
- step(generator.next(value));
34
- } catch (e) {
35
- reject(e);
36
- }
37
- };
38
- var rejected = (value) => {
39
- try {
40
- step(generator.throw(value));
41
- } catch (e) {
42
- reject(e);
43
- }
44
- };
45
- var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
46
- step((generator = generator.apply(__this, __arguments)).next());
47
- });
48
- };
49
-
50
- // src/index.ts
51
- var index_exports = {};
52
- __export(index_exports, {
53
- PluvPlatform: () => PluvPlatform,
54
- platformPluv: () => platformPluv
55
- });
56
- module.exports = __toCommonJS(index_exports);
57
-
58
- // src/PluvPlatform.ts
59
- var import_io = require("@pluv/io");
60
- var import_fast_json_stable_stringify = __toESM(require("fast-json-stable-stringify"));
61
- var import_hono = require("hono");
62
-
63
- // src/constants.ts
64
- var SIGNATURE_ALGORITHM = "sha256";
65
- var SIGNATURE_HEADER = "x-pluv-signature-256";
66
-
67
- // src/schemas.ts
68
- var import_zod = require("zod");
69
- var ZodEventKind = import_zod.z.union([
70
- import_zod.z.literal("initial-storage"),
71
- import_zod.z.literal("room-deleted"),
72
- import_zod.z.literal("user-connected"),
73
- import_zod.z.literal("user-disconnected")
74
- ]);
75
- var ZodEventInitialStorage = import_zod.z.object({
76
- event: import_zod.z.literal("initial-storage"),
77
- data: import_zod.z.object({
78
- room: import_zod.z.string()
79
- })
80
- });
81
- var ZodEventRoomDeleted = import_zod.z.object({
82
- event: import_zod.z.literal("room-deleted"),
83
- data: import_zod.z.object({
84
- room: import_zod.z.string(),
85
- storage: import_zod.z.string().nullable()
86
- })
87
- });
88
- var ZodEventUserConnected = import_zod.z.object({
89
- event: import_zod.z.literal("user-connected"),
90
- data: import_zod.z.object({
91
- room: import_zod.z.string(),
92
- storage: import_zod.z.string().nullable(),
93
- user: import_zod.z.object({
94
- id: import_zod.z.string()
95
- }).passthrough()
96
- })
97
- });
98
- var ZodEventUserDisconnected = import_zod.z.object({
99
- event: import_zod.z.literal("user-disconnected"),
100
- data: import_zod.z.object({
101
- room: import_zod.z.string(),
102
- storage: import_zod.z.string().nullable(),
103
- user: import_zod.z.object({
104
- id: import_zod.z.string()
105
- }).passthrough()
106
- })
107
- });
108
- var ZodEvent = import_zod.z.discriminatedUnion("event", [
109
- ZodEventInitialStorage,
110
- ZodEventRoomDeleted,
111
- ZodEventUserConnected,
112
- ZodEventUserDisconnected
113
- ]);
114
- var ZodInitialStorageResponse = import_zod.z.object({
115
- event: import_zod.z.literal("initial-storage"),
116
- room: import_zod.z.string(),
117
- storage: import_zod.z.string().nullable()
118
- });
119
- var ZodRoomDeletedResponse = import_zod.z.object({
120
- event: import_zod.z.literal("room-deleted"),
121
- room: import_zod.z.string()
122
- });
123
- var ZodUserConnectedResponse = import_zod.z.object({
124
- event: import_zod.z.literal("user-connected"),
125
- room: import_zod.z.string()
126
- });
127
- var ZodUserDisconnectedResponse = import_zod.z.object({
128
- event: import_zod.z.literal("user-disconnected"),
129
- room: import_zod.z.string()
130
- });
131
- var ZodEventResponse = import_zod.z.discriminatedUnion("event", [
132
- ZodInitialStorageResponse,
133
- ZodRoomDeletedResponse,
134
- ZodUserConnectedResponse,
135
- ZodUserDisconnectedResponse
136
- ]);
137
-
138
- // src/shared/createErrorResponse.ts
139
- var createErrorResponse = (c, error, status) => {
140
- return c.json({ ok: false, error: { message: error.message } }, status);
141
- };
142
-
143
- // src/shared/getCrypto.ts
144
- var getCrypto = () => {
145
- if (typeof crypto !== "undefined") {
146
- return crypto;
147
- }
148
- if (typeof require === "function") {
149
- return require("crypto").webcrypto;
150
- }
151
- throw new Error("Missing crypto module");
152
- };
153
-
154
- // src/shared/createHmac.ts
155
- var createHmac = (params) => __async(null, null, function* () {
156
- const { payload, secret } = params;
157
- if (!payload || !secret) throw new Error("Secret and payload are required to sign payload");
158
- const encoder = new TextEncoder();
159
- const keyBytes = encoder.encode(secret);
160
- const crypto2 = getCrypto();
161
- const algorithm = { name: "HMAC", hash: { name: "SHA-256" } };
162
- const extractable = false;
163
- const key = yield crypto2.subtle.importKey("raw", keyBytes, algorithm, extractable, [
164
- "sign",
165
- "verify"
166
- ]);
167
- const payloadBytes = encoder.encode(payload);
168
- const signature = yield crypto2.subtle.sign("HMAC", key, payloadBytes);
169
- const hmac = Array.from(new Uint8Array(signature)).map((b) => ("0" + b.toString(16)).slice(-2)).join("");
170
- return { algorithm: "sha256", hmac };
171
- });
172
-
173
- // src/shared/createSuccessResponse.ts
174
- var createSuccessResponse = (c, data, status = 200) => {
175
- return c.json(
176
- {
177
- ok: true,
178
- data: ZodEventResponse.parse(data)
179
- },
180
- status
181
- );
182
- };
183
-
184
- // src/shared/HttpError.ts
185
- var HttpError = class extends Error {
186
- constructor(message, status) {
187
- super(message);
188
- this.status = status;
189
- }
190
- };
191
-
192
- // src/shared/timingSafeEqual.ts
193
- var timingSafeEqual = (a, b) => {
194
- if (a.length !== b.length) return false;
195
- let result = 0;
196
- for (let i = 0; i < a.length; i++) {
197
- result |= a[i] ^ b[i];
198
- }
199
- return result === 0;
200
- };
201
-
202
- // src/shared/verifyWebhook.ts
203
- var verifyWebhook = (params) => __async(null, null, function* () {
204
- const { payload, secret, signature } = params;
205
- if (!secret || !payload || !signature) {
206
- throw new Error("Secret, payload and signature are required to verify payload");
207
- }
208
- const { hmac } = yield createHmac({ payload, secret });
209
- if (hmac.length !== signature.length) return false;
210
- const encoder = new TextEncoder();
211
- const verificationBytes = encoder.encode(hmac);
212
- const signatureBytes = encoder.encode(signature);
213
- if (verificationBytes.length !== signatureBytes.length) return false;
214
- return timingSafeEqual(verificationBytes, signatureBytes);
215
- });
216
-
217
- // src/PluvPlatform.ts
218
- var PluvPlatform = class extends import_io.AbstractPlatform {
219
- constructor(params) {
220
- var _a, _b;
221
- super();
222
- this.id = Math.random().toString();
223
- this._config = {
224
- authorize: {
225
- secret: false
226
- },
227
- handleMode: "fetch",
228
- registrationMode: "attached",
229
- requireAuth: true,
230
- listeners: {
231
- onRoomDeleted: true,
232
- onRoomMessage: false,
233
- onStorageUpdated: false,
234
- onUserConnected: true,
235
- onUserDisconnected: true
236
- },
237
- router: false
238
- };
239
- this._name = "platformPluv";
240
- this._createToken = (params) => __async(this, null, function* () {
241
- var _a, _b;
242
- const parsed = params.authorize.user.parse(params.user);
243
- const [endpoints, publicKey, secretKey] = yield Promise.all([
244
- typeof this._endpoints === "object" ? this._endpoints : this._endpoints(),
245
- typeof this._publicKey === "string" ? this._publicKey : this._publicKey(),
246
- typeof this._secretKey === "string" ? this._secretKey : this._secretKey()
247
- ]);
248
- this._logDebug({ endpoints, publicKey, secretKey });
249
- const res = yield fetch(endpoints.createToken, {
250
- headers: { "content-type": "application/json" },
251
- method: "post",
252
- body: JSON.stringify({
253
- maxAge: (_a = params.maxAge) != null ? _a : null,
254
- publicKey,
255
- room: params.room,
256
- secretKey,
257
- user: parsed
258
- })
259
- }).catch((error) => {
260
- this._logDebug(error);
261
- return null;
262
- });
263
- this._logDebug({ response: { status: (_b = res == null ? void 0 : res.status) != null ? _b : null } });
264
- if (!res || !res.ok || res.status !== 200) {
265
- throw new Error("Authorization failed");
266
- }
267
- const token = yield res.text().catch(() => null);
268
- this._logDebug({ token });
269
- if (typeof token !== "string") throw new Error("Authorization failed");
270
- return token;
271
- });
272
- this._webhooksRouter = new import_hono.Hono().basePath("/").post("/", (c) => __async(this, null, function* () {
273
- var _a, _b, _c, _d, _e, _f, _g, _h;
274
- const [algorithm, signature] = (_b = (_a = c.req.header(SIGNATURE_HEADER)) == null ? void 0 : _a.split("=")) != null ? _b : [];
275
- try {
276
- if (!this._webhookSecret) {
277
- this._logDebug("Missing webhook secret");
278
- throw new HttpError("Unauthorized", 401);
279
- }
280
- if (algorithm !== SIGNATURE_ALGORITHM) {
281
- this._logDebug(
282
- `Verification algorithm is not ${SIGNATURE_ALGORITHM}. Found: `,
283
- algorithm
284
- );
285
- throw new HttpError("Unauthorized", 401);
286
- }
287
- if (!signature) {
288
- this._logDebug("Missing webhook signature");
289
- throw new HttpError("Unauthorized", 401);
290
- }
291
- const [payload, webhookSecret] = yield Promise.all([
292
- c.req.json(),
293
- typeof this._webhookSecret === "string" ? this._webhookSecret : yield this._webhookSecret()
294
- ]).catch((error) => {
295
- this._logDebug(
296
- "Could not derive webhook secret: ",
297
- error instanceof Error ? error.message : "Unexpected error"
298
- );
299
- throw error;
300
- });
301
- const verified = yield verifyWebhook({
302
- payload: (0, import_fast_json_stable_stringify.default)(payload),
303
- signature,
304
- secret: webhookSecret
305
- }).catch((error) => {
306
- this._logDebug(
307
- "Error while verifying webhook: ",
308
- error instanceof Error ? error.message : "Unexpected error"
309
- );
310
- return false;
311
- });
312
- if (!verified) {
313
- this._logDebug("Failed to verify webhook");
314
- throw new HttpError("Unauthorized", 401);
315
- }
316
- const parsed = ZodEvent.safeParse(payload);
317
- if (!parsed.success) {
318
- this._logDebug(
319
- "Failed to validate event payload:",
320
- JSON.stringify((_c = parsed.data) != null ? _c : {}, null, 4)
321
- );
322
- throw new HttpError("Invalid request", 400);
323
- }
324
- const { event, data } = parsed.data;
325
- const context = yield this._getContext();
326
- switch (event) {
327
- case "initial-storage": {
328
- const room = data.room;
329
- const storage = typeof room === "string" ? (_e = yield (_d = this._getInitialStorage) == null ? void 0 : _d.call(this, { context, room })) != null ? _e : null : null;
330
- try {
331
- return createSuccessResponse(c, { event, room, storage });
332
- } catch (error) {
333
- this._logDebug("Could not create getInitialStorage response");
334
- throw error;
335
- }
336
- }
337
- case "room-deleted": {
338
- const room = data.room;
339
- const encodedState = data.storage;
340
- yield Promise.resolve(
341
- (_f = this._listeners) == null ? void 0 : _f.onRoomDeleted({ context, encodedState, room })
342
- );
343
- try {
344
- return createSuccessResponse(c, { event, room });
345
- } catch (error) {
346
- this._logDebug("Could not create onRoomDeleted response");
347
- throw error;
348
- }
349
- }
350
- case "user-connected": {
351
- const room = data.room;
352
- const encodedState = data.storage;
353
- const user = data.user;
354
- yield Promise.resolve(
355
- (_g = this._listeners) == null ? void 0 : _g.onUserConnected({
356
- context,
357
- encodedState,
358
- platform: this,
359
- room,
360
- user
361
- })
362
- );
363
- try {
364
- return createSuccessResponse(c, { event, room });
365
- } catch (error) {
366
- this._logDebug("Could not create onUserConnected response");
367
- throw error;
368
- }
369
- }
370
- case "user-disconnected": {
371
- const room = data.room;
372
- const encodedState = data.storage;
373
- const user = data.user;
374
- yield Promise.resolve(
375
- (_h = this._listeners) == null ? void 0 : _h.onUserDisconnected({
376
- context,
377
- encodedState,
378
- platform: this,
379
- room,
380
- user
381
- })
382
- );
383
- try {
384
- return createSuccessResponse(c, { event, room });
385
- } catch (error) {
386
- this._logDebug("Could not create onUserDisconnected response");
387
- throw error;
388
- }
389
- }
390
- default: {
391
- throw new HttpError("Unknown event", 400);
392
- }
393
- }
394
- } catch (error) {
395
- const message = error instanceof Error ? error.message : "Unexpected error";
396
- const status = error instanceof HttpError ? error.status : 500;
397
- this._logDebug("Uncaught error: ", message);
398
- return createErrorResponse(c, { message }, status);
399
- }
400
- }));
401
- const { _defs, basePath, context, publicKey, secretKey, webhookSecret } = params;
402
- this._basePath = basePath;
403
- this._context = context != null ? context : {};
404
- this._debug = (_a = _defs == null ? void 0 : _defs.debug) != null ? _a : false;
405
- this._endpoints = (_b = _defs == null ? void 0 : _defs.endpoints) != null ? _b : {
406
- createToken: "https://rooms.pluv.io/api/room/token"
407
- };
408
- this._publicKey = publicKey;
409
- this._secretKey = secretKey;
410
- this._webhookSecret = webhookSecret;
411
- this._app = new import_hono.Hono().basePath(this._basePath).route("/", this._webhooksRouter);
412
- this._fetch = this._app.fetch;
413
- }
414
- acceptWebSocket(webSocket) {
415
- throw new Error("Not implemented");
416
- }
417
- convertWebSocket(webSocket, config) {
418
- throw new Error("Not implemented");
419
- }
420
- getLastPing(webSocket) {
421
- throw new Error("Not implemented");
422
- }
423
- getSerializedState(webSocket) {
424
- throw new Error("Not implemented");
425
- }
426
- getSessionId(webSocket) {
427
- throw new Error("Not implemented");
428
- }
429
- getWebSockets() {
430
- throw new Error("Not implemented");
431
- }
432
- initialize(config) {
433
- throw new Error("Not implemented");
434
- }
435
- parseData(data) {
436
- throw new Error("Not implemented");
437
- }
438
- randomUUID() {
439
- throw new Error("Not implemented");
440
- }
441
- setSerializedState(webSocket, state) {
442
- throw new Error("Not implemented");
443
- }
444
- validateConfig(config) {
445
- this._logDebug("validating config with properties:", Object.keys(config != null ? config : {}));
446
- if (!config.authorize) {
447
- this._logDebug("Config `authorize` must be provided to `platformPluv`");
448
- throw new Error("Config `authorize` must be provided to `platformPluv`");
449
- }
450
- if (!!config.onRoomMessage) {
451
- this._logDebug("Config `onRoomMessage` is not supported on `platformPluv`");
452
- throw new Error("Config `onRoomMessage` is not supported on `platformPluv`");
453
- }
454
- if (!!config.onStorageUpdated) {
455
- this._logDebug("Config `onStorageUpdated` is not supported on `platformPluv`");
456
- throw new Error("Config `onStorageUpdated` is not supported on `platformPluv`");
457
- }
458
- this._getInitialStorage = config.getInitialStorage;
459
- this._listeners = {
460
- onRoomDeleted: (event) => {
461
- var _a;
462
- return (_a = config.onRoomDeleted) == null ? void 0 : _a.call(config, event);
463
- },
464
- onUserConnected: (event) => {
465
- var _a;
466
- return (_a = config.onUserConnected) == null ? void 0 : _a.call(config, event);
467
- },
468
- onUserDisconnected: (event) => {
469
- var _a;
470
- return (_a = config.onUserDisconnected) == null ? void 0 : _a.call(config, event);
471
- }
472
- };
473
- }
474
- _getContext() {
475
- return __async(this, null, function* () {
476
- return typeof this._context === "function" ? yield Promise.resolve(this._context(this._roomContext)) : yield Promise.resolve(this._context);
477
- });
478
- }
479
- _logDebug(...args) {
480
- if (!this._debug) return;
481
- console.log("[PLATFORM PLUV]", ...args);
482
- }
483
- };
484
-
485
- // src/platformPluv.ts
486
- var platformPluv = (config) => {
487
- const { authorize, context, crdt, debug } = config;
488
- return {
489
- authorize,
490
- context,
491
- crdt,
492
- debug,
493
- platform: () => new PluvPlatform(config)
494
- };
495
- };
496
- // Annotate the CommonJS export names for ESM import in node:
497
- 0 && (module.exports = {
498
- PluvPlatform,
499
- platformPluv
500
- });