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