@edge-base/server 0.2.5 → 0.2.7
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/admin-build/_app/immutable/chunks/{DILS_-VJ.js → B3CvhH3c.js} +1 -1
- package/admin-build/_app/immutable/chunks/BDYewzou.js +1 -0
- package/admin-build/_app/immutable/chunks/{Cdm5zBRA.js → BEM1BeVF.js} +1 -1
- package/admin-build/_app/immutable/chunks/{Dt4vL4Df.js → BYL_uBga.js} +1 -1
- package/admin-build/_app/immutable/chunks/{B94PilAN.js → BYyykAbh.js} +1 -1
- package/admin-build/_app/immutable/chunks/BaUG2TJ-.js +1 -0
- package/admin-build/_app/immutable/chunks/{C72lTcG0.js → Bcs4KYNp.js} +1 -1
- package/admin-build/_app/immutable/chunks/{D2j3I1VQ.js → BfpUQYr3.js} +1 -1
- package/admin-build/_app/immutable/chunks/BhCO1Fpt.js +1 -0
- package/admin-build/_app/immutable/chunks/{B8s_s9QY.js → BkZCgsc3.js} +1 -1
- package/admin-build/_app/immutable/chunks/CIOC1v_q.js +128 -0
- package/admin-build/_app/immutable/chunks/CjcrXziO.js +2 -0
- package/admin-build/_app/immutable/chunks/CvczjTXx.js +1 -0
- package/admin-build/_app/immutable/chunks/D1u3u7xu.js +1 -0
- package/admin-build/_app/immutable/chunks/{B0HRJ657.js → DOOPbWwG.js} +1 -1
- package/admin-build/_app/immutable/chunks/{BqTb6Mxk.js → DaXO-sFP.js} +1 -1
- package/admin-build/_app/immutable/chunks/DnpbvAPi.js +1 -0
- package/admin-build/_app/immutable/chunks/{B6MschND.js → Dz9cUCuv.js} +1 -1
- package/admin-build/_app/immutable/chunks/{CaVKAiCe.js → Tea2dBJ8.js} +1 -1
- package/admin-build/_app/immutable/chunks/{Z41NK6i6.js → bguI1TeA.js} +1 -1
- package/admin-build/_app/immutable/chunks/{J2Gw0SMu.js → ejoEf2I5.js} +1 -1
- package/admin-build/_app/immutable/chunks/{B2TnDKF7.js → iEyeblJR.js} +1 -1
- package/admin-build/_app/immutable/chunks/{_teD5ji5.js → nlAMTi52.js} +1 -1
- package/admin-build/_app/immutable/chunks/qKdzaeX3.js +1 -0
- package/admin-build/_app/immutable/entry/{app.D3flihMw.js → app.DoUaxnew.js} +2 -2
- package/admin-build/_app/immutable/entry/start.MmZh8oBH.js +1 -0
- package/admin-build/_app/immutable/nodes/{0.CdczqZLK.js → 0.Dsxi8s7i.js} +1 -1
- package/admin-build/_app/immutable/nodes/1.Cp2l-hol.js +1 -0
- package/admin-build/_app/immutable/nodes/10.4oY6m8Nz.js +1 -0
- package/admin-build/_app/immutable/nodes/11.DfcozD4J.js +1 -0
- package/admin-build/_app/immutable/nodes/12.uJgZdCIA.js +1 -0
- package/admin-build/_app/immutable/nodes/13.CaN1kRev.js +110 -0
- package/admin-build/_app/immutable/nodes/14.DQ5xIi3s.js +3 -0
- package/admin-build/_app/immutable/nodes/15.B_EkebTJ.js +1 -0
- package/admin-build/_app/immutable/nodes/{16.BR7WwQrS.js → 16.Tko1ZX8-.js} +1 -1
- package/admin-build/_app/immutable/nodes/{17.Cm57KKXV.js → 17.BCmWMJX9.js} +1 -1
- package/admin-build/_app/immutable/nodes/18.hmGhl1O2.js +1 -0
- package/admin-build/_app/immutable/nodes/19.D-1infOo.js +2 -0
- package/admin-build/_app/immutable/nodes/{20.DnHeFlTv.js → 20.CY4KKcBL.js} +1 -1
- package/admin-build/_app/immutable/nodes/21.B9lbNUQr.js +1 -0
- package/admin-build/_app/immutable/nodes/22.14Vd7bnt.js +1 -0
- package/admin-build/_app/immutable/nodes/{23.CWSGMcKJ.js → 23.Be6jK77o.js} +2 -2
- package/admin-build/_app/immutable/nodes/24.CSTFkr6R.js +2 -0
- package/admin-build/_app/immutable/nodes/25.DRTg8fHc.js +2 -0
- package/admin-build/_app/immutable/nodes/26.DKt-9lwQ.js +1 -0
- package/admin-build/_app/immutable/nodes/27.D5caPu0F.js +1 -0
- package/admin-build/_app/immutable/nodes/28.hJhlnlyY.js +1 -0
- package/admin-build/_app/immutable/nodes/29.CDYBzFyT.js +1 -0
- package/admin-build/_app/immutable/nodes/{3.B6q-7qr8.js → 3.DMyKwkGn.js} +1 -1
- package/admin-build/_app/immutable/nodes/30.BaHNeEmc.js +1 -0
- package/admin-build/_app/immutable/nodes/31.C6PV5L-2.js +1 -0
- package/admin-build/_app/immutable/nodes/4.9E118Ftm.js +1 -0
- package/admin-build/_app/immutable/nodes/5.D8guAl3v.js +1 -0
- package/admin-build/_app/immutable/nodes/6.D1u__DtT.js +1 -0
- package/admin-build/_app/immutable/nodes/7.DWXHnRFf.js +1 -0
- package/admin-build/_app/immutable/nodes/8.Dojd8krc.js +1 -0
- package/admin-build/_app/immutable/nodes/9.CLtrr0K_.js +1 -0
- package/admin-build/_app/version.json +1 -1
- package/admin-build/index.html +7 -7
- package/openapi.json +6 -1941
- package/package.json +3 -3
- package/src/__tests__/openapi-coverage.test.ts +0 -6
- package/src/__tests__/push-handlers.test.ts +1 -1
- package/src/__tests__/room-auth-state-loss.test.ts +6 -0
- package/src/__tests__/room-handler-context.test.ts +0 -31
- package/src/__tests__/room-rate-limit-scopes.test.ts +1 -5
- package/src/__tests__/room-runtime-routing.test.ts +24 -111
- package/src/__tests__/route-parser.test.ts +6 -0
- package/src/__tests__/schema.test.ts +15 -6
- package/src/__tests__/smoke-skip-report.test.ts +1 -1
- package/src/durable-objects/database-do.ts +7 -1
- package/src/durable-objects/room-runtime-base.ts +290 -57
- package/src/durable-objects/rooms-do.ts +212 -1336
- package/src/index.ts +23 -9
- package/src/lib/d1-handler.ts +32 -17
- package/src/lib/openapi.ts +1 -4
- package/src/lib/postgres-handler.ts +24 -12
- package/src/lib/route-parser.ts +3 -0
- package/src/lib/schemas.ts +12 -2
- package/src/middleware/captcha-verify.ts +16 -3
- package/src/middleware/error-handler.ts +1 -1
- package/src/middleware/rules.ts +28 -9
- package/src/routes/admin-auth.ts +3 -3
- package/src/routes/admin.ts +13 -8
- package/src/routes/analytics-api.ts +3 -3
- package/src/routes/auth.ts +1 -1
- package/src/routes/backup.ts +1 -1
- package/src/routes/d1.ts +14 -7
- package/src/routes/database-live.ts +13 -6
- package/src/routes/kv.ts +21 -10
- package/src/routes/oauth.ts +1 -1
- package/src/routes/push.ts +119 -77
- package/src/routes/room.ts +203 -280
- package/src/routes/schema-endpoint.ts +2 -2
- package/src/routes/sql.ts +10 -6
- package/src/routes/storage.ts +4 -2
- package/src/routes/vectorize.ts +16 -4
- package/src/types.ts +1 -14
- package/admin-build/_app/immutable/chunks/6oMK_164.js +0 -1
- package/admin-build/_app/immutable/chunks/BEW7Ez_g.js +0 -1
- package/admin-build/_app/immutable/chunks/BoOooyH6.js +0 -1
- package/admin-build/_app/immutable/chunks/BvHnF5tV.js +0 -1
- package/admin-build/_app/immutable/chunks/CoI6jjbg.js +0 -2
- package/admin-build/_app/immutable/chunks/CrOZMmdF.js +0 -1
- package/admin-build/_app/immutable/chunks/Cw6OYcq-.js +0 -1
- package/admin-build/_app/immutable/chunks/DPdQ7z0T.js +0 -128
- package/admin-build/_app/immutable/chunks/pUxw8jfq.js +0 -1
- package/admin-build/_app/immutable/entry/start.Cl6sLxnz.js +0 -1
- package/admin-build/_app/immutable/nodes/1.DxcSsEqS.js +0 -1
- package/admin-build/_app/immutable/nodes/10.DuAd4aIm.js +0 -1
- package/admin-build/_app/immutable/nodes/11.0jgHQL92.js +0 -1
- package/admin-build/_app/immutable/nodes/12.CKNPqmyy.js +0 -1
- package/admin-build/_app/immutable/nodes/13.B1p2POXS.js +0 -110
- package/admin-build/_app/immutable/nodes/14.Bb-REBND.js +0 -3
- package/admin-build/_app/immutable/nodes/15.1uBFCX0X.js +0 -1
- package/admin-build/_app/immutable/nodes/18.CoiwfAuQ.js +0 -1
- package/admin-build/_app/immutable/nodes/19.B8ZdLlXj.js +0 -2
- package/admin-build/_app/immutable/nodes/21.CJFaf0Ia.js +0 -1
- package/admin-build/_app/immutable/nodes/22.CItETFzy.js +0 -1
- package/admin-build/_app/immutable/nodes/24.CWbEqNMB.js +0 -2
- package/admin-build/_app/immutable/nodes/25.DRkLEhKi.js +0 -2
- package/admin-build/_app/immutable/nodes/26.BRxO8AYH.js +0 -1
- package/admin-build/_app/immutable/nodes/27.BLs-nVHz.js +0 -1
- package/admin-build/_app/immutable/nodes/28.G79qkdBK.js +0 -1
- package/admin-build/_app/immutable/nodes/29.BOcI6g0N.js +0 -1
- package/admin-build/_app/immutable/nodes/30.DAIC7dKd.js +0 -1
- package/admin-build/_app/immutable/nodes/31.pl0XXjXF.js +0 -1
- package/admin-build/_app/immutable/nodes/4.DOdvVlZj.js +0 -1
- package/admin-build/_app/immutable/nodes/5.BW_zlgye.js +0 -1
- package/admin-build/_app/immutable/nodes/6.Dxy1CAI2.js +0 -1
- package/admin-build/_app/immutable/nodes/7.BG98w_o7.js +0 -1
- package/admin-build/_app/immutable/nodes/8.DoG5R2rG.js +0 -1
- package/admin-build/_app/immutable/nodes/9.Dmxf6zAC.js +0 -1
- package/src/__tests__/cloudflare-realtime.test.ts +0 -113
- package/src/lib/cloudflare-realtime.ts +0 -251
package/src/routes/push.ts
CHANGED
|
@@ -117,6 +117,38 @@ function metadataExceedsByteLimit(metadata: Record<string, unknown> | undefined)
|
|
|
117
117
|
return utf8Encoder.encode(JSON.stringify(metadata)).length > MAX_METADATA_BYTES;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
function invalidPushJsonMessage(context: string): string {
|
|
121
|
+
return `Invalid JSON body for ${context}. Send application/json with the expected fields.`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function pushAuthRequiredMessage(context: string): string {
|
|
125
|
+
return `Authentication required. Sign in before trying to ${context}.`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function pushServiceKeyRequiredMessage(context: string): string {
|
|
129
|
+
return `X-EdgeBase-Service-Key is required to ${context}.`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function pushInvalidServiceKeyMessage(context: string): string {
|
|
133
|
+
return `Invalid X-EdgeBase-Service-Key for ${context}.`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function pushNotConfiguredMessage(action: string): string {
|
|
137
|
+
return `Push notifications are not configured. Add push.fcm config with FCM credentials before trying to ${action}.`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function pushRequiredFieldMessage(field: string, context: string): string {
|
|
141
|
+
return `Missing required field '${field}' for ${context}.`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function pushMetadataTooLargeMessage(context: string): string {
|
|
145
|
+
return `metadata exceeds the ${MAX_METADATA_BYTES}-byte limit for ${context}.`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function pushRuleRejectedMessage(context: string): string {
|
|
149
|
+
return `Push send denied by push access rules for ${context}.`;
|
|
150
|
+
}
|
|
151
|
+
|
|
120
152
|
function getSharedMirrorDb(env: Env): D1Database | null {
|
|
121
153
|
const config = parseConfig(env);
|
|
122
154
|
if (!shouldRouteToD1('shared', config)) return null;
|
|
@@ -316,7 +348,7 @@ const pushRegister = createRoute({
|
|
|
316
348
|
pushRoute.openapi(pushRegister, async (c) => {
|
|
317
349
|
const auth = c.get('auth' as never) as { id: string } | null | undefined;
|
|
318
350
|
if (!auth?.id) {
|
|
319
|
-
return c.json({ code: 401, message: '
|
|
351
|
+
return c.json({ code: 401, message: pushAuthRequiredMessage('register a push token') }, 401);
|
|
320
352
|
}
|
|
321
353
|
|
|
322
354
|
let body: {
|
|
@@ -329,23 +361,23 @@ pushRoute.openapi(pushRegister, async (c) => {
|
|
|
329
361
|
try {
|
|
330
362
|
body = await c.req.json();
|
|
331
363
|
} catch {
|
|
332
|
-
return c.json({ code: 400, message: '
|
|
364
|
+
return c.json({ code: 400, message: invalidPushJsonMessage('push token registration') }, 400);
|
|
333
365
|
}
|
|
334
366
|
|
|
335
367
|
const { deviceId, token, platform } = body;
|
|
336
368
|
if (!deviceId || typeof deviceId !== 'string') {
|
|
337
|
-
return c.json({ code: 400, message: 'deviceId
|
|
369
|
+
return c.json({ code: 400, message: pushRequiredFieldMessage('deviceId', 'push token registration') }, 400);
|
|
338
370
|
}
|
|
339
371
|
if (!token || typeof token !== 'string') {
|
|
340
|
-
return c.json({ code: 400, message: 'token
|
|
372
|
+
return c.json({ code: 400, message: pushRequiredFieldMessage('token', 'push token registration') }, 400);
|
|
341
373
|
}
|
|
342
374
|
if (!platform || typeof platform !== 'string') {
|
|
343
|
-
return c.json({ code: 400, message: 'platform
|
|
375
|
+
return c.json({ code: 400, message: pushRequiredFieldMessage('platform', 'push token registration') }, 400);
|
|
344
376
|
}
|
|
345
377
|
|
|
346
378
|
// Validate metadata size (≤1KB)
|
|
347
379
|
if (metadataExceedsByteLimit(body.metadata)) {
|
|
348
|
-
return c.json({ code: 400, message:
|
|
380
|
+
return c.json({ code: 400, message: pushMetadataTooLargeMessage('push token registration') }, 400);
|
|
349
381
|
}
|
|
350
382
|
|
|
351
383
|
// All tokens are FCM Registration Tokens — store directly
|
|
@@ -394,18 +426,18 @@ const pushUnregister = createRoute({
|
|
|
394
426
|
pushRoute.openapi(pushUnregister, async (c) => {
|
|
395
427
|
const auth = c.get('auth' as never) as { id: string } | null | undefined;
|
|
396
428
|
if (!auth?.id) {
|
|
397
|
-
return c.json({ code: 401, message: '
|
|
429
|
+
return c.json({ code: 401, message: pushAuthRequiredMessage('unregister a push token') }, 401);
|
|
398
430
|
}
|
|
399
431
|
|
|
400
432
|
let body: { deviceId?: string };
|
|
401
433
|
try {
|
|
402
434
|
body = await c.req.json();
|
|
403
435
|
} catch {
|
|
404
|
-
return c.json({ code: 400, message: '
|
|
436
|
+
return c.json({ code: 400, message: invalidPushJsonMessage('push token unregistration') }, 400);
|
|
405
437
|
}
|
|
406
438
|
|
|
407
439
|
if (!body.deviceId || typeof body.deviceId !== 'string') {
|
|
408
|
-
return c.json({ code: 400, message: 'deviceId
|
|
440
|
+
return c.json({ code: 400, message: pushRequiredFieldMessage('deviceId', 'push token unregistration') }, 400);
|
|
409
441
|
}
|
|
410
442
|
|
|
411
443
|
// Get the FCM token from user's device array BEFORE removing
|
|
@@ -461,30 +493,30 @@ pushRoute.openapi(pushSend, async (c) => {
|
|
|
461
493
|
buildConstraintCtx(c.env, c.req),
|
|
462
494
|
);
|
|
463
495
|
if (skResult === 'missing') {
|
|
464
|
-
return c.json({ code: 403, message: '
|
|
496
|
+
return c.json({ code: 403, message: pushServiceKeyRequiredMessage('send a push notification') }, 403);
|
|
465
497
|
}
|
|
466
498
|
if (skResult === 'invalid') {
|
|
467
|
-
return c.json({ code: 401, message: '
|
|
499
|
+
return c.json({ code: 401, message: pushInvalidServiceKeyMessage('push notification send') }, 401);
|
|
468
500
|
}
|
|
469
501
|
|
|
470
502
|
const provider = createPushProvider(config.push, c.env);
|
|
471
503
|
if (!provider) {
|
|
472
|
-
return c.json({ code: 503, message: '
|
|
504
|
+
return c.json({ code: 503, message: pushNotConfiguredMessage('send a push notification') }, 503);
|
|
473
505
|
}
|
|
474
506
|
|
|
475
507
|
let body: { userId?: string; payload?: PushPayload };
|
|
476
508
|
try {
|
|
477
509
|
body = await c.req.json();
|
|
478
510
|
} catch {
|
|
479
|
-
return c.json({ code: 400, message: '
|
|
511
|
+
return c.json({ code: 400, message: invalidPushJsonMessage('push send') }, 400);
|
|
480
512
|
}
|
|
481
513
|
|
|
482
514
|
let { userId, payload } = body;
|
|
483
515
|
if (!userId || typeof userId !== 'string') {
|
|
484
|
-
return c.json({ code: 400, message: 'userId
|
|
516
|
+
return c.json({ code: 400, message: pushRequiredFieldMessage('userId', 'push send') }, 400);
|
|
485
517
|
}
|
|
486
518
|
if (!payload || typeof payload !== 'object') {
|
|
487
|
-
return c.json({ code: 400, message: 'payload
|
|
519
|
+
return c.json({ code: 400, message: pushRequiredFieldMessage('payload', 'push send') }, 400);
|
|
488
520
|
}
|
|
489
521
|
|
|
490
522
|
({ userId, payload } = await runBeforeSendHook(c, auth, {
|
|
@@ -495,12 +527,12 @@ pushRoute.openapi(pushSend, async (c) => {
|
|
|
495
527
|
userId = asNonEmptyString(userId);
|
|
496
528
|
payload = asPushPayload(payload);
|
|
497
529
|
if (!userId || !payload) {
|
|
498
|
-
return c.json({ code: 400, message: 'beforeSend must return a valid userId and payload' }, 400);
|
|
530
|
+
return c.json({ code: 400, message: 'push.hooks.beforeSend must return a valid userId and payload when overriding send-by-user delivery.' }, 400);
|
|
499
531
|
}
|
|
500
532
|
|
|
501
533
|
const payloadStr = JSON.stringify(payload);
|
|
502
534
|
if (payloadStr.length > 4096) {
|
|
503
|
-
return c.json({ code: 400, message: '
|
|
535
|
+
return c.json({ code: 400, message: 'Push payload exceeds the 4KB FCM limit for send-by-user delivery.' }, 400);
|
|
504
536
|
}
|
|
505
537
|
|
|
506
538
|
// Evaluate push send rule (if defined)
|
|
@@ -508,10 +540,10 @@ pushRoute.openapi(pushSend, async (c) => {
|
|
|
508
540
|
if (sendRule) {
|
|
509
541
|
try {
|
|
510
542
|
if (!sendRule(auth, { userId })) {
|
|
511
|
-
return c.json({ code: 403, message:
|
|
543
|
+
return c.json({ code: 403, message: pushRuleRejectedMessage(`user '${userId}'`) }, 403);
|
|
512
544
|
}
|
|
513
545
|
} catch {
|
|
514
|
-
return c.json({ code: 403, message:
|
|
546
|
+
return c.json({ code: 403, message: pushRuleRejectedMessage(`user '${userId}'`) }, 403);
|
|
515
547
|
}
|
|
516
548
|
}
|
|
517
549
|
|
|
@@ -556,33 +588,33 @@ pushRoute.openapi(pushSendMany, async (c) => {
|
|
|
556
588
|
buildConstraintCtx(c.env, c.req),
|
|
557
589
|
);
|
|
558
590
|
if (skResult === 'missing') {
|
|
559
|
-
return c.json({ code: 403, message: '
|
|
591
|
+
return c.json({ code: 403, message: pushServiceKeyRequiredMessage('send push notifications to multiple users') }, 403);
|
|
560
592
|
}
|
|
561
593
|
if (skResult === 'invalid') {
|
|
562
|
-
return c.json({ code: 401, message: '
|
|
594
|
+
return c.json({ code: 401, message: pushInvalidServiceKeyMessage('multi-user push send') }, 401);
|
|
563
595
|
}
|
|
564
596
|
|
|
565
597
|
const provider = createPushProvider(config.push, c.env);
|
|
566
598
|
if (!provider) {
|
|
567
|
-
return c.json({ code: 503, message: '
|
|
599
|
+
return c.json({ code: 503, message: pushNotConfiguredMessage('send push notifications to multiple users') }, 503);
|
|
568
600
|
}
|
|
569
601
|
|
|
570
602
|
let body: { userIds?: string[]; payload?: PushPayload };
|
|
571
603
|
try {
|
|
572
604
|
body = await c.req.json();
|
|
573
605
|
} catch {
|
|
574
|
-
return c.json({ code: 400, message: '
|
|
606
|
+
return c.json({ code: 400, message: invalidPushJsonMessage('multi-user push send') }, 400);
|
|
575
607
|
}
|
|
576
608
|
|
|
577
609
|
let { userIds, payload } = body;
|
|
578
610
|
if (!userIds || !Array.isArray(userIds) || userIds.length === 0) {
|
|
579
|
-
return c.json({ code: 400, message: 'userIds
|
|
611
|
+
return c.json({ code: 400, message: "Missing required field 'userIds' for multi-user push send, or the array was empty." }, 400);
|
|
580
612
|
}
|
|
581
613
|
if (userIds.length > 10000) {
|
|
582
|
-
return c.json({ code: 400, message: 'userIds
|
|
614
|
+
return c.json({ code: 400, message: 'userIds for multi-user push send must not exceed 10,000 entries.' }, 400);
|
|
583
615
|
}
|
|
584
616
|
if (!payload || typeof payload !== 'object') {
|
|
585
|
-
return c.json({ code: 400, message: 'payload
|
|
617
|
+
return c.json({ code: 400, message: pushRequiredFieldMessage('payload', 'multi-user push send') }, 400);
|
|
586
618
|
}
|
|
587
619
|
|
|
588
620
|
({ userIds, payload } = await runBeforeSendHook(c, auth, {
|
|
@@ -593,12 +625,12 @@ pushRoute.openapi(pushSendMany, async (c) => {
|
|
|
593
625
|
userIds = asStringArray(userIds);
|
|
594
626
|
payload = asPushPayload(payload);
|
|
595
627
|
if (!userIds || userIds.length === 0 || !payload) {
|
|
596
|
-
return c.json({ code: 400, message: 'beforeSend must return userIds
|
|
628
|
+
return c.json({ code: 400, message: 'push.hooks.beforeSend must return a non-empty userIds array and payload when overriding multi-user delivery.' }, 400);
|
|
597
629
|
}
|
|
598
630
|
|
|
599
631
|
const payloadStr = JSON.stringify(payload);
|
|
600
632
|
if (payloadStr.length > 4096) {
|
|
601
|
-
return c.json({ code: 400, message: '
|
|
633
|
+
return c.json({ code: 400, message: 'Push payload exceeds the 4KB FCM limit for multi-user delivery.' }, 400);
|
|
602
634
|
}
|
|
603
635
|
|
|
604
636
|
// Evaluate push send rule per userId (if defined)
|
|
@@ -613,7 +645,7 @@ pushRoute.openapi(pushSendMany, async (c) => {
|
|
|
613
645
|
}
|
|
614
646
|
});
|
|
615
647
|
if (allowedUserIds.length === 0) {
|
|
616
|
-
return c.json({ code: 403, message: '
|
|
648
|
+
return c.json({ code: 403, message: pushRuleRejectedMessage('the requested user set') }, 403);
|
|
617
649
|
}
|
|
618
650
|
}
|
|
619
651
|
|
|
@@ -727,29 +759,29 @@ pushRoute.openapi(pushSendToToken, async (c) => {
|
|
|
727
759
|
buildConstraintCtx(c.env, c.req),
|
|
728
760
|
);
|
|
729
761
|
if (skResult === 'missing') {
|
|
730
|
-
return c.json({ code: 403, message: '
|
|
762
|
+
return c.json({ code: 403, message: pushServiceKeyRequiredMessage('send a push notification to a token') }, 403);
|
|
731
763
|
}
|
|
732
764
|
if (skResult === 'invalid') {
|
|
733
|
-
return c.json({ code: 401, message: '
|
|
765
|
+
return c.json({ code: 401, message: pushInvalidServiceKeyMessage('direct token push send') }, 401);
|
|
734
766
|
}
|
|
735
767
|
|
|
736
768
|
const provider = createPushProvider(config.push, c.env);
|
|
737
769
|
if (!provider) {
|
|
738
|
-
return c.json({ code: 503, message: '
|
|
770
|
+
return c.json({ code: 503, message: pushNotConfiguredMessage('send a push notification to a token') }, 503);
|
|
739
771
|
}
|
|
740
772
|
|
|
741
773
|
let body: { token?: string; platform?: string; payload?: PushPayload };
|
|
742
774
|
try {
|
|
743
775
|
body = await c.req.json();
|
|
744
776
|
} catch {
|
|
745
|
-
return c.json({ code: 400, message: '
|
|
777
|
+
return c.json({ code: 400, message: invalidPushJsonMessage('direct token push send') }, 400);
|
|
746
778
|
}
|
|
747
779
|
|
|
748
780
|
if (!body.token || typeof body.token !== 'string') {
|
|
749
|
-
return c.json({ code: 400, message: 'token
|
|
781
|
+
return c.json({ code: 400, message: pushRequiredFieldMessage('token', 'direct token push send') }, 400);
|
|
750
782
|
}
|
|
751
783
|
if (!body.payload || typeof body.payload !== 'object') {
|
|
752
|
-
return c.json({ code: 400, message: 'payload
|
|
784
|
+
return c.json({ code: 400, message: pushRequiredFieldMessage('payload', 'direct token push send') }, 400);
|
|
753
785
|
}
|
|
754
786
|
|
|
755
787
|
const hookInput = await runBeforeSendHook(c, auth, {
|
|
@@ -762,12 +794,12 @@ pushRoute.openapi(pushSendToToken, async (c) => {
|
|
|
762
794
|
const platform = asNonEmptyString(hookInput.platform) ?? 'web';
|
|
763
795
|
const payload = asPushPayload(hookInput.payload);
|
|
764
796
|
if (!token || !payload) {
|
|
765
|
-
return c.json({ code: 400, message: 'beforeSend must return token and payload' }, 400);
|
|
797
|
+
return c.json({ code: 400, message: 'push.hooks.beforeSend must return a token and payload when overriding direct token delivery.' }, 400);
|
|
766
798
|
}
|
|
767
799
|
|
|
768
800
|
const payloadStr = JSON.stringify(payload);
|
|
769
801
|
if (payloadStr.length > 4096) {
|
|
770
|
-
return c.json({ code: 400, message: '
|
|
802
|
+
return c.json({ code: 400, message: 'Push payload exceeds the 4KB FCM limit for direct token delivery.' }, 400);
|
|
771
803
|
}
|
|
772
804
|
|
|
773
805
|
const result = await provider.send({
|
|
@@ -852,29 +884,29 @@ pushRoute.openapi(pushSendToTopic, async (c) => {
|
|
|
852
884
|
buildConstraintCtx(c.env, c.req),
|
|
853
885
|
);
|
|
854
886
|
if (skResult === 'missing') {
|
|
855
|
-
return c.json({ code: 403, message: '
|
|
887
|
+
return c.json({ code: 403, message: pushServiceKeyRequiredMessage('send a push notification to a topic') }, 403);
|
|
856
888
|
}
|
|
857
889
|
if (skResult === 'invalid') {
|
|
858
|
-
return c.json({ code: 401, message: '
|
|
890
|
+
return c.json({ code: 401, message: pushInvalidServiceKeyMessage('topic push send') }, 401);
|
|
859
891
|
}
|
|
860
892
|
|
|
861
893
|
const provider = createPushProvider(config.push, c.env);
|
|
862
894
|
if (!provider) {
|
|
863
|
-
return c.json({ code: 503, message: '
|
|
895
|
+
return c.json({ code: 503, message: pushNotConfiguredMessage('send a push notification to a topic') }, 503);
|
|
864
896
|
}
|
|
865
897
|
|
|
866
898
|
let body: { topic?: string; payload?: PushPayload };
|
|
867
899
|
try {
|
|
868
900
|
body = await c.req.json();
|
|
869
901
|
} catch {
|
|
870
|
-
return c.json({ code: 400, message: '
|
|
902
|
+
return c.json({ code: 400, message: invalidPushJsonMessage('topic push send') }, 400);
|
|
871
903
|
}
|
|
872
904
|
|
|
873
905
|
if (!body.topic || typeof body.topic !== 'string') {
|
|
874
|
-
return c.json({ code: 400, message: 'topic
|
|
906
|
+
return c.json({ code: 400, message: pushRequiredFieldMessage('topic', 'topic push send') }, 400);
|
|
875
907
|
}
|
|
876
908
|
if (!body.payload || typeof body.payload !== 'object') {
|
|
877
|
-
return c.json({ code: 400, message: 'payload
|
|
909
|
+
return c.json({ code: 400, message: pushRequiredFieldMessage('payload', 'topic push send') }, 400);
|
|
878
910
|
}
|
|
879
911
|
|
|
880
912
|
const hookInput = await runBeforeSendHook(c, auth, {
|
|
@@ -885,7 +917,7 @@ pushRoute.openapi(pushSendToTopic, async (c) => {
|
|
|
885
917
|
const topic = asNonEmptyString(hookInput.topic);
|
|
886
918
|
const payload = asPushPayload(hookInput.payload);
|
|
887
919
|
if (!topic || !payload) {
|
|
888
|
-
return c.json({ code: 400, message: 'beforeSend must return topic and payload' }, 400);
|
|
920
|
+
return c.json({ code: 400, message: 'push.hooks.beforeSend must return a topic and payload when overriding topic delivery.' }, 400);
|
|
889
921
|
}
|
|
890
922
|
const topicInput: PushSendInput = { kind: 'topic', topic, payload: payload as Record<string, unknown> };
|
|
891
923
|
const result = await provider.sendToTopic(topic, payload);
|
|
@@ -943,26 +975,26 @@ pushRoute.openapi(pushBroadcast, async (c) => {
|
|
|
943
975
|
buildConstraintCtx(c.env, c.req),
|
|
944
976
|
);
|
|
945
977
|
if (skResult === 'missing') {
|
|
946
|
-
return c.json({ code: 403, message: '
|
|
978
|
+
return c.json({ code: 403, message: pushServiceKeyRequiredMessage('broadcast a push notification') }, 403);
|
|
947
979
|
}
|
|
948
980
|
if (skResult === 'invalid') {
|
|
949
|
-
return c.json({ code: 401, message: '
|
|
981
|
+
return c.json({ code: 401, message: pushInvalidServiceKeyMessage('push broadcast') }, 401);
|
|
950
982
|
}
|
|
951
983
|
|
|
952
984
|
const provider = createPushProvider(config.push, c.env);
|
|
953
985
|
if (!provider) {
|
|
954
|
-
return c.json({ code: 503, message: '
|
|
986
|
+
return c.json({ code: 503, message: pushNotConfiguredMessage('broadcast a push notification') }, 503);
|
|
955
987
|
}
|
|
956
988
|
|
|
957
989
|
let body: { payload?: PushPayload };
|
|
958
990
|
try {
|
|
959
991
|
body = await c.req.json();
|
|
960
992
|
} catch {
|
|
961
|
-
return c.json({ code: 400, message: '
|
|
993
|
+
return c.json({ code: 400, message: invalidPushJsonMessage('push broadcast') }, 400);
|
|
962
994
|
}
|
|
963
995
|
|
|
964
996
|
if (!body.payload || typeof body.payload !== 'object') {
|
|
965
|
-
return c.json({ code: 400, message: 'payload
|
|
997
|
+
return c.json({ code: 400, message: pushRequiredFieldMessage('payload', 'push broadcast') }, 400);
|
|
966
998
|
}
|
|
967
999
|
|
|
968
1000
|
const hookInput = await runBeforeSendHook(c, auth, {
|
|
@@ -971,7 +1003,7 @@ pushRoute.openapi(pushBroadcast, async (c) => {
|
|
|
971
1003
|
});
|
|
972
1004
|
const payload = asPushPayload(hookInput.payload);
|
|
973
1005
|
if (!payload) {
|
|
974
|
-
return c.json({ code: 400, message: 'beforeSend must return payload' }, 400);
|
|
1006
|
+
return c.json({ code: 400, message: 'push.hooks.beforeSend must return a payload object when overriding broadcast delivery.' }, 400);
|
|
975
1007
|
}
|
|
976
1008
|
const broadcastInput: PushSendInput = { kind: 'broadcast', payload: payload as Record<string, unknown> };
|
|
977
1009
|
const result = await provider.broadcast(payload);
|
|
@@ -1018,24 +1050,24 @@ const pushTopicSubscribe = createRoute({
|
|
|
1018
1050
|
pushRoute.openapi(pushTopicSubscribe, async (c) => {
|
|
1019
1051
|
const auth = c.get('auth' as never) as { id: string } | null | undefined;
|
|
1020
1052
|
if (!auth?.id) {
|
|
1021
|
-
return c.json({ code: 401, message: '
|
|
1053
|
+
return c.json({ code: 401, message: pushAuthRequiredMessage('subscribe push tokens to a topic') }, 401);
|
|
1022
1054
|
}
|
|
1023
1055
|
|
|
1024
1056
|
const config = parseConfig(c.env);
|
|
1025
1057
|
const provider = createPushProvider(config.push, c.env);
|
|
1026
1058
|
if (!provider) {
|
|
1027
|
-
return c.json({ code: 503, message: '
|
|
1059
|
+
return c.json({ code: 503, message: pushNotConfiguredMessage('subscribe push tokens to a topic') }, 503);
|
|
1028
1060
|
}
|
|
1029
1061
|
|
|
1030
1062
|
let body: { topic?: string };
|
|
1031
1063
|
try {
|
|
1032
1064
|
body = await c.req.json();
|
|
1033
1065
|
} catch {
|
|
1034
|
-
return c.json({ code: 400, message: '
|
|
1066
|
+
return c.json({ code: 400, message: invalidPushJsonMessage('push topic subscription') }, 400);
|
|
1035
1067
|
}
|
|
1036
1068
|
|
|
1037
1069
|
if (!body.topic || typeof body.topic !== 'string') {
|
|
1038
|
-
return c.json({ code: 400, message: 'topic
|
|
1070
|
+
return c.json({ code: 400, message: pushRequiredFieldMessage('topic', 'push topic subscription') }, 400);
|
|
1039
1071
|
}
|
|
1040
1072
|
|
|
1041
1073
|
// Get user's devices and subscribe all tokens to the topic
|
|
@@ -1080,24 +1112,24 @@ const pushTopicUnsubscribe = createRoute({
|
|
|
1080
1112
|
pushRoute.openapi(pushTopicUnsubscribe, async (c) => {
|
|
1081
1113
|
const auth = c.get('auth' as never) as { id: string } | null | undefined;
|
|
1082
1114
|
if (!auth?.id) {
|
|
1083
|
-
return c.json({ code: 401, message: '
|
|
1115
|
+
return c.json({ code: 401, message: pushAuthRequiredMessage('unsubscribe push tokens from a topic') }, 401);
|
|
1084
1116
|
}
|
|
1085
1117
|
|
|
1086
1118
|
const config = parseConfig(c.env);
|
|
1087
1119
|
const provider = createPushProvider(config.push, c.env);
|
|
1088
1120
|
if (!provider) {
|
|
1089
|
-
return c.json({ code: 503, message: '
|
|
1121
|
+
return c.json({ code: 503, message: pushNotConfiguredMessage('unsubscribe push tokens from a topic') }, 503);
|
|
1090
1122
|
}
|
|
1091
1123
|
|
|
1092
1124
|
let body: { topic?: string };
|
|
1093
1125
|
try {
|
|
1094
1126
|
body = await c.req.json();
|
|
1095
1127
|
} catch {
|
|
1096
|
-
return c.json({ code: 400, message: '
|
|
1128
|
+
return c.json({ code: 400, message: invalidPushJsonMessage('push topic unsubscription') }, 400);
|
|
1097
1129
|
}
|
|
1098
1130
|
|
|
1099
1131
|
if (!body.topic || typeof body.topic !== 'string') {
|
|
1100
|
-
return c.json({ code: 400, message: 'topic
|
|
1132
|
+
return c.json({ code: 400, message: pushRequiredFieldMessage('topic', 'push topic unsubscription') }, 400);
|
|
1101
1133
|
}
|
|
1102
1134
|
|
|
1103
1135
|
const devices = await getDevicesForUser(await getPushTokenStore(c), auth.id);
|
|
@@ -1150,15 +1182,15 @@ pushRoute.openapi(pushLogsRoute, async (c) => {
|
|
|
1150
1182
|
buildConstraintCtx(c.env, c.req),
|
|
1151
1183
|
);
|
|
1152
1184
|
if (skResult === 'missing') {
|
|
1153
|
-
return c.json({ code: 403, message: '
|
|
1185
|
+
return c.json({ code: 403, message: pushServiceKeyRequiredMessage('read push notification logs') }, 403);
|
|
1154
1186
|
}
|
|
1155
1187
|
if (skResult === 'invalid') {
|
|
1156
|
-
return c.json({ code: 401, message: '
|
|
1188
|
+
return c.json({ code: 401, message: pushInvalidServiceKeyMessage('push log reads') }, 401);
|
|
1157
1189
|
}
|
|
1158
1190
|
|
|
1159
1191
|
const userId = c.req.query('userId');
|
|
1160
1192
|
if (!userId) {
|
|
1161
|
-
return c.json({ code: 400, message:
|
|
1193
|
+
return c.json({ code: 400, message: "Missing required query parameter 'userId' for push log reads." }, 400);
|
|
1162
1194
|
}
|
|
1163
1195
|
|
|
1164
1196
|
const limitStr = c.req.query('limit');
|
|
@@ -1201,15 +1233,15 @@ pushRoute.openapi(pushTokensRoute, async (c) => {
|
|
|
1201
1233
|
buildConstraintCtx(c.env, c.req),
|
|
1202
1234
|
);
|
|
1203
1235
|
if (skResult === 'missing') {
|
|
1204
|
-
return c.json({ code: 403, message: '
|
|
1236
|
+
return c.json({ code: 403, message: pushServiceKeyRequiredMessage('read registered push tokens') }, 403);
|
|
1205
1237
|
}
|
|
1206
1238
|
if (skResult === 'invalid') {
|
|
1207
|
-
return c.json({ code: 401, message: '
|
|
1239
|
+
return c.json({ code: 401, message: pushInvalidServiceKeyMessage('push token reads') }, 401);
|
|
1208
1240
|
}
|
|
1209
1241
|
|
|
1210
1242
|
const userId = c.req.query('userId');
|
|
1211
1243
|
if (!userId) {
|
|
1212
|
-
return c.json({ code: 400, message:
|
|
1244
|
+
return c.json({ code: 400, message: "Missing required query parameter 'userId' for push token reads." }, 400);
|
|
1213
1245
|
}
|
|
1214
1246
|
|
|
1215
1247
|
const devices = await getDevicesForUser(await getPushTokenStore(c), userId);
|
|
@@ -1259,26 +1291,31 @@ pushRoute.openapi(putPushTokens, async (c) => {
|
|
|
1259
1291
|
buildConstraintCtx(c.env, c.req),
|
|
1260
1292
|
);
|
|
1261
1293
|
if (skResult === 'missing') {
|
|
1262
|
-
return c.json({ code: 403, message: '
|
|
1294
|
+
return c.json({ code: 403, message: pushServiceKeyRequiredMessage('upsert a push token') }, 403);
|
|
1263
1295
|
}
|
|
1264
1296
|
if (skResult === 'invalid') {
|
|
1265
|
-
return c.json({ code: 401, message: '
|
|
1297
|
+
return c.json({ code: 401, message: pushInvalidServiceKeyMessage('push token upsert') }, 401);
|
|
1266
1298
|
}
|
|
1267
1299
|
|
|
1268
|
-
|
|
1300
|
+
let body: {
|
|
1269
1301
|
userId?: string;
|
|
1270
1302
|
deviceId?: string;
|
|
1271
1303
|
token?: string;
|
|
1272
1304
|
platform?: string;
|
|
1273
1305
|
deviceInfo?: { name?: string; osVersion?: string; appVersion?: string; locale?: string };
|
|
1274
1306
|
metadata?: Record<string, unknown>;
|
|
1275
|
-
}
|
|
1307
|
+
};
|
|
1308
|
+
try {
|
|
1309
|
+
body = await c.req.json();
|
|
1310
|
+
} catch {
|
|
1311
|
+
return c.json({ code: 400, message: invalidPushJsonMessage('push token upsert') }, 400);
|
|
1312
|
+
}
|
|
1276
1313
|
if (!body.userId || !body.deviceId || !body.token || !body.platform) {
|
|
1277
|
-
return c.json({ code: 400, message: 'userId, deviceId, token, and platform
|
|
1314
|
+
return c.json({ code: 400, message: "Missing required fields for push token upsert. Expected 'userId', 'deviceId', 'token', and 'platform'." }, 400);
|
|
1278
1315
|
}
|
|
1279
1316
|
|
|
1280
1317
|
if (metadataExceedsByteLimit(body.metadata)) {
|
|
1281
|
-
return c.json({ code: 400, message:
|
|
1318
|
+
return c.json({ code: 400, message: pushMetadataTooLargeMessage('push token upsert') }, 400);
|
|
1282
1319
|
}
|
|
1283
1320
|
|
|
1284
1321
|
await registerToken(
|
|
@@ -1344,29 +1381,34 @@ pushRoute.openapi(patchPushTokens, async (c) => {
|
|
|
1344
1381
|
buildConstraintCtx(c.env, c.req),
|
|
1345
1382
|
);
|
|
1346
1383
|
if (skResult === 'missing') {
|
|
1347
|
-
return c.json({ code: 403, message: '
|
|
1384
|
+
return c.json({ code: 403, message: pushServiceKeyRequiredMessage('update push token metadata') }, 403);
|
|
1348
1385
|
}
|
|
1349
1386
|
if (skResult === 'invalid') {
|
|
1350
|
-
return c.json({ code: 401, message: '
|
|
1387
|
+
return c.json({ code: 401, message: pushInvalidServiceKeyMessage('push token metadata updates') }, 401);
|
|
1351
1388
|
}
|
|
1352
1389
|
|
|
1353
|
-
|
|
1390
|
+
let body: { userId?: string; deviceId?: string; metadata?: Record<string, unknown> };
|
|
1391
|
+
try {
|
|
1392
|
+
body = await c.req.json();
|
|
1393
|
+
} catch {
|
|
1394
|
+
return c.json({ code: 400, message: invalidPushJsonMessage('push token metadata update') }, 400);
|
|
1395
|
+
}
|
|
1354
1396
|
if (!body.userId || !body.deviceId) {
|
|
1355
|
-
return c.json({ code: 400, message: 'userId and deviceId
|
|
1397
|
+
return c.json({ code: 400, message: "Missing required fields for push token metadata update. Expected 'userId' and 'deviceId'." }, 400);
|
|
1356
1398
|
}
|
|
1357
1399
|
if (!body.metadata) {
|
|
1358
|
-
return c.json({ code: 400, message: 'metadata
|
|
1400
|
+
return c.json({ code: 400, message: pushRequiredFieldMessage('metadata', 'push token metadata update') }, 400);
|
|
1359
1401
|
}
|
|
1360
1402
|
|
|
1361
1403
|
if (metadataExceedsByteLimit(body.metadata)) {
|
|
1362
|
-
return c.json({ code: 400, message:
|
|
1404
|
+
return c.json({ code: 400, message: pushMetadataTooLargeMessage('push token metadata update') }, 400);
|
|
1363
1405
|
}
|
|
1364
1406
|
|
|
1365
1407
|
const pushStore = await getPushTokenStore(c);
|
|
1366
1408
|
const devices = await getDevicesForUser(pushStore, body.userId);
|
|
1367
1409
|
const device = devices.find(d => d.deviceId === body.deviceId);
|
|
1368
1410
|
if (!device) {
|
|
1369
|
-
return c.json({ code: 404, message: '
|
|
1411
|
+
return c.json({ code: 404, message: `Push device '${body.deviceId}' for user '${body.userId}' was not found.` }, 404);
|
|
1370
1412
|
}
|
|
1371
1413
|
|
|
1372
1414
|
device.metadata = body.metadata;
|