@pluv/platform-pluv 0.38.8 → 0.38.10
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 +6 -6
- package/CHANGELOG.md +17 -0
- package/dist/index.js +102 -42
- package/dist/index.mjs +102 -42
- package/package.json +8 -8
- package/src/PluvPlatform.ts +53 -43
- package/src/schemas.ts +40 -5
- package/src/shared/HttpError.ts +11 -0
- package/src/shared/createErrorResponse.ts +11 -0
- package/src/shared/createSuccessResponse.ts +24 -0
- package/src/shared/index.ts +5 -0
- package/src/types.ts +18 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @pluv/platform-pluv@0.38.
|
|
2
|
+
> @pluv/platform-pluv@0.38.10 build /home/runner/work/pluv/pluv/packages/platform-pluv
|
|
3
3
|
> tsup src/index.ts --format esm,cjs --dts
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
[34mCLI[39m Target: es6
|
|
9
9
|
[34mESM[39m Build start
|
|
10
10
|
[34mCJS[39m Build start
|
|
11
|
-
[32mESM[39m [1mdist/index.mjs [22m[
|
|
12
|
-
[32mESM[39m ⚡️ Build success in
|
|
13
|
-
[32mCJS[39m [1mdist/index.js [22m[
|
|
14
|
-
[32mCJS[39m ⚡️ Build success in
|
|
11
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m12.35 KB[39m
|
|
12
|
+
[32mESM[39m ⚡️ Build success in 86ms
|
|
13
|
+
[32mCJS[39m [1mdist/index.js [22m[32m14.09 KB[39m
|
|
14
|
+
[32mCJS[39m ⚡️ Build success in 87ms
|
|
15
15
|
[34mDTS[39m Build start
|
|
16
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
+
[32mDTS[39m ⚡️ Build success in 6301ms
|
|
17
17
|
[32mDTS[39m [1mdist/index.d.mts [22m[32m3.43 KB[39m
|
|
18
18
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m3.43 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @pluv/platform-pluv
|
|
2
2
|
|
|
3
|
+
## 0.38.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- @pluv/crdt@0.38.10
|
|
8
|
+
- @pluv/io@0.38.10
|
|
9
|
+
- @pluv/types@0.38.10
|
|
10
|
+
|
|
11
|
+
## 0.38.9
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 9b3af07: Updated internal event handlers to return more meaningful status codes and room identifiers.
|
|
16
|
+
- @pluv/crdt@0.38.9
|
|
17
|
+
- @pluv/io@0.38.9
|
|
18
|
+
- @pluv/types@0.38.9
|
|
19
|
+
|
|
3
20
|
## 0.38.8
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
package/dist/index.js
CHANGED
|
@@ -80,10 +80,16 @@ var SIGNATURE_HEADER = "x-pluv-signature-256";
|
|
|
80
80
|
|
|
81
81
|
// src/schemas.ts
|
|
82
82
|
var import_zod = require("zod");
|
|
83
|
+
var ZodEventKind = import_zod.z.union([
|
|
84
|
+
import_zod.z.literal("initial-storage"),
|
|
85
|
+
import_zod.z.literal("room-deleted"),
|
|
86
|
+
import_zod.z.literal("user-connected"),
|
|
87
|
+
import_zod.z.literal("user-disconnected")
|
|
88
|
+
]);
|
|
83
89
|
var ZodEventInitialStorage = import_zod.z.object({
|
|
84
90
|
event: import_zod.z.literal("initial-storage"),
|
|
85
91
|
data: import_zod.z.object({
|
|
86
|
-
room: import_zod.z.string()
|
|
92
|
+
room: import_zod.z.string()
|
|
87
93
|
})
|
|
88
94
|
});
|
|
89
95
|
var ZodEventRoomDeleted = import_zod.z.object({
|
|
@@ -119,6 +125,34 @@ var ZodEvent = import_zod.z.discriminatedUnion("event", [
|
|
|
119
125
|
ZodEventUserConnected,
|
|
120
126
|
ZodEventUserDisconnected
|
|
121
127
|
]);
|
|
128
|
+
var ZodInitialStorageResponse = import_zod.z.object({
|
|
129
|
+
event: import_zod.z.literal("initial-storage"),
|
|
130
|
+
room: import_zod.z.string(),
|
|
131
|
+
storage: import_zod.z.string().nullable()
|
|
132
|
+
});
|
|
133
|
+
var ZodRoomDeletedResponse = import_zod.z.object({
|
|
134
|
+
event: import_zod.z.literal("room-deleted"),
|
|
135
|
+
room: import_zod.z.string()
|
|
136
|
+
});
|
|
137
|
+
var ZodUserConnectedResponse = import_zod.z.object({
|
|
138
|
+
event: import_zod.z.literal("user-connected"),
|
|
139
|
+
room: import_zod.z.string()
|
|
140
|
+
});
|
|
141
|
+
var ZodUserDisconnectedResponse = import_zod.z.object({
|
|
142
|
+
event: import_zod.z.literal("user-disconnected"),
|
|
143
|
+
room: import_zod.z.string()
|
|
144
|
+
});
|
|
145
|
+
var ZodEventResponse = import_zod.z.discriminatedUnion("event", [
|
|
146
|
+
ZodInitialStorageResponse,
|
|
147
|
+
ZodRoomDeletedResponse,
|
|
148
|
+
ZodUserConnectedResponse,
|
|
149
|
+
ZodUserDisconnectedResponse
|
|
150
|
+
]);
|
|
151
|
+
|
|
152
|
+
// src/shared/createErrorResponse.ts
|
|
153
|
+
var createErrorResponse = (c, error, status) => {
|
|
154
|
+
return c.json({ ok: false, error: { message: error.message } }, status);
|
|
155
|
+
};
|
|
122
156
|
|
|
123
157
|
// src/shared/getCrypto.ts
|
|
124
158
|
var getCrypto = () => {
|
|
@@ -147,6 +181,25 @@ var createHmac = (params) => __async(void 0, null, function* () {
|
|
|
147
181
|
return { algorithm: "sha256", hmac };
|
|
148
182
|
});
|
|
149
183
|
|
|
184
|
+
// src/shared/createSuccessResponse.ts
|
|
185
|
+
var createSuccessResponse = (c, data, status = 200) => {
|
|
186
|
+
return c.json(
|
|
187
|
+
{
|
|
188
|
+
ok: true,
|
|
189
|
+
data: ZodEventResponse.parse(data)
|
|
190
|
+
},
|
|
191
|
+
status
|
|
192
|
+
);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// src/shared/HttpError.ts
|
|
196
|
+
var HttpError = class extends Error {
|
|
197
|
+
constructor(message, status) {
|
|
198
|
+
super(message);
|
|
199
|
+
this.status = status;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
150
203
|
// src/shared/timingSafeEqual.ts
|
|
151
204
|
var timingSafeEqual = (a, b) => {
|
|
152
205
|
if (a.length !== b.length) return false;
|
|
@@ -217,48 +270,55 @@ var PluvPlatform = class extends import_io.AbstractPlatform {
|
|
|
217
270
|
this._webhooksRouter = new import_hono.Hono().basePath("/").post("/", (c) => __async(this, null, function* () {
|
|
218
271
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
219
272
|
const [algorithm, signature] = (_b = (_a = c.req.header(SIGNATURE_HEADER)) == null ? void 0 : _a.split("=")) != null ? _b : [];
|
|
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
|
-
|
|
273
|
+
try {
|
|
274
|
+
if (!this._webhookSecret) throw new HttpError("Unauthorized", 401);
|
|
275
|
+
if (algorithm !== SIGNATURE_ALGORITHM) throw new HttpError("Unauthorized", 401);
|
|
276
|
+
if (!signature) throw new HttpError("Unauthorized", 401);
|
|
277
|
+
const payload = yield c.req.json();
|
|
278
|
+
const verified = yield verifyWebhook({
|
|
279
|
+
payload: (0, import_fast_json_stable_stringify.default)(payload),
|
|
280
|
+
signature,
|
|
281
|
+
secret: this._webhookSecret
|
|
282
|
+
});
|
|
283
|
+
if (!verified) throw new HttpError("Unauthorized", 401);
|
|
284
|
+
const parsed = ZodEvent.safeParse(payload);
|
|
285
|
+
if (!parsed.success) throw new HttpError("Invalid request", 400);
|
|
286
|
+
const { event, data } = parsed.data;
|
|
287
|
+
const context = this._context;
|
|
288
|
+
switch (event) {
|
|
289
|
+
case "initial-storage": {
|
|
290
|
+
const room = data.room;
|
|
291
|
+
const storage = typeof room === "string" ? (_d = yield (_c = this._getInitialStorage) == null ? void 0 : _c.call(this, { context, room })) != null ? _d : null : null;
|
|
292
|
+
return createSuccessResponse(c, { event, room, storage });
|
|
293
|
+
}
|
|
294
|
+
case "room-deleted": {
|
|
295
|
+
const room = data.room;
|
|
296
|
+
const encodedState = data.storage;
|
|
297
|
+
yield Promise.resolve((_e = this._listeners) == null ? void 0 : _e.onRoomDeleted({ context, encodedState, room }));
|
|
298
|
+
return createSuccessResponse(c, { event, room });
|
|
299
|
+
}
|
|
300
|
+
case "user-connected": {
|
|
301
|
+
const room = data.room;
|
|
302
|
+
const encodedState = data.storage;
|
|
303
|
+
const user = data.user;
|
|
304
|
+
yield Promise.resolve((_f = this._listeners) == null ? void 0 : _f.onUserConnected({ context, encodedState, room, user }));
|
|
305
|
+
return createSuccessResponse(c, { event, room });
|
|
306
|
+
}
|
|
307
|
+
case "user-disconnected": {
|
|
308
|
+
const room = data.room;
|
|
309
|
+
const encodedState = data.storage;
|
|
310
|
+
const user = data.user;
|
|
311
|
+
yield Promise.resolve((_g = this._listeners) == null ? void 0 : _g.onUserDisconnected({ context, encodedState, room, user }));
|
|
312
|
+
return createSuccessResponse(c, { event, room });
|
|
313
|
+
}
|
|
314
|
+
default: {
|
|
315
|
+
throw new HttpError("Unknown event", 400);
|
|
316
|
+
}
|
|
259
317
|
}
|
|
260
|
-
|
|
261
|
-
|
|
318
|
+
} catch (error) {
|
|
319
|
+
const message = error instanceof Error ? error.message : "Unexpected error";
|
|
320
|
+
const status = error instanceof HttpError ? error.status : 500;
|
|
321
|
+
return createErrorResponse(c, { message }, status);
|
|
262
322
|
}
|
|
263
323
|
}));
|
|
264
324
|
const { _defs, context, basePath, publicKey, secretKey, webhookSecret } = params;
|
package/dist/index.mjs
CHANGED
|
@@ -52,10 +52,16 @@ var SIGNATURE_HEADER = "x-pluv-signature-256";
|
|
|
52
52
|
|
|
53
53
|
// src/schemas.ts
|
|
54
54
|
import { z } from "zod";
|
|
55
|
+
var ZodEventKind = z.union([
|
|
56
|
+
z.literal("initial-storage"),
|
|
57
|
+
z.literal("room-deleted"),
|
|
58
|
+
z.literal("user-connected"),
|
|
59
|
+
z.literal("user-disconnected")
|
|
60
|
+
]);
|
|
55
61
|
var ZodEventInitialStorage = z.object({
|
|
56
62
|
event: z.literal("initial-storage"),
|
|
57
63
|
data: z.object({
|
|
58
|
-
room: z.string()
|
|
64
|
+
room: z.string()
|
|
59
65
|
})
|
|
60
66
|
});
|
|
61
67
|
var ZodEventRoomDeleted = z.object({
|
|
@@ -91,6 +97,34 @@ var ZodEvent = z.discriminatedUnion("event", [
|
|
|
91
97
|
ZodEventUserConnected,
|
|
92
98
|
ZodEventUserDisconnected
|
|
93
99
|
]);
|
|
100
|
+
var ZodInitialStorageResponse = z.object({
|
|
101
|
+
event: z.literal("initial-storage"),
|
|
102
|
+
room: z.string(),
|
|
103
|
+
storage: z.string().nullable()
|
|
104
|
+
});
|
|
105
|
+
var ZodRoomDeletedResponse = z.object({
|
|
106
|
+
event: z.literal("room-deleted"),
|
|
107
|
+
room: z.string()
|
|
108
|
+
});
|
|
109
|
+
var ZodUserConnectedResponse = z.object({
|
|
110
|
+
event: z.literal("user-connected"),
|
|
111
|
+
room: z.string()
|
|
112
|
+
});
|
|
113
|
+
var ZodUserDisconnectedResponse = z.object({
|
|
114
|
+
event: z.literal("user-disconnected"),
|
|
115
|
+
room: z.string()
|
|
116
|
+
});
|
|
117
|
+
var ZodEventResponse = z.discriminatedUnion("event", [
|
|
118
|
+
ZodInitialStorageResponse,
|
|
119
|
+
ZodRoomDeletedResponse,
|
|
120
|
+
ZodUserConnectedResponse,
|
|
121
|
+
ZodUserDisconnectedResponse
|
|
122
|
+
]);
|
|
123
|
+
|
|
124
|
+
// src/shared/createErrorResponse.ts
|
|
125
|
+
var createErrorResponse = (c, error, status) => {
|
|
126
|
+
return c.json({ ok: false, error: { message: error.message } }, status);
|
|
127
|
+
};
|
|
94
128
|
|
|
95
129
|
// src/shared/getCrypto.ts
|
|
96
130
|
var getCrypto = () => {
|
|
@@ -119,6 +153,25 @@ var createHmac = (params) => __async(void 0, null, function* () {
|
|
|
119
153
|
return { algorithm: "sha256", hmac };
|
|
120
154
|
});
|
|
121
155
|
|
|
156
|
+
// src/shared/createSuccessResponse.ts
|
|
157
|
+
var createSuccessResponse = (c, data, status = 200) => {
|
|
158
|
+
return c.json(
|
|
159
|
+
{
|
|
160
|
+
ok: true,
|
|
161
|
+
data: ZodEventResponse.parse(data)
|
|
162
|
+
},
|
|
163
|
+
status
|
|
164
|
+
);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// src/shared/HttpError.ts
|
|
168
|
+
var HttpError = class extends Error {
|
|
169
|
+
constructor(message, status) {
|
|
170
|
+
super(message);
|
|
171
|
+
this.status = status;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
122
175
|
// src/shared/timingSafeEqual.ts
|
|
123
176
|
var timingSafeEqual = (a, b) => {
|
|
124
177
|
if (a.length !== b.length) return false;
|
|
@@ -189,48 +242,55 @@ var PluvPlatform = class extends AbstractPlatform {
|
|
|
189
242
|
this._webhooksRouter = new Hono().basePath("/").post("/", (c) => __async(this, null, function* () {
|
|
190
243
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
191
244
|
const [algorithm, signature] = (_b = (_a = c.req.header(SIGNATURE_HEADER)) == null ? void 0 : _a.split("=")) != null ? _b : [];
|
|
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
|
-
|
|
245
|
+
try {
|
|
246
|
+
if (!this._webhookSecret) throw new HttpError("Unauthorized", 401);
|
|
247
|
+
if (algorithm !== SIGNATURE_ALGORITHM) throw new HttpError("Unauthorized", 401);
|
|
248
|
+
if (!signature) throw new HttpError("Unauthorized", 401);
|
|
249
|
+
const payload = yield c.req.json();
|
|
250
|
+
const verified = yield verifyWebhook({
|
|
251
|
+
payload: stringify(payload),
|
|
252
|
+
signature,
|
|
253
|
+
secret: this._webhookSecret
|
|
254
|
+
});
|
|
255
|
+
if (!verified) throw new HttpError("Unauthorized", 401);
|
|
256
|
+
const parsed = ZodEvent.safeParse(payload);
|
|
257
|
+
if (!parsed.success) throw new HttpError("Invalid request", 400);
|
|
258
|
+
const { event, data } = parsed.data;
|
|
259
|
+
const context = this._context;
|
|
260
|
+
switch (event) {
|
|
261
|
+
case "initial-storage": {
|
|
262
|
+
const room = data.room;
|
|
263
|
+
const storage = typeof room === "string" ? (_d = yield (_c = this._getInitialStorage) == null ? void 0 : _c.call(this, { context, room })) != null ? _d : null : null;
|
|
264
|
+
return createSuccessResponse(c, { event, room, storage });
|
|
265
|
+
}
|
|
266
|
+
case "room-deleted": {
|
|
267
|
+
const room = data.room;
|
|
268
|
+
const encodedState = data.storage;
|
|
269
|
+
yield Promise.resolve((_e = this._listeners) == null ? void 0 : _e.onRoomDeleted({ context, encodedState, room }));
|
|
270
|
+
return createSuccessResponse(c, { event, room });
|
|
271
|
+
}
|
|
272
|
+
case "user-connected": {
|
|
273
|
+
const room = data.room;
|
|
274
|
+
const encodedState = data.storage;
|
|
275
|
+
const user = data.user;
|
|
276
|
+
yield Promise.resolve((_f = this._listeners) == null ? void 0 : _f.onUserConnected({ context, encodedState, room, user }));
|
|
277
|
+
return createSuccessResponse(c, { event, room });
|
|
278
|
+
}
|
|
279
|
+
case "user-disconnected": {
|
|
280
|
+
const room = data.room;
|
|
281
|
+
const encodedState = data.storage;
|
|
282
|
+
const user = data.user;
|
|
283
|
+
yield Promise.resolve((_g = this._listeners) == null ? void 0 : _g.onUserDisconnected({ context, encodedState, room, user }));
|
|
284
|
+
return createSuccessResponse(c, { event, room });
|
|
285
|
+
}
|
|
286
|
+
default: {
|
|
287
|
+
throw new HttpError("Unknown event", 400);
|
|
288
|
+
}
|
|
231
289
|
}
|
|
232
|
-
|
|
233
|
-
|
|
290
|
+
} catch (error) {
|
|
291
|
+
const message = error instanceof Error ? error.message : "Unexpected error";
|
|
292
|
+
const status = error instanceof HttpError ? error.status : 500;
|
|
293
|
+
return createErrorResponse(c, { message }, status);
|
|
234
294
|
}
|
|
235
295
|
}));
|
|
236
296
|
const { _defs, context, basePath, publicKey, secretKey, webhookSecret } = params;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pluv/platform-pluv",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.10",
|
|
4
4
|
"description": "@pluv/io adapter for pluv.io",
|
|
5
5
|
"author": "leedavidcs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,20 +17,20 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@types/node": "^22.
|
|
20
|
+
"@types/node": "^22.14.0",
|
|
21
21
|
"fast-json-stable-stringify": "^2.1.0",
|
|
22
|
-
"hono": "^4.7.
|
|
22
|
+
"hono": "^4.7.5",
|
|
23
23
|
"zod": "^3.24.2",
|
|
24
|
-
"@pluv/crdt": "^0.38.
|
|
25
|
-
"@pluv/io": "^0.38.
|
|
26
|
-
"@pluv/types": "^0.38.
|
|
24
|
+
"@pluv/crdt": "^0.38.10",
|
|
25
|
+
"@pluv/io": "^0.38.10",
|
|
26
|
+
"@pluv/types": "^0.38.10"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"eslint": "^8.57.1",
|
|
30
30
|
"tsup": "^8.4.0",
|
|
31
31
|
"typescript": "^5.8.2",
|
|
32
|
-
"@pluv/tsconfig": "^0.38.
|
|
33
|
-
"eslint-config-pluv": "^0.38.
|
|
32
|
+
"@pluv/tsconfig": "^0.38.10",
|
|
33
|
+
"eslint-config-pluv": "^0.38.10"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
36
|
"build": "tsup src/index.ts --format esm,cjs --dts",
|
package/src/PluvPlatform.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { Hono } from "hono";
|
|
|
14
14
|
import type { BlankEnv, BlankInput } from "hono/types";
|
|
15
15
|
import { SIGNATURE_ALGORITHM, SIGNATURE_HEADER } from "./constants";
|
|
16
16
|
import { ZodEvent } from "./schemas";
|
|
17
|
-
import { verifyWebhook } from "./shared";
|
|
17
|
+
import { createErrorResponse, createSuccessResponse, HttpError, verifyWebhook } from "./shared";
|
|
18
18
|
import type { PluvIOEndpoints, PluvIOListeners } from "./types";
|
|
19
19
|
|
|
20
20
|
export interface PluvPlatformConfig<TContext extends Record<string, any> = {}> {
|
|
@@ -194,64 +194,74 @@ export class PluvPlatform<
|
|
|
194
194
|
private _webhooksRouter = new Hono().basePath("/").post("/", async (c: Context<BlankEnv, "/", BlankInput>) => {
|
|
195
195
|
const [algorithm, signature] = c.req.header(SIGNATURE_HEADER)?.split("=") ?? [];
|
|
196
196
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
197
|
+
try {
|
|
198
|
+
if (!this._webhookSecret) throw new HttpError("Unauthorized", 401);
|
|
199
|
+
if (algorithm !== SIGNATURE_ALGORITHM) throw new HttpError("Unauthorized", 401);
|
|
200
|
+
if (!signature) throw new HttpError("Unauthorized", 401);
|
|
200
201
|
|
|
201
|
-
|
|
202
|
+
const payload = await c.req.json();
|
|
202
203
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
204
|
+
const verified = await verifyWebhook({
|
|
205
|
+
payload: stringify(payload),
|
|
206
|
+
signature,
|
|
207
|
+
secret: this._webhookSecret,
|
|
208
|
+
});
|
|
208
209
|
|
|
209
|
-
|
|
210
|
+
if (!verified) throw new HttpError("Unauthorized", 401);
|
|
210
211
|
|
|
211
|
-
|
|
212
|
+
const parsed = ZodEvent.safeParse(payload);
|
|
212
213
|
|
|
213
|
-
|
|
214
|
+
if (!parsed.success) throw new HttpError("Invalid request", 400);
|
|
214
215
|
|
|
215
|
-
|
|
216
|
+
const { event, data } = parsed.data;
|
|
216
217
|
|
|
217
|
-
|
|
218
|
+
const context = this._context;
|
|
218
219
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
220
|
+
switch (event) {
|
|
221
|
+
case "initial-storage": {
|
|
222
|
+
const room = data.room;
|
|
223
|
+
const storage =
|
|
224
|
+
typeof room === "string"
|
|
225
|
+
? ((await this._getInitialStorage?.({ context, room })) ?? null)
|
|
226
|
+
: null;
|
|
224
227
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
228
|
+
return createSuccessResponse(c, { event, room, storage });
|
|
229
|
+
}
|
|
230
|
+
case "room-deleted": {
|
|
231
|
+
const room = data.room;
|
|
232
|
+
const encodedState = data.storage;
|
|
230
233
|
|
|
231
|
-
|
|
234
|
+
await Promise.resolve(this._listeners?.onRoomDeleted({ context, encodedState, room }));
|
|
232
235
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
236
|
+
return createSuccessResponse(c, { event, room });
|
|
237
|
+
}
|
|
238
|
+
case "user-connected": {
|
|
239
|
+
const room = data.room;
|
|
240
|
+
const encodedState = data.storage;
|
|
241
|
+
const user = data.user as any;
|
|
239
242
|
|
|
240
|
-
|
|
243
|
+
await Promise.resolve(this._listeners?.onUserConnected({ context, encodedState, room, user }));
|
|
241
244
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
245
|
+
return createSuccessResponse(c, { event, room });
|
|
246
|
+
}
|
|
247
|
+
case "user-disconnected": {
|
|
248
|
+
const room = data.room;
|
|
249
|
+
const encodedState = data.storage;
|
|
250
|
+
const user = data.user as any;
|
|
248
251
|
|
|
249
|
-
|
|
252
|
+
await Promise.resolve(this._listeners?.onUserDisconnected({ context, encodedState, room, user }));
|
|
250
253
|
|
|
251
|
-
|
|
254
|
+
return createSuccessResponse(c, { event, room });
|
|
255
|
+
}
|
|
256
|
+
default: {
|
|
257
|
+
throw new HttpError("Unknown event", 400);
|
|
258
|
+
}
|
|
252
259
|
}
|
|
253
|
-
|
|
254
|
-
|
|
260
|
+
} catch (error) {
|
|
261
|
+
const message = error instanceof Error ? error.message : "Unexpected error";
|
|
262
|
+
const status = error instanceof HttpError ? error.status : 500;
|
|
263
|
+
|
|
264
|
+
return createErrorResponse(c, { message }, status);
|
|
255
265
|
}
|
|
256
266
|
});
|
|
257
267
|
}
|
package/src/schemas.ts
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
export const ZodEventKind = z.union([
|
|
4
|
+
z.literal("initial-storage"),
|
|
5
|
+
z.literal("room-deleted"),
|
|
6
|
+
z.literal("user-connected"),
|
|
7
|
+
z.literal("user-disconnected"),
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
export const ZodEventInitialStorage = z.object({
|
|
4
11
|
event: z.literal("initial-storage"),
|
|
5
12
|
data: z.object({
|
|
6
|
-
room: z.string()
|
|
13
|
+
room: z.string(),
|
|
7
14
|
}),
|
|
8
15
|
});
|
|
9
16
|
|
|
10
|
-
const ZodEventRoomDeleted = z.object({
|
|
17
|
+
export const ZodEventRoomDeleted = z.object({
|
|
11
18
|
event: z.literal("room-deleted"),
|
|
12
19
|
data: z.object({
|
|
13
20
|
room: z.string(),
|
|
@@ -15,7 +22,7 @@ const ZodEventRoomDeleted = z.object({
|
|
|
15
22
|
}),
|
|
16
23
|
});
|
|
17
24
|
|
|
18
|
-
const ZodEventUserConnected = z.object({
|
|
25
|
+
export const ZodEventUserConnected = z.object({
|
|
19
26
|
event: z.literal("user-connected"),
|
|
20
27
|
data: z.object({
|
|
21
28
|
room: z.string(),
|
|
@@ -28,7 +35,7 @@ const ZodEventUserConnected = z.object({
|
|
|
28
35
|
}),
|
|
29
36
|
});
|
|
30
37
|
|
|
31
|
-
const ZodEventUserDisconnected = z.object({
|
|
38
|
+
export const ZodEventUserDisconnected = z.object({
|
|
32
39
|
event: z.literal("user-disconnected"),
|
|
33
40
|
data: z.object({
|
|
34
41
|
room: z.string(),
|
|
@@ -47,3 +54,31 @@ export const ZodEvent = z.discriminatedUnion("event", [
|
|
|
47
54
|
ZodEventUserConnected,
|
|
48
55
|
ZodEventUserDisconnected,
|
|
49
56
|
]);
|
|
57
|
+
|
|
58
|
+
export const ZodInitialStorageResponse = z.object({
|
|
59
|
+
event: z.literal("initial-storage"),
|
|
60
|
+
room: z.string(),
|
|
61
|
+
storage: z.string().nullable(),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export const ZodRoomDeletedResponse = z.object({
|
|
65
|
+
event: z.literal("room-deleted"),
|
|
66
|
+
room: z.string(),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export const ZodUserConnectedResponse = z.object({
|
|
70
|
+
event: z.literal("user-connected"),
|
|
71
|
+
room: z.string(),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
export const ZodUserDisconnectedResponse = z.object({
|
|
75
|
+
event: z.literal("user-disconnected"),
|
|
76
|
+
room: z.string(),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export const ZodEventResponse = z.discriminatedUnion("event", [
|
|
80
|
+
ZodInitialStorageResponse,
|
|
81
|
+
ZodRoomDeletedResponse,
|
|
82
|
+
ZodUserConnectedResponse,
|
|
83
|
+
ZodUserDisconnectedResponse,
|
|
84
|
+
]);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ContentfulStatusCode } from "hono/utils/http-status";
|
|
2
|
+
|
|
3
|
+
export class HttpError extends Error {
|
|
4
|
+
readonly status: ContentfulStatusCode;
|
|
5
|
+
|
|
6
|
+
constructor(message: string, status: ContentfulStatusCode) {
|
|
7
|
+
super(message);
|
|
8
|
+
|
|
9
|
+
this.status = status;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
import type { BlankEnv, BlankInput } from "hono/types";
|
|
3
|
+
import type { ContentfulStatusCode } from "hono/utils/http-status";
|
|
4
|
+
|
|
5
|
+
export const createErrorResponse = <TStatus extends ContentfulStatusCode>(
|
|
6
|
+
c: Context<BlankEnv, "/", BlankInput>,
|
|
7
|
+
error: { message: string },
|
|
8
|
+
status: TStatus,
|
|
9
|
+
) => {
|
|
10
|
+
return c.json({ ok: false, error: { message: error.message } }, status);
|
|
11
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
import type { BlankEnv, BlankInput } from "hono/types";
|
|
3
|
+
import type { ContentfulStatusCode } from "hono/utils/http-status";
|
|
4
|
+
import { ZodEventResponse } from "../schemas";
|
|
5
|
+
import type { EventResponse } from "../types";
|
|
6
|
+
|
|
7
|
+
export interface CreateSuccessResponseParams<TStatus extends ContentfulStatusCode> {
|
|
8
|
+
data: EventResponse;
|
|
9
|
+
status?: TStatus;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const createSuccessResponse = <TStatus extends ContentfulStatusCode = 200>(
|
|
13
|
+
c: Context<BlankEnv, "/", BlankInput>,
|
|
14
|
+
data: EventResponse,
|
|
15
|
+
status: TStatus = 200 as TStatus,
|
|
16
|
+
) => {
|
|
17
|
+
return c.json(
|
|
18
|
+
{
|
|
19
|
+
ok: true,
|
|
20
|
+
data: ZodEventResponse.parse(data),
|
|
21
|
+
},
|
|
22
|
+
status,
|
|
23
|
+
);
|
|
24
|
+
};
|
package/src/shared/index.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
export { createErrorResponse } from "./createErrorResponse";
|
|
2
|
+
export type { CreateErrorResponseParams } from "./createErrorResponse";
|
|
1
3
|
export { createHmac } from "./createHmac";
|
|
2
4
|
export type { CreateHmacParams } from "./createHmac";
|
|
5
|
+
export { createSuccessResponse } from "./createSuccessResponse";
|
|
6
|
+
export type { CreateSuccessResponseParams } from "./createSuccessResponse";
|
|
3
7
|
export { getCrypto } from "./getCrypto";
|
|
8
|
+
export { HttpError } from "./HttpError";
|
|
4
9
|
export { signWebhook } from "./signWebhook";
|
|
5
10
|
export type { SignWebhookParams } from "./signWebhook";
|
|
6
11
|
export { timingSafeEqual } from "./timingSafeEqual";
|
package/src/types.ts
CHANGED
|
@@ -6,7 +6,16 @@ import type {
|
|
|
6
6
|
IOUserDisconnectedEvent,
|
|
7
7
|
} from "@pluv/io";
|
|
8
8
|
import type { InputZodLike } from "@pluv/types";
|
|
9
|
+
import type { z } from "zod";
|
|
9
10
|
import type { PluvPlatform } from "./PluvPlatform";
|
|
11
|
+
import type {
|
|
12
|
+
ZodEventKind,
|
|
13
|
+
ZodEventResponse,
|
|
14
|
+
ZodInitialStorageResponse,
|
|
15
|
+
ZodRoomDeletedResponse,
|
|
16
|
+
ZodUserConnectedResponse,
|
|
17
|
+
ZodUserDisconnectedResponse,
|
|
18
|
+
} from "./schemas";
|
|
10
19
|
|
|
11
20
|
export interface PluvIOEndpoints {
|
|
12
21
|
createToken: string;
|
|
@@ -22,3 +31,12 @@ export type PluvIOListeners<TContext extends Record<string, any> = {}, TUser ext
|
|
|
22
31
|
event: IOUserDisconnectedEvent<PluvPlatform<TContext>, { user: InputZodLike<TUser> }, TContext>,
|
|
23
32
|
) => void;
|
|
24
33
|
};
|
|
34
|
+
|
|
35
|
+
export type EventKind = z.output<typeof ZodEventKind>;
|
|
36
|
+
|
|
37
|
+
export type InitialStorageResponse = z.output<typeof ZodInitialStorageResponse>;
|
|
38
|
+
export type RoomDeletedResponse = z.output<typeof ZodRoomDeletedResponse>;
|
|
39
|
+
export type UserConnectedResponse = z.output<typeof ZodUserConnectedResponse>;
|
|
40
|
+
export type UserDisconnectedResponse = z.output<typeof ZodUserDisconnectedResponse>;
|
|
41
|
+
|
|
42
|
+
export type EventResponse = z.output<typeof ZodEventResponse>;
|