Phase 7
This commit is contained in:
189 files changed
+9873
-655
No files matched your search
@@ -0,0 +1,62 @@
|
||||
import webpush from "web-push";
|
||||
import type { PushProvider, PushSendResult, SendPushParams } from "./push-provider";
|
||||
|
||||
/**
|
||||
* Standards-based Web Push (RFC 8030 + VAPID) via the `web-push` package —
|
||||
* works against any browser's push service (Chrome/FCM, Firefox, Safari)
|
||||
* without a vendor-specific SDK. Nothing here is Brevo-specific; push and
|
||||
* email are unrelated delivery channels with unrelated providers.
|
||||
*/
|
||||
export interface WebPushProviderConfig {
|
||||
publicKey: string;
|
||||
privateKey: string;
|
||||
subject: string;
|
||||
}
|
||||
|
||||
export function loadWebPushProviderConfigFromEnv(): WebPushProviderConfig {
|
||||
const publicKey = process.env.VAPID_PUBLIC_KEY;
|
||||
const privateKey = process.env.VAPID_PRIVATE_KEY;
|
||||
const subject = process.env.VAPID_SUBJECT;
|
||||
if (!publicKey || !privateKey || !subject) {
|
||||
throw new Error("Web Push is not configured — VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, and VAPID_SUBJECT must all be set. See .env.example.");
|
||||
}
|
||||
return { publicKey, privateKey, subject };
|
||||
}
|
||||
|
||||
interface WebPushError {
|
||||
statusCode?: number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export class WebPushProvider implements PushProvider {
|
||||
readonly name = "web-push";
|
||||
|
||||
constructor(config: WebPushProviderConfig) {
|
||||
webpush.setVapidDetails(config.subject, config.publicKey, config.privateKey);
|
||||
}
|
||||
|
||||
async sendPush(params: SendPushParams): Promise<PushSendResult> {
|
||||
try {
|
||||
await webpush.sendNotification(
|
||||
{
|
||||
endpoint: params.subscription.endpoint,
|
||||
keys: { p256dh: params.subscription.p256dhKey, auth: params.subscription.authKey },
|
||||
},
|
||||
JSON.stringify({ title: params.title, body: params.body, url: params.deepLinkPath }),
|
||||
);
|
||||
return { status: "sent" };
|
||||
} catch (error) {
|
||||
const webPushError = error as WebPushError;
|
||||
// 404/410: the push service itself says this subscription no longer
|
||||
// exists (browser data cleared, permission revoked, uninstalled) —
|
||||
// never retryable, and the caller should revoke the row.
|
||||
if (webPushError.statusCode === 404 || webPushError.statusCode === 410) {
|
||||
return { status: "failed", retryable: false, subscriptionInvalid: true, reason: "Subscription is no longer valid." };
|
||||
}
|
||||
if (typeof webPushError.statusCode === "number" && webPushError.statusCode >= 500) {
|
||||
return { status: "failed", retryable: true, reason: webPushError.message ?? "Push service error." };
|
||||
}
|
||||
return { status: "failed", retryable: false, reason: webPushError.message ?? "Unknown push error." };
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user