export const WEB_PUSH_LIMITS = Object.freeze({ decodedHintBytes: 3 * 1024, hintFutureSkewMs: 5 * 60 * 1_000, hintMaxLifetimeMs: 24 * 60 * 60 * 1_000, handlerDeadlineMs: 10_000, fenceOperationDeadlineMs: 2_000, nativeOperationDeadlineMs: 30_000, backendOperationDeadlineMs: 15_000, notificationCleanupCount: 64, notificationCleanupDeadlineMs: 2_000, clientHandoffCount: 32, } as const); export const WEB_PUSH_PROTOCOLS = Object.freeze({ control: "PUSH_CONTROL_V1", hint: "WEB_PUSH_HINT_V1", click: "NOTIFICATION_CLICK_DATA_V1", registration: "WEB_PUSH_REGISTRATION_V1", reconciliation: "WEB_PUSH_RECONCILIATION_V1", revoke: "WEB_PUSH_REVOKE_V1", clickHandoff: "WEB_PUSH_CLICK_HANDOFF_V1", reconcileRequired: "WEB_PUSH_RECONCILE_REQUIRED_V1", } as const); /** * Product IDs stay opaque to the mechanism. The selected composition must * provide a closed registry that resolves these syntactically validated IDs. */ export type NotificationTypeId = string; export type NotificationRouteIntentId = string; export type PushAuthoritySnapshot = Readonly<{ fenceGeneration: string; sessionBindingEpoch: string; releaseEpoch: string; }>; export type PushControlAssociationV1 = | Readonly<{ state: "UNASSOCIATED" }> | Readonly<{ state: "ACTIVE" | "REVOKED"; associationEpoch: string; }>; /** * One origin-scoped durable authority record shared by the window and worker. * It intentionally contains no account identifier or native subscription * material. `updatedAt` is diagnostic metadata, never an ordering authority. */ export type PushControlV1 = Readonly<{ protocol: typeof WEB_PUSH_PROTOCOLS.control; fenceGeneration: string; sessionBindingEpoch: string; releaseEpoch: string; updatedAt: string; association: PushControlAssociationV1; }>; export type WebPushHintV1 = Readonly<{ protocol: typeof WEB_PUSH_PROTOCOLS.hint; notificationType: NotificationTypeId; notificationId: string; associationEpoch: string; releaseEpoch: string; issuedAt: string; expiresAt: string; routeIntent: NotificationRouteIntentId; }>; export type NotificationClickDataV1 = Readonly<{ protocol: typeof WEB_PUSH_PROTOCOLS.click; notificationId: string; routeIntent: NotificationRouteIntentId; associationEpoch: string; releaseEpoch: string; expiresAt: string; }>; export type WebPushReadinessState = | "PUSH_READY" | "PUSH_PERMISSION_REQUIRED" | "PUSH_DENIED" | "PUSH_UNSUPPORTED" | "PUSH_UNAVAILABLE"; export type WebPushUnavailableReason = | "ABORTED" | "BACKEND_ASSOCIATION_MISSING" | "BACKEND_REVOKE_AMBIGUOUS" | "BUSY" | "CLOSED" | "LOCAL_FENCE_UNSAFE" | "NATIVE_UNSUBSCRIBE_AMBIGUOUS" | "NATIVE_SUBSCRIPTION_MISSING" | "PERMISSION_DISMISSED" | "REGISTRATION_NOT_ACTIVE" | "REVOKED" | "SESSION_AUTHORITY_CHANGED" | "SUBSCRIPTION_KEY_MISMATCH" | "WORKER_UNAVAILABLE"; export type WebPushReadiness = Readonly<{ state: WebPushReadinessState; reason?: WebPushUnavailableReason; }>; export type WebPushOperation = | "CONTROL_OPEN" | "CONTROL_READ" | "CONTROL_PREPARE" | "CONTROL_ACTIVATE" | "CONTROL_REVOKE" | "CONTROL_PURGE" | "PERMISSION_REQUEST" | "SUBSCRIPTION_INSPECT" | "SUBSCRIPTION_CREATE" | "SUBSCRIPTION_RECONCILE" | "SUBSCRIPTION_REVOKE" | "PUSH_DECODE" | "PUSH_HANDLE" | "NOTIFICATION_SHOW" | "NOTIFICATION_CLICK" | "NOTIFICATION_CLEANUP"; export type WebPushFailureCode = | "ABORTED" | "ASSOCIATION_MISMATCH" | "BLOCKED" | "CONTRACT_REJECTED" | "CONTROL_CORRUPT" | "DEADLINE_EXCEEDED" | "DECLARATIVE_PUSH_FORBIDDEN" | "EXPIRED" | "INVALID_INPUT" | "LIMIT_EXCEEDED" | "NATIVE_FAILURE" | "PERMISSION_DENIED" | "PROVIDER_UNAVAILABLE" | "RELEASE_MISMATCH" | "STALE_AUTHORITY" | "STALE_REVISION" | "TOMBSTONE_CONFLICT" | "UNSUPPORTED"; export type WebPushFailure = Readonly<{ code: WebPushFailureCode; operation: WebPushOperation; retryable: boolean; }>; export type WebPushResult = | Readonly<{ ok: true; value: Value }> | Readonly<{ ok: false; error: WebPushFailure }>; export type WebPushObservationEvent = | "web_push_permission_finished" | "web_push_registration_finished" | "web_push_subscription_rotated" | "web_push_hint_processed" | "web_push_notification_finished" | "web_push_click_dispatched" | "web_push_association_revoked"; /** * WP-06. Bounded fan-out is a deliberate policy, but reporting a truncated pass * as plain success hid the fact that only part of the set was handled. */ export type WebPushCountBucket = | "0" | "1_8" | "9_32" | "33_64" | "GT_64"; export function webPushCountBucket(count: number): WebPushCountBucket { if (!Number.isFinite(count) || count <= 0) return "0"; if (count <= 8) return "1_8"; if (count <= 32) return "9_32"; if (count <= 64) return "33_64"; return "GT_64"; } /** * WP-07. Certainty of a user-visible native effect. It is evidence only and * never authorizes a retry. */ export type WebPushNativeEffectCertainty = | "CONFIRMED" | "NOT_APPLIED" | "MAYBE_APPLIED"; export type WebPushObservation = Readonly<{ event: WebPushObservationEvent; outcome: "SUCCEEDED" | "FAILED" | "DEGRADED"; reason?: WebPushFailureCode | WebPushUnavailableReason; countBucket?: WebPushCountBucket; truncated?: boolean; nativeEffect?: WebPushNativeEffectCertainty; }>; export interface WebPushObserver { record(observation: WebPushObservation): void; } export function webPushSuccess( value: Value, ): WebPushResult { return Object.freeze({ ok: true, value }); } export function webPushFailure( code: WebPushFailureCode, operation: WebPushOperation, retryable = false, ): WebPushResult { return Object.freeze({ ok: false, error: Object.freeze({ code, operation, retryable }), }); } export function samePushAuthority( left: PushAuthoritySnapshot, right: PushAuthoritySnapshot, ): boolean { return ( left.fenceGeneration === right.fenceGeneration && left.sessionBindingEpoch === right.sessionBindingEpoch && left.releaseEpoch === right.releaseEpoch ); }