@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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @pluv/platform-pluv@0.38.8 build /home/runner/work/pluv/pluv/packages/platform-pluv
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
  CLI Building entry: src/index.ts
@@ -8,11 +8,11 @@
8
8
  CLI Target: es6
9
9
  ESM Build start
10
10
  CJS Build start
11
- ESM dist/index.mjs 10.72 KB
12
- ESM ⚡️ Build success in 110ms
13
- CJS dist/index.js 12.26 KB
14
- CJS ⚡️ Build success in 113ms
11
+ ESM dist/index.mjs 12.35 KB
12
+ ESM ⚡️ Build success in 86ms
13
+ CJS dist/index.js 14.09 KB
14
+ CJS ⚡️ Build success in 87ms
15
15
  DTS Build start
16
- DTS ⚡️ Build success in 5913ms
16
+ DTS ⚡️ Build success in 6301ms
17
17
  DTS dist/index.d.mts 3.43 KB
18
18
  DTS dist/index.d.ts 3.43 KB
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().nullable()
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
- if (!this._webhookSecret) return c.json({ error: "Unauthorized" }, 401);
221
- if (algorithm !== SIGNATURE_ALGORITHM) return c.json({ error: "Unauthorized" }, 401);
222
- if (!signature) return c.json({ error: "Unauthorized" }, 401);
223
- const payload = yield c.req.json();
224
- const verified = yield verifyWebhook({
225
- payload: (0, import_fast_json_stable_stringify.default)(payload),
226
- signature,
227
- secret: this._webhookSecret
228
- });
229
- if (!verified) return c.json({ error: "Unauthorized" }, 401);
230
- const parsed = ZodEvent.safeParse(payload);
231
- if (!parsed.success) return c.json({ data: { ok: true } }, 200);
232
- const { event, data } = parsed.data;
233
- const context = this._context;
234
- switch (event) {
235
- case "initial-storage": {
236
- const room = data.room;
237
- const storage = typeof room === "string" ? (_d = yield (_c = this._getInitialStorage) == null ? void 0 : _c.call(this, { context, room })) != null ? _d : null : null;
238
- return c.json({ data: { storage } }, 200);
239
- }
240
- case "room-deleted": {
241
- const room = data.room;
242
- const encodedState = data.storage;
243
- yield Promise.resolve((_e = this._listeners) == null ? void 0 : _e.onRoomDeleted({ context, encodedState, room }));
244
- return c.json({ data: { room } }, 200);
245
- }
246
- case "user-connected": {
247
- const room = data.room;
248
- const encodedState = data.storage;
249
- const user = data.user;
250
- yield Promise.resolve((_f = this._listeners) == null ? void 0 : _f.onUserConnected({ context, encodedState, room, user }));
251
- return c.json({ data: { ok: true } }, 200);
252
- }
253
- case "user-disconnected": {
254
- const room = data.room;
255
- const encodedState = data.storage;
256
- const user = data.user;
257
- yield Promise.resolve((_g = this._listeners) == null ? void 0 : _g.onUserDisconnected({ context, encodedState, room, user }));
258
- return c.json({ data: { ok: true } }, 200);
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
- default:
261
- return c.json({ data: { ok: true } }, 200);
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().nullable()
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
- if (!this._webhookSecret) return c.json({ error: "Unauthorized" }, 401);
193
- if (algorithm !== SIGNATURE_ALGORITHM) return c.json({ error: "Unauthorized" }, 401);
194
- if (!signature) return c.json({ error: "Unauthorized" }, 401);
195
- const payload = yield c.req.json();
196
- const verified = yield verifyWebhook({
197
- payload: stringify(payload),
198
- signature,
199
- secret: this._webhookSecret
200
- });
201
- if (!verified) return c.json({ error: "Unauthorized" }, 401);
202
- const parsed = ZodEvent.safeParse(payload);
203
- if (!parsed.success) return c.json({ data: { ok: true } }, 200);
204
- const { event, data } = parsed.data;
205
- const context = this._context;
206
- switch (event) {
207
- case "initial-storage": {
208
- const room = data.room;
209
- const storage = typeof room === "string" ? (_d = yield (_c = this._getInitialStorage) == null ? void 0 : _c.call(this, { context, room })) != null ? _d : null : null;
210
- return c.json({ data: { storage } }, 200);
211
- }
212
- case "room-deleted": {
213
- const room = data.room;
214
- const encodedState = data.storage;
215
- yield Promise.resolve((_e = this._listeners) == null ? void 0 : _e.onRoomDeleted({ context, encodedState, room }));
216
- return c.json({ data: { room } }, 200);
217
- }
218
- case "user-connected": {
219
- const room = data.room;
220
- const encodedState = data.storage;
221
- const user = data.user;
222
- yield Promise.resolve((_f = this._listeners) == null ? void 0 : _f.onUserConnected({ context, encodedState, room, user }));
223
- return c.json({ data: { ok: true } }, 200);
224
- }
225
- case "user-disconnected": {
226
- const room = data.room;
227
- const encodedState = data.storage;
228
- const user = data.user;
229
- yield Promise.resolve((_g = this._listeners) == null ? void 0 : _g.onUserDisconnected({ context, encodedState, room, user }));
230
- return c.json({ data: { ok: true } }, 200);
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
- default:
233
- return c.json({ data: { ok: true } }, 200);
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.8",
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.13.10",
20
+ "@types/node": "^22.14.0",
21
21
  "fast-json-stable-stringify": "^2.1.0",
22
- "hono": "^4.7.4",
22
+ "hono": "^4.7.5",
23
23
  "zod": "^3.24.2",
24
- "@pluv/crdt": "^0.38.8",
25
- "@pluv/io": "^0.38.8",
26
- "@pluv/types": "^0.38.8"
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.8",
33
- "eslint-config-pluv": "^0.38.8"
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",
@@ -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
- if (!this._webhookSecret) return c.json({ error: "Unauthorized" }, 401);
198
- if (algorithm !== SIGNATURE_ALGORITHM) return c.json({ error: "Unauthorized" }, 401);
199
- if (!signature) return c.json({ error: "Unauthorized" }, 401);
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
- const payload = await c.req.json();
202
+ const payload = await c.req.json();
202
203
 
203
- const verified = await verifyWebhook({
204
- payload: stringify(payload),
205
- signature,
206
- secret: this._webhookSecret,
207
- });
204
+ const verified = await verifyWebhook({
205
+ payload: stringify(payload),
206
+ signature,
207
+ secret: this._webhookSecret,
208
+ });
208
209
 
209
- if (!verified) return c.json({ error: "Unauthorized" }, 401);
210
+ if (!verified) throw new HttpError("Unauthorized", 401);
210
211
 
211
- const parsed = ZodEvent.safeParse(payload);
212
+ const parsed = ZodEvent.safeParse(payload);
212
213
 
213
- if (!parsed.success) return c.json({ data: { ok: true } }, 200);
214
+ if (!parsed.success) throw new HttpError("Invalid request", 400);
214
215
 
215
- const { event, data } = parsed.data;
216
+ const { event, data } = parsed.data;
216
217
 
217
- const context = this._context;
218
+ const context = this._context;
218
219
 
219
- switch (event) {
220
- case "initial-storage": {
221
- const room = data.room;
222
- const storage =
223
- typeof room === "string" ? ((await this._getInitialStorage?.({ context, room })) ?? null) : null;
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
- return c.json({ data: { storage } }, 200);
226
- }
227
- case "room-deleted": {
228
- const room = data.room;
229
- const encodedState = data.storage;
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
- await Promise.resolve(this._listeners?.onRoomDeleted({ context, encodedState, room }));
234
+ await Promise.resolve(this._listeners?.onRoomDeleted({ context, encodedState, room }));
232
235
 
233
- return c.json({ data: { room } }, 200);
234
- }
235
- case "user-connected": {
236
- const room = data.room;
237
- const encodedState = data.storage;
238
- const user = data.user as any;
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
- await Promise.resolve(this._listeners?.onUserConnected({ context, encodedState, room, user }));
243
+ await Promise.resolve(this._listeners?.onUserConnected({ context, encodedState, room, user }));
241
244
 
242
- return c.json({ data: { ok: true } }, 200);
243
- }
244
- case "user-disconnected": {
245
- const room = data.room;
246
- const encodedState = data.storage;
247
- const user = data.user as any;
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
- await Promise.resolve(this._listeners?.onUserDisconnected({ context, encodedState, room, user }));
252
+ await Promise.resolve(this._listeners?.onUserDisconnected({ context, encodedState, room, user }));
250
253
 
251
- return c.json({ data: { ok: true } }, 200);
254
+ return createSuccessResponse(c, { event, room });
255
+ }
256
+ default: {
257
+ throw new HttpError("Unknown event", 400);
258
+ }
252
259
  }
253
- default:
254
- return c.json({ data: { ok: true } }, 200);
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 ZodEventInitialStorage = z.object({
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().nullable(),
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
+ };
@@ -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>;