fix(push): stop web push from posting to arbitrary endpoints
This commit is contained in:
parent
1afaea7214
commit
043ef579a8
7 changed files with 418 additions and 34 deletions
74
server/supabase/functions/send-push/dispatch-response.ts
Normal file
74
server/supabase/functions/send-push/dispatch-response.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
// server/supabase/functions/send-push/dispatch-response.ts
|
||||
// Pure presenter: turns a processed push dispatch into the HTTP body/status.
|
||||
//
|
||||
// Provider transport codes (webpush_send_timeout, fcm_send_failed, …) and
|
||||
// their distinct HTTP statuses describe what happened on the function's own
|
||||
// network. Echoing them to an end-user caller turns the dispatch endpoint into
|
||||
// a reachability oracle, so user callers only ever see one generic failure.
|
||||
// Trusted system callers (service role, drain worker) keep the detailed code
|
||||
// for operations.
|
||||
|
||||
export type DispatchAudience = 'user' | 'system'
|
||||
|
||||
export interface DispatchOutcome {
|
||||
status: 'pending' | 'processing' | 'succeeded' | 'partial' | 'failed'
|
||||
complete: boolean
|
||||
attempted: number
|
||||
delivered: number
|
||||
stale: number
|
||||
retryableFailed: number
|
||||
permanentFailed: number
|
||||
nextRetryAt: string | null
|
||||
transientError: string | null
|
||||
transientStatus: number | null
|
||||
}
|
||||
|
||||
export interface DispatchResponse {
|
||||
body: Record<string, unknown>
|
||||
status: number
|
||||
}
|
||||
|
||||
export const DELIVERY_FAILURE_CODE = 'push_delivery_failed'
|
||||
const DELIVERY_FAILURE_STATUS = 502
|
||||
|
||||
export function presentDispatchOutcome(
|
||||
audience: DispatchAudience,
|
||||
eventType: string,
|
||||
resourceId: string,
|
||||
outcome: DispatchOutcome,
|
||||
): DispatchResponse {
|
||||
const body: Record<string, unknown> = {
|
||||
event_type: eventType,
|
||||
resource_id: resourceId,
|
||||
duplicate: false,
|
||||
status: outcome.status,
|
||||
complete: outcome.complete,
|
||||
attempted: outcome.attempted,
|
||||
sent: outcome.delivered,
|
||||
stale_removed: outcome.stale,
|
||||
retryable_failed: outcome.retryableFailed,
|
||||
permanent_failed: outcome.permanentFailed,
|
||||
next_retry_at: outcome.nextRetryAt,
|
||||
}
|
||||
const failed = outcome.status === 'partial' || outcome.status === 'failed'
|
||||
|
||||
if (outcome.transientError) {
|
||||
if (audience === 'user') {
|
||||
return {
|
||||
body: { error: DELIVERY_FAILURE_CODE, ...body },
|
||||
status: DELIVERY_FAILURE_STATUS,
|
||||
}
|
||||
}
|
||||
return {
|
||||
body: { error: outcome.transientError, ...body },
|
||||
status: outcome.transientStatus ?? DELIVERY_FAILURE_STATUS,
|
||||
}
|
||||
}
|
||||
if (failed) {
|
||||
return {
|
||||
body: { error: DELIVERY_FAILURE_CODE, ...body },
|
||||
status: DELIVERY_FAILURE_STATUS,
|
||||
}
|
||||
}
|
||||
return { body, status: outcome.complete ? 200 : 202 }
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue