node-webpush πŸ””
    Preparing search index...

    node-webpush πŸ””

    Node WebPush (Node.js + TypeScript)

    npm version License CI Codecov

    A dependency-free Web Push implementation for Node.js (TypeScript-first).

    This library focuses on standards-compliant payload encryption + VAPID authentication, and produces request details that can be sent with Node’s built-in fetch().

    • RFC 8188: HTTP Encrypted Content Encoding (record-based framing, per-record nonce derivation).
    • RFC 8291: Web Push message encryption (ECDH + auth secret, β€œWebPush: info” key schedule, aes128gcm).
    • RFC 8292: VAPID (JWT ES256) authentication headers.
    • Supports both encodings:
      • aes128gcm (recommended): modern Web Push encoding (RFC 8291 + RFC 8188).
      • aesgcm (legacy): kept for interoperability with older endpoints.
    • Pure Node.js crypto (no external libs).
    • RFC8188 record framing:
      • delimiter rules (0x01 for non-last, 0x02 for last)
      • optional final-record padding
      • nonce = baseNonce XOR SEQ per record
    • Native ES256 JWT signing using Node’s crypto (JWK-based key objects).
    • Key generation (VAPID.GenerateKeys()).
    • Full validation helpers (VAPID.Validate.*).
    • generateRequest() produces { endpoint, init } for fetch(endpoint, init).
    • Sets required headers:
      • TTL, Urgency, optional Topic
      • Content-Encoding, Content-Type, Content-Length
      • Authorization (VAPID or GCM/FCM key when applicable)
    • Detects legacy GCM endpoints:
      • Uses Authorization: key=<apiKey> (VAPID not supported on legacy GCM).
    • Supports FCM endpoints:
      • Uses VAPID by default when configured.
      • Can fall back to Authorization: key=<apiKey> if VAPID is disabled and a key is provided.

    npm install node-webpush
    

    TypeScript is supported out of the box (the package emits .d.ts).


    import {WebPush} from "node-webpush";

    const webpush = new WebPush({
    vapid: {
    subject: "mailto:admin@example.com",
    publicKey: process.env.VAPID_PUBLIC_KEY!,
    privateKey: process.env.VAPID_PRIVATE_KEY!,
    },
    // Optional: used for legacy GCM/FCM key-based auth fallback
    gcm: {apiKey: process.env.GCM_API_KEY ?? null},
    });
    const subscription = {
    endpoint: "https://push-service.example/...",
    keys: {
    p256dh: "<base64url>",
    auth: "<base64url>",
    },
    };

    const res = await webpush.notify(subscription, "Hello from WebPush!", {
    TTL: 60,
    });

    console.log("Status:", res.status);

    import {VAPID} from "node-webpush";

    const keys = VAPID.GenerateKeys();
    console.log(keys.publicKey);
    console.log(keys.privateKey);

    You typically store these as environment variables:

    • VAPID_PUBLIC_KEY
    • VAPID_PRIVATE_KEY

    type WebPushConfig = {
    vapid: {
    publicKey: string;
    privateKey: string;
    subject: string | URL; // must be https: or mailto:
    };
    gcm?: { apiKey?: string | null };
    };

    Constructing WebPush validates:

    • VAPID subject format (https: or mailto:)
    • VAPID key sizes and base64url encoding
    • GCM/FCM key if provided (must be non-empty)

    Returns the request parameters to call fetch() yourself.

    const {endpoint, init} = webpush.generateRequest(subscription, "payload", {
    TTL: 60,
    });

    const res = await fetch(endpoint, init);

    This is useful if you want to:

    • inspect headers
    • plug into your own HTTP stack
    • retry logic / circuit breakers
    • log request metadata

    Sends the request using fetch().

    const res = await webpush.notify(subscription, "hello");
    

    default it return the response even if not successful. It can also throw an error if the push service returns a non-2xx response. This can be enabled by:

    import {WebPushError} from "./webpush";

    try {
    const res = await webpush.notify(subscription, "hello", {
    throwOnInvalidResponse: true //Add this to the options
    });
    } catch (error: WebPushError){
    console.error(error);
    const responseObject = error.response; //<<-- The resulting response object can still be accessed
    }

    Throws WebPushError when the push service returns a non-2xx response.

    This also contains the response but can be handled in the try-catch logic


    type GenerateRequestOptions = {
    headers?: Record<string, string>;

    TTL?: number; // seconds
    urgency?: "very-low" | "low" | "normal" | "high";
    topic?: string; // base64url <= 32 chars

    contentEncoding?: "aes128gcm" | "aesgcm";

    // RFC8188 knobs (primarily for advanced use/testing)
    rs?: number; // default 4096, must be >= 18
    allowMultipleRecords?: boolean; // default false (Web Push wants single record)
    finalRecordPadding?: number; // default 0

    // Override authentication behavior:
    vapidDetails?: WebPushConfig["vapid"] | null;
    gcmAPIKey?: string | null;
    };
    • aes128gcm is recommended for Web Push.
    • For Web Push interoperability, leave allowMultipleRecords at false (default).
    • topic must use URL-safe base64 characters and be <= 32 chars.

    This library follows typical push-service rules:

    1. Legacy GCM endpoint (https://android.googleapis.com/gcm/send...)
    • Uses Authorization: key=<gcmAPIKey>
    • VAPID is ignored (not supported)
    1. Everything else
    • If vapidDetails is present: uses VAPID
    • Else if endpoint is FCM and a key is present: uses Authorization: key=<gcmAPIKey>

    If you want to disable VAPID for a call:

    await webpush.notify(subscription, "hello", {
    vapidDetails: null,
    gcmAPIKey: process.env.GCM_API_KEY!,
    });

    import {WebPush} from "node-webpush";

    const webpush = new WebPush({
    vapid: {
    subject: "https://example.com/contact",
    publicKey: process.env.VAPID_PUBLIC_KEY!,
    privateKey: process.env.VAPID_PRIVATE_KEY!,
    },
    });

    const {endpoint, init} = webpush.generateRequest(subscription, "ping", {
    TTL: 120,
    urgency: "high",
    });

    console.log(init.headers); // inspect headers

    const res = await fetch(endpoint, init);
    console.log(res.status);

    import {WebPush, WebPushError} from "node-webpush";

    try {
    await webpush.notify(subscription, "hello");
    } catch (e) {
    if (e instanceof WebPushError) {
    console.error("Push service rejected request:", e.response.status);
    console.error("Response body:", await e.response.text());
    } else {
    console.error("Unexpected error:", e);
    }
    }

    • Node.js with global fetch (Node 18+ recommended).
    • TypeScript target: ES2020 works.

    • Keep your VAPID private key secret.
    • Always validate subscriptions server-side before storing or using them.
    • Avoid sending sensitive data in payloads; push payloads can be stored/forwarded by push services.

    Apache 2.0 See LICENSE