node-webpush 1.1.1 → 1.1.2

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/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # Node WebPush (Node.js + TypeScript)
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/node-webpush?logo=npm)](https://www.npmjs.com/package/node-webpush)
4
- [![License](https://img.shields.io/npm/l/node-webpush)](https://github.com/alexanderslaa/node-webpush/blob/main/LICENSE)
5
- [![CI](https://github.com/alexanderslaa/node-webpush/actions/workflows/test.yml/badge.svg)](https://github.com/alexanderslaa/node-webpush/actions)
6
- [![Codecov](https://img.shields.io/codecov/c/github/alexanderslaa/node-webpush)](https://codecov.io/gh/alexanderslaa/node-webpush)
4
+ [![License](https://img.shields.io/npm/l/node-webpush)](https://github.com/sourceregistry/node-webpush/blob/main/LICENSE)
5
+ [![CI](https://github.com/sourceregistry/node-webpush/actions/workflows/test.yml/badge.svg)](https://github.com/sourceregistry/node-webpush/actions)
6
+ [![Codecov](https://img.shields.io/codecov/c/github/sourceregistry/node-webpush)](https://codecov.io/gh/sourceregistry/node-webpush)
7
7
 
8
8
  A dependency-free Web Push implementation for Node.js (TypeScript-first).
9
9
 
@@ -17,9 +17,8 @@ that can be sent with Node’s built-in `fetch()`.
17
17
  - **RFC 8188**: HTTP Encrypted Content Encoding (record-based framing, per-record nonce derivation).
18
18
  - **RFC 8291**: Web Push message encryption (ECDH + auth secret, “WebPush: info” key schedule, `aes128gcm`).
19
19
  - **RFC 8292**: VAPID (JWT ES256) authentication headers.
20
- - Supports both encodings:
21
- - **`aes128gcm` (recommended)**: modern Web Push encoding (RFC 8291 + RFC 8188).
22
- - **`aesgcm` (legacy)**: kept for interoperability with older endpoints.
20
+ - Encodes payloads with **`aes128gcm`** (RFC 8291 + RFC 8188) — the only encoding modern browsers accept.
21
+ The legacy `aesgcm` draft encoding is not supported; all current push services and browsers use `aes128gcm`.
23
22
 
24
23
  ### 🔐 Encryption
25
24
 
@@ -201,7 +200,7 @@ type GenerateRequestOptions = {
201
200
  urgency?: "very-low" | "low" | "normal" | "high";
202
201
  topic?: string; // base64url <= 32 chars
203
202
 
204
- contentEncoding?: "aes128gcm" | "aesgcm";
203
+ contentEncoding?: "aes128gcm";
205
204
 
206
205
  // RFC8188 knobs (primarily for advanced use/testing)
207
206
  rs?: number; // default 4096, must be >= 18
@@ -216,7 +215,7 @@ type GenerateRequestOptions = {
216
215
 
217
216
  ### Notes
218
217
 
219
- * **`aes128gcm` is recommended** for Web Push.
218
+ * Payloads are always encrypted with `aes128gcm` (RFC 8291 + RFC 8188).
220
219
  * For Web Push interoperability, leave `allowMultipleRecords` at `false` (default).
221
220
  * `topic` must use URL-safe base64 characters and be <= 32 chars.
222
221
 
@@ -294,7 +293,7 @@ try {
294
293
 
295
294
  ## Runtime requirements
296
295
 
297
- * Node.js with global `fetch` (Node 18+ recommended).
296
+ * Node.js 20+ (with global `fetch`).
298
297
  * TypeScript `target: ES2020` works.
299
298
 
300
299
  ---
@@ -1,5 +1,4 @@
1
1
  export declare enum SupportedContentEncoding {
2
- AES_GCM = "aesgcm",
3
2
  AES_128_GCM = "aes128gcm"
4
3
  }
5
4
  export declare enum SupportedUrgency {
@@ -12,16 +12,3 @@ export declare function encryptAes128GcmBody(params: {
12
12
  allowMultipleRecords: boolean;
13
13
  finalRecordPadding: number;
14
14
  }): Buffer;
15
- /**
16
- * Legacy aesgcm encryption for interop.
17
- * Returns ciphertext plus values needed for headers: Encryption (salt) and Crypto-Key (dh).
18
- */
19
- export declare function encryptAesGcmLegacy(params: {
20
- p256dh: string;
21
- auth: string;
22
- payload: Buffer;
23
- }): {
24
- saltB64Url: string;
25
- localPublicKey: Buffer;
26
- ciphertext: Buffer;
27
- };
package/dist/index.cjs.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const m=require("crypto");var w=(e=>(e.AES_GCM="aesgcm",e.AES_128_GCM="aes128gcm",e))(w||{}),A=(e=>(e.VERY_LOW="very-low",e.LOW="low",e.NORMAL="normal",e.HIGH="high",e))(A||{});const T=2419200,D=4096,P=18,f={validate(e){return/^[A-Za-z0-9\-_]+$/.test(e)},toBuffer(e){return Buffer.from(e,"base64url")},toString(e){return(typeof e=="string"?Buffer.from(e,"utf8"):Buffer.from(e)).toString("base64url")},toJSON(e){return this.toString(Buffer.from(JSON.stringify(e),"utf8"))}},N=720*60,$=1440*60;function L(e){const t=new Date;return t.setSeconds(t.getSeconds()+e),Math.floor(t.getTime()/1e3)}const E={subject(e){if(!e)throw new Error("No subject set in VAPID details.");const t=typeof e=="string"?new URL(e):e;if(!["https:","mailto:"].includes(t.protocol))throw new Error(`VAPID subject must be https: or mailto:. Got: ${t.toString()}`);t.hostname==="localhost"&&console.warn("VAPID subject points to localhost; some push services may reject this.")},publicKey(e){if(!e)throw new Error("No VAPID public key set.");if(typeof e!="string"||!f.validate(e))throw new Error('VAPID public key must be base64url without "=" padding.');const t=f.toBuffer(e);if(t.length!==65||t[0]!==4)throw new Error("VAPID public key must decode to 65-byte uncompressed P-256.")},privateKey(e){if(!e)throw new Error("No VAPID private key set.");if(typeof e!="string"||!f.validate(e))throw new Error('VAPID private key must be base64url without "=" padding.');if(f.toBuffer(e).length!==32)throw new Error("VAPID private key must decode to 32 bytes.")},expiration(e){if(!Number.isInteger(e))throw new Error("VAPID expiration must be an integer.");if(e<0)throw new Error("VAPID expiration must be positive.");const t=L($);if(e>=t)throw new Error("VAPID expiration exceeds 24 hours.")},audience(e){if(!e)throw new Error("No audience set for VAPID.");const t=typeof e=="string"?new URL(e):e;if(!t.origin||t.origin==="null")throw new Error(`VAPID audience must be an origin. Got: ${t.toString()}`)}};function G(){const e=m.createECDH("prime256v1");e.generateKeys();let t=e.getPublicKey(),r=e.getPrivateKey();return r.length<32&&(r=Buffer.concat([Buffer.alloc(32-r.length,0),r])),t.length<65&&(t=Buffer.concat([Buffer.alloc(65-t.length,0),t])),{publicKey:f.toString(t),privateKey:f.toString(r)}}function U(e){const t=f.toBuffer(e);if(t.length!==65||t[0]!==4)throw new Error("Invalid uncompressed P-256 public key.");return{x:f.toString(t.subarray(1,33)),y:f.toString(t.subarray(33,65))}}function j(e){const t=f.toBuffer(e);if(t.length!==32)throw new Error("Invalid P-256 private key scalar.");return f.toString(t)}function O(e,t=32){let r=0;if(e[r++]!==48)throw new Error("Invalid DER signature (expected SEQUENCE).");let n=e[r++];if(n&128){const l=n&127;r+=l}if(e[r++]!==2)throw new Error("Invalid DER signature (expected INTEGER r).");const c=e[r++];let o=e.subarray(r,r+c);if(r+=c,e[r++]!==2)throw new Error("Invalid DER signature (expected INTEGER s).");const i=e[r++];let s=e.subarray(r,r+i);if(o[0]===0&&(o=o.subarray(1)),s[0]===0&&(s=s.subarray(1)),o.length>t||s.length>t)throw new Error("Invalid DER signature size.");const u=Buffer.concat([Buffer.alloc(t-o.length,0),o]),d=Buffer.concat([Buffer.alloc(t-s.length,0),s]);return Buffer.concat([u,d])}function V(e,t,r){const n={typ:"JWT",alg:"ES256"},c=f.toJSON(n),o=f.toJSON(e),i=`${c}.${o}`,{x:s,y:u}=U(r),d=j(t),l=m.createPrivateKey({key:{kty:"EC",crv:"P-256",x:s,y:u,d},format:"jwk"}),y=m.sign("sha256",Buffer.from(i,"utf8"),{key:l,dsaEncoding:"der"}),h=O(y,32),g=f.toString(h);return`${i}.${g}`}function _(e){E.audience(e.audience),E.subject(e.subject),E.publicKey(e.publicKey),E.privateKey(e.privateKey);const t=(typeof e.audience=="string"?new URL(e.audience):e.audience).origin,r=(typeof e.subject=="string"?new URL(e.subject):e.subject).toString(),n=e.expiration??L(N);E.expiration(n);const c=V({aud:t,exp:n,sub:r},e.privateKey,e.publicKey),o=new Headers;if(e.contentEncoding===w.AES_128_GCM)o.set("Authorization",`vapid t=${c}, k=${e.publicKey}`);else if(e.contentEncoding===w.AES_GCM)o.set("Authorization",`WebPush ${c}`),o.set("Crypto-Key",`p256ecdsa=${e.publicKey}`);else throw new Error(`Unsupported content encoding: ${e.contentEncoding}`);return o}const F=Object.freeze(Object.defineProperty({__proto__:null,GenerateHeaders:_,GenerateKeys:G,Validate:E},Symbol.toStringTag,{value:"Module"}));function W(e){const t=Buffer.alloc(2);return t.writeUInt16BE(e,0),t}function H(e){const t=Buffer.alloc(4);return t.writeUInt32BE(e>>>0,0),t}function J(e,t){if(e.length!==t.length)throw new Error("xorBytes: length mismatch");const r=Buffer.alloc(e.length);for(let n=0;n<e.length;n++)r[n]=e[n]^t[n];return r}function z(e){if(!Number.isSafeInteger(e)||e<0)throw new Error("SEQ must be a non-negative safe integer");const t=Buffer.alloc(12,0),r=Math.floor(e/4294967296),n=e>>>0;return t.writeUInt32BE(r>>>0,4),t.writeUInt32BE(n,8),t}function X(e){const{message:t,rs:r,allowMultipleRecords:n,finalRecordPadding:c}=e;if(!Number.isInteger(r)||r<P)throw new Error(`Invalid rs; must be an integer >= ${P}.`);const o=r-16;if(o<=1)throw new Error("Invalid rs (too small)");const i=o-1;if(!n&&t.length>i)throw new Error(`Payload too large for a single RFC8188 record: ${t.length} bytes. Max is ${i} bytes for rs=${r}.`);if(!Number.isInteger(c)||c<0)throw new Error("finalRecordPadding must be a non-negative integer");const s=[];let u=0;for(;u<t.length;){const d=t.length-u,l=d<=i,y=l?d:i,h=t.subarray(u,u+y);u+=y;const g=Buffer.from([l?2:1]);if(l)s.push(Buffer.concat([h,g,Buffer.alloc(c,0)]));else{const a=o-(h.length+1);s.push(Buffer.concat([h,g,Buffer.alloc(a,0)]))}}return t.length===0&&s.push(Buffer.from([2])),s}function q(e){const{cek:t,baseNonce:r,plaintextRecords:n}=e;if(t.length!==16)throw new Error("CEK must be 16 bytes");if(r.length!==12)throw new Error("baseNonce must be 12 bytes");const c=[];for(let o=0;o<n.length;o++){const i=J(r,z(o)),s=m.createCipheriv("aes-128-gcm",t,i),u=Buffer.concat([s.update(n[o]),s.final()]);c.push(Buffer.concat([u,s.getAuthTag()]))}return Buffer.concat(c)}function b(e,t){return m.createHmac("sha256",e).update(t).digest()}function x(e,t){if(!e||typeof e!="string")throw new Error("subscription.keys.p256dh must be a string");if(!t||typeof t!="string")throw new Error("subscription.keys.auth must be a string");const r=f.toBuffer(e);if(r.length!==65||r[0]!==4)throw new Error("subscription.keys.p256dh must decode to a 65-byte uncompressed P-256 public key");if(f.toBuffer(t).length<16)throw new Error("subscription.keys.auth must decode to at least 16 bytes")}function Q(e){x(e.p256dh,e.auth);const t=f.toBuffer(e.p256dh),r=f.toBuffer(e.auth),n=m.createECDH("prime256v1");n.generateKeys();const c=n.getPublicKey(),o=n.computeSecret(t),i=m.randomBytes(16),s=b(r,o),u=Buffer.concat([Buffer.from("WebPush: info","utf8"),Buffer.from([0]),t,c]),d=b(s,Buffer.concat([u,Buffer.from([1])])),l=b(i,d),y=Buffer.concat([Buffer.from("Content-Encoding: aes128gcm","utf8"),Buffer.from([0])]),h=Buffer.concat([Buffer.from("Content-Encoding: nonce","utf8"),Buffer.from([0])]),g=b(l,Buffer.concat([y,Buffer.from([1])])).subarray(0,16),a=b(l,Buffer.concat([h,Buffer.from([1])])).subarray(0,12),B=X({message:e.payload,rs:e.rs,allowMultipleRecords:e.allowMultipleRecords,finalRecordPadding:e.finalRecordPadding}),I=q({cek:g,baseNonce:a,plaintextRecords:B});if(c.length!==65)throw new Error("Sender public key must be 65 bytes");return Buffer.concat([i,H(e.rs),Buffer.from([c.length]),c,I])}function Y(e){x(e.p256dh,e.auth);const t=f.toBuffer(e.p256dh),r=f.toBuffer(e.auth),n=m.createECDH("prime256v1");n.generateKeys();const c=n.getPublicKey(),o=n.computeSecret(t),i=m.randomBytes(16),s=b(r,o),u=Buffer.concat([Buffer.from("WebPush: info","utf8"),Buffer.from([0]),t,c]),d=b(s,Buffer.concat([u,Buffer.from([1])])),l=b(i,d),y=Buffer.concat([Buffer.from("Content-Encoding: aesgcm","utf8"),Buffer.from([0])]),h=Buffer.concat([Buffer.from("Content-Encoding: nonce","utf8"),Buffer.from([0])]),g=b(l,Buffer.concat([y,Buffer.from([1])])).subarray(0,16),a=b(l,Buffer.concat([h,Buffer.from([1])])).subarray(0,12),B=0,I=Buffer.concat([W(B),Buffer.alloc(B,0),e.payload]),v=m.createCipheriv("aes-128-gcm",g,a),k=Buffer.concat([v.update(I),v.final()]),R=v.getAuthTag();return{saltB64Url:f.toString(i),localPublicKey:c,ciphertext:Buffer.concat([k,R])}}class M extends Error{constructor(t,r){super(t),this.response=r}}function Z(e){if(!f.validate(e))throw new Error("Topic contains invalid characters; must be URL-safe base64 chars.");if(e.length>32)throw new Error("Topic must be <= 32 characters.")}function ee(e){return e.startsWith("https://android.googleapis.com/gcm/send")}function te(e){return e.startsWith("https://fcm.googleapis.com/fcm/send")||e.includes("fcm.googleapis.com")}function re(e){return new URL(e).origin}function ne(e){if(!Object.values(w).includes(e))throw new Error(`Unsupported content encoding: ${e}`)}function oe(e){if(!Object.values(A).includes(e))throw new Error(`Unsupported urgency: ${e}`)}class ce{constructor(t){if(this.config=t,E.subject(t.vapid.subject),E.privateKey(t.vapid.privateKey),E.publicKey(t.vapid.publicKey),t.gcm?.apiKey!=null&&t.gcm.apiKey.length===0)throw new Error("The GCM/FCM API Key should be a non-empty string, null, or undefined.")}generateRequest(t,r,n){if(!t?.endpoint||typeof t.endpoint!="string"||t.endpoint.length===0)throw new Error("You must pass a subscription with a valid endpoint URL.");const c=n?.TTL??T;if(!Number.isFinite(c)||c<0)throw new Error("TTL must be a number >= 0.");const o=n?.contentEncoding??w.AES_128_GCM;ne(o);const i=n?.urgency??A.NORMAL;oe(i);const s=n?.topic;s&&Z(s);const u=n?.allowMultipleRecords??!1,d=n?.rs??D;if(!Number.isInteger(d)||d<P)throw new Error(`rs must be an integer >= ${P}.`);const l=n?.finalRecordPadding??0,y=n?.headers??{},h=n?.gcmAPIKey??this.config.gcm?.apiKey??null,g=n?.vapidDetails===void 0?this.config.vapid:n.vapidDetails,a={...y,TTL:String(c),Urgency:i};s&&(a.Topic=s);let B;if(r!=null&&r!==""){if(!t.keys?.p256dh||!t.keys?.auth)throw new Error("To send a payload, the subscription must include 'keys.p256dh' and 'keys.auth'.");const S=Buffer.isBuffer(r)?r:r instanceof Uint8Array?Buffer.from(r):Buffer.from(r,"utf8");if(a["Content-Type"]="application/octet-stream",o===w.AES_128_GCM){const p=Q({p256dh:t.keys.p256dh,auth:t.keys.auth,payload:S,rs:d,allowMultipleRecords:u,finalRecordPadding:l});B=p,a["Content-Encoding"]=w.AES_128_GCM,a["Content-Length"]=String(p.length)}else{const p=Y({p256dh:t.keys.p256dh,auth:t.keys.auth,payload:S});B=p.ciphertext,a["Content-Encoding"]=w.AES_GCM,a["Content-Length"]=String(p.ciphertext.length),a.Encryption=`salt=${p.saltB64Url}`,a["Crypto-Key"]=`dh=${f.toString(p.localPublicKey)}`}}else a["Content-Length"]="0";const v=t.endpoint,k=ee(v),R=te(v);if(k)h?a.Authorization=`key=${h}`:console.warn("GCM endpoint detected but no GCM API key provided.");else if(g){const S=re(v),p=_({audience:S,subject:g.subject,publicKey:g.publicKey,privateKey:g.privateKey,contentEncoding:o}),C=p.get("Authorization");C&&(a.Authorization=C);const K=p.get("Crypto-Key");K&&o===w.AES_GCM&&(a["Crypto-Key"]=a["Crypto-Key"]?`${a["Crypto-Key"]}; ${K}`:K)}else R&&h&&(a.Authorization=`key=${h}`);return{endpoint:v,init:{method:"POST",headers:a,body:B}}}async notify(t,r,n){const{endpoint:c,init:o}=this.generateRequest(t,r,n),i=await fetch(c,o);if(!i.ok&&n?.throwOnInvalidResponse)throw new M("Received unexpected response code",i);return i}}exports.DEFAULT_RS=D;exports.DEFAULT_TTL=T;exports.MIN_RS=P;exports.SupportedContentEncoding=w;exports.SupportedUrgency=A;exports.VAPID=F;exports.WebPush=ce;exports.WebPushError=M;
2
- //# sourceMappingURL=index.cjs.js.map
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r},s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(n,r,o)=>(o=n==null?{}:e(i(n)),s(r||!n||!n.__esModule||!a.call(n,`default`)?t(o,`default`,{value:n,enumerable:!0}):o,n));let l=require("crypto");l=c(l,1);var u=function(e){return e.AES_128_GCM=`aes128gcm`,e}({}),d=function(e){return e.VERY_LOW=`very-low`,e.LOW=`low`,e.NORMAL=`normal`,e.HIGH=`high`,e}({}),f=2419200,p=4096,m=18,h={validate(e){return/^[A-Za-z0-9\-_]+$/.test(e)},toBuffer(e){return Buffer.from(e,`base64url`)},toString(e){return(typeof e==`string`?Buffer.from(e,`utf8`):Buffer.from(e)).toString(`base64url`)},toJSON(e){return this.toString(Buffer.from(JSON.stringify(e),`utf8`))}},g=o({GenerateHeaders:()=>E,GenerateKeys:()=>x,Validate:()=>b}),_=43200,v=86400;function y(e){let t=new Date;return t.setSeconds(t.getSeconds()+e),Math.floor(t.getTime()/1e3)}var b={subject(e){if(!e)throw Error(`No subject set in VAPID details.`);let t=typeof e==`string`?new URL(e):e;if(![`https:`,`mailto:`].includes(t.protocol))throw Error(`VAPID subject must be https: or mailto:. Got: ${t.toString()}`);t.hostname===`localhost`&&console.warn(`VAPID subject points to localhost; some push services may reject this.`)},publicKey(e){if(!e)throw Error(`No VAPID public key set.`);if(typeof e!=`string`||!h.validate(e))throw Error(`VAPID public key must be base64url without "=" padding.`);let t=h.toBuffer(e);if(t.length!==65||t[0]!==4)throw Error(`VAPID public key must decode to 65-byte uncompressed P-256.`)},privateKey(e){if(!e)throw Error(`No VAPID private key set.`);if(typeof e!=`string`||!h.validate(e))throw Error(`VAPID private key must be base64url without "=" padding.`);if(h.toBuffer(e).length!==32)throw Error(`VAPID private key must decode to 32 bytes.`)},expiration(e){if(!Number.isInteger(e))throw Error(`VAPID expiration must be an integer.`);if(e<0)throw Error(`VAPID expiration must be positive.`);if(e>=y(v))throw Error(`VAPID expiration exceeds 24 hours.`)},audience(e){if(!e)throw Error(`No audience set for VAPID.`);let t=typeof e==`string`?new URL(e):e;if(!t.origin||t.origin===`null`)throw Error(`VAPID audience must be an origin. Got: ${t.toString()}`)}};function x(){let e=l.default.createECDH(`prime256v1`);e.generateKeys();let t=e.getPublicKey(),n=e.getPrivateKey();return n.length<32&&(n=Buffer.concat([Buffer.alloc(32-n.length,0),n])),t.length<65&&(t=Buffer.concat([Buffer.alloc(65-t.length,0),t])),{publicKey:h.toString(t),privateKey:h.toString(n)}}function S(e){let t=h.toBuffer(e);if(t.length!==65||t[0]!==4)throw Error(`Invalid uncompressed P-256 public key.`);return{x:h.toString(t.subarray(1,33)),y:h.toString(t.subarray(33,65))}}function C(e){let t=h.toBuffer(e);if(t.length!==32)throw Error(`Invalid P-256 private key scalar.`);return h.toString(t)}function w(e,t=32){let n=0;if(e[n++]!==48)throw Error(`Invalid DER signature (expected SEQUENCE).`);let r=e[n++];if(r&128){let e=r&127;n+=e}if(e[n++]!==2)throw Error(`Invalid DER signature (expected INTEGER r).`);let i=e[n++],a=e.subarray(n,n+i);if(n+=i,e[n++]!==2)throw Error(`Invalid DER signature (expected INTEGER s).`);let o=e[n++],s=e.subarray(n,n+o);if(a[0]===0&&(a=a.subarray(1)),s[0]===0&&(s=s.subarray(1)),a.length>t||s.length>t)throw Error(`Invalid DER signature size.`);let c=Buffer.concat([Buffer.alloc(t-a.length,0),a]),l=Buffer.concat([Buffer.alloc(t-s.length,0),s]);return Buffer.concat([c,l])}function T(e,t,n){let r=`${h.toJSON({typ:`JWT`,alg:`ES256`})}.${h.toJSON(e)}`,{x:i,y:a}=S(n),o=C(t),s=l.default.createPrivateKey({key:{kty:`EC`,crv:`P-256`,x:i,y:a,d:o},format:`jwk`}),c=w(l.default.sign(`sha256`,Buffer.from(r,`utf8`),{key:s,dsaEncoding:`der`}),32);return`${r}.${h.toString(c)}`}function E(e){b.audience(e.audience),b.subject(e.subject),b.publicKey(e.publicKey),b.privateKey(e.privateKey);let t=(typeof e.audience==`string`?new URL(e.audience):e.audience).origin,n=(typeof e.subject==`string`?new URL(e.subject):e.subject).toString(),r=e.expiration??y(_);b.expiration(r);let i=T({aud:t,exp:r,sub:n},e.privateKey,e.publicKey),a=new Headers;if(e.contentEncoding!==u.AES_128_GCM)throw Error(`Unsupported content encoding: ${e.contentEncoding}`);return a.set(`Authorization`,`vapid t=${i}, k=${e.publicKey}`),a}function D(e){let t=Buffer.alloc(4);return t.writeUInt32BE(e>>>0,0),t}function O(e,t){if(e.length!==t.length)throw Error(`xorBytes: length mismatch`);let n=Buffer.alloc(e.length);for(let r=0;r<e.length;r++)n[r]=e[r]^t[r];return n}function k(e){if(!Number.isSafeInteger(e)||e<0)throw Error(`SEQ must be a non-negative safe integer`);let t=Buffer.alloc(12,0),n=Math.floor(e/4294967296),r=e>>>0;return t.writeUInt32BE(n>>>0,4),t.writeUInt32BE(r,8),t}function A(e){let{message:t,rs:n,allowMultipleRecords:r,finalRecordPadding:i}=e;if(!Number.isInteger(n)||n<18)throw Error(`Invalid rs; must be an integer >= 18.`);let a=n-16;if(a<=1)throw Error(`Invalid rs (too small)`);let o=a-1;if(!r&&t.length>o)throw Error(`Payload too large for a single RFC8188 record: ${t.length} bytes. Max is ${o} bytes for rs=${n}.`);if(!Number.isInteger(i)||i<0)throw Error(`finalRecordPadding must be a non-negative integer`);let s=[],c=0;for(;c<t.length;){let e=t.length-c,n=e<=o,r=n?e:o,l=t.subarray(c,c+r);c+=r;let u=Buffer.from([n?2:1]);if(n)s.push(Buffer.concat([l,u,Buffer.alloc(i,0)]));else{let e=a-(l.length+1);s.push(Buffer.concat([l,u,Buffer.alloc(e,0)]))}}return t.length===0&&s.push(Buffer.from([2])),s}function j(e){let{cek:t,baseNonce:n,plaintextRecords:r}=e;if(t.length!==16)throw Error(`CEK must be 16 bytes`);if(n.length!==12)throw Error(`baseNonce must be 12 bytes`);let i=[];for(let e=0;e<r.length;e++){let a=O(n,k(e)),o=l.default.createCipheriv(`aes-128-gcm`,t,a),s=Buffer.concat([o.update(r[e]),o.final()]);i.push(Buffer.concat([s,o.getAuthTag()]))}return Buffer.concat(i)}function M(e,t){return l.default.createHmac(`sha256`,e).update(t).digest()}function N(e,t){if(!e||typeof e!=`string`)throw Error(`subscription.keys.p256dh must be a string`);if(!t||typeof t!=`string`)throw Error(`subscription.keys.auth must be a string`);let n=h.toBuffer(e);if(n.length!==65||n[0]!==4)throw Error(`subscription.keys.p256dh must decode to a 65-byte uncompressed P-256 public key`);if(h.toBuffer(t).length<16)throw Error(`subscription.keys.auth must decode to at least 16 bytes`)}function P(e){N(e.p256dh,e.auth);let t=h.toBuffer(e.p256dh),n=h.toBuffer(e.auth),r=l.default.createECDH(`prime256v1`);r.generateKeys();let i=r.getPublicKey(),a=r.computeSecret(t),o=l.default.randomBytes(16),s=M(n,a),c=Buffer.concat([Buffer.from(`WebPush: info`,`utf8`),Buffer.from([0]),t,i]),u=M(o,M(s,Buffer.concat([c,Buffer.from([1])]))),d=Buffer.concat([Buffer.from(`Content-Encoding: aes128gcm`,`utf8`),Buffer.from([0])]),f=Buffer.concat([Buffer.from(`Content-Encoding: nonce`,`utf8`),Buffer.from([0])]),p=j({cek:M(u,Buffer.concat([d,Buffer.from([1])])).subarray(0,16),baseNonce:M(u,Buffer.concat([f,Buffer.from([1])])).subarray(0,12),plaintextRecords:A({message:e.payload,rs:e.rs,allowMultipleRecords:e.allowMultipleRecords,finalRecordPadding:e.finalRecordPadding})});if(i.length!==65)throw Error(`Sender public key must be 65 bytes`);return Buffer.concat([o,D(e.rs),Buffer.from([i.length]),i,p])}var F=class extends Error{constructor(e,t){super(e),this.response=t}};function I(e){if(!h.validate(e))throw Error(`Topic contains invalid characters; must be URL-safe base64 chars.`);if(e.length>32)throw Error(`Topic must be <= 32 characters.`)}function L(e){return e.startsWith(`https://android.googleapis.com/gcm/send`)}function R(e){return e.startsWith(`https://fcm.googleapis.com/fcm/send`)||e.includes(`fcm.googleapis.com`)}function z(e){return new URL(e).origin}function B(e){if(!Object.values(u).includes(e))throw Error(`Unsupported content encoding: ${e}`)}function V(e){if(!Object.values(d).includes(e))throw Error(`Unsupported urgency: ${e}`)}var H=class{constructor(e){if(this.config=e,b.subject(e.vapid.subject),b.privateKey(e.vapid.privateKey),b.publicKey(e.vapid.publicKey),e.gcm?.apiKey!=null&&e.gcm.apiKey.length===0)throw Error(`The GCM/FCM API Key should be a non-empty string, null, or undefined.`)}generateRequest(e,t,n){if(!e?.endpoint||typeof e.endpoint!=`string`||e.endpoint.length===0)throw Error(`You must pass a subscription with a valid endpoint URL.`);let r=n?.TTL??2419200;if(!Number.isFinite(r)||r<0)throw Error(`TTL must be a number >= 0.`);let i=n?.contentEncoding??u.AES_128_GCM;B(i);let a=n?.urgency??d.NORMAL;V(a);let o=n?.topic;o&&I(o);let s=n?.allowMultipleRecords??!1,c=n?.rs??4096;if(!Number.isInteger(c)||c<18)throw Error(`rs must be an integer >= 18.`);let l=n?.finalRecordPadding??0,f=n?.headers??{},p=n?.gcmAPIKey??this.config.gcm?.apiKey??null,m=n?.vapidDetails===void 0?this.config.vapid:n.vapidDetails,h={...f,TTL:String(r),Urgency:a};o&&(h.Topic=o);let g;if(t!=null&&t!==``){if(!e.keys?.p256dh||!e.keys?.auth)throw Error(`To send a payload, the subscription must include 'keys.p256dh' and 'keys.auth'.`);let n=Buffer.isBuffer(t)?t:t instanceof Uint8Array?Buffer.from(t):Buffer.from(t,`utf8`);h[`Content-Type`]=`application/octet-stream`;let r=P({p256dh:e.keys.p256dh,auth:e.keys.auth,payload:n,rs:c,allowMultipleRecords:s,finalRecordPadding:l});g=r,h[`Content-Encoding`]=u.AES_128_GCM,h[`Content-Length`]=String(r.length)}else h[`Content-Length`]=`0`;let _=e.endpoint,v=L(_),y=R(_);if(v)p?h.Authorization=`key=${p}`:console.warn(`GCM endpoint detected but no GCM API key provided.`);else if(m){let e=E({audience:z(_),subject:m.subject,publicKey:m.publicKey,privateKey:m.privateKey,contentEncoding:i}).get(`Authorization`);e&&(h.Authorization=e)}else y&&p&&(h.Authorization=`key=${p}`);return{endpoint:_,init:{method:`POST`,headers:h,body:g}}}async notify(e,t,n){let{endpoint:r,init:i}=this.generateRequest(e,t,n),a=await fetch(r,i);if(!a.ok&&n?.throwOnInvalidResponse)throw new F(`Received unexpected response code`,a);return a}};exports.DEFAULT_RS=p,exports.DEFAULT_TTL=f,exports.MIN_RS=m,exports.SupportedContentEncoding=u,exports.SupportedUrgency=d,Object.defineProperty(exports,"VAPID",{enumerable:!0,get:function(){return g}}),exports.WebPush=H,exports.WebPushError=F;
2
+ //# sourceMappingURL=index.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../src/constants.ts","../src/utils/base64url.ts","../src/vapid.ts","../src/utils/binary.ts","../src/crypto/rfc8188.ts","../src/crypto/webpush-encryption.ts","../src/webpush.ts"],"sourcesContent":["'use strict';\n\nexport enum SupportedContentEncoding {\n AES_GCM = 'aesgcm',\n AES_128_GCM = 'aes128gcm',\n}\n\nexport enum SupportedUrgency {\n VERY_LOW = 'very-low',\n LOW = 'low',\n NORMAL = 'normal',\n HIGH = 'high',\n}\n\n/** Default TTL is 4 weeks (seconds). */\nexport const DEFAULT_TTL = 2419200;\n\n/**\n * Default record size (RFC8188 `rs`) for aes128gcm.\n * For Web Push (RFC8291), a single record is required, but `rs` still appears in the body header.\n */\nexport const DEFAULT_RS = 4096;\n\n/** Minimum valid `rs` for aes128gcm per RFC8188 (must be >= 18). */\nexport const MIN_RS = 18;\n","'use strict';\n\nexport const base64url = {\n validate(input: string): boolean {\n // URL-safe base64 without \"=\" padding\n return /^[A-Za-z0-9\\-_]+$/.test(input);\n },\n toBuffer(input: string): Buffer {\n return Buffer.from(input, 'base64url');\n },\n toString(input: Buffer | Uint8Array | string): string {\n const buf = typeof input === 'string' ? Buffer.from(input, 'utf8') : Buffer.from(input);\n return buf.toString('base64url');\n },\n toJSON(obj: any) {\n return this.toString(Buffer.from(JSON.stringify(obj), 'utf8'));\n }\n};\n","'use strict';\n\nimport crypto from 'crypto';\nimport {base64url} from './utils/base64url';\nimport {SupportedContentEncoding} from './constants';\n\nconst DEFAULT_EXPIRATION_SECONDS = 12 * 60 * 60;\nconst MAX_EXPIRATION_SECONDS = 24 * 60 * 60;\n\nfunction getFutureExpirationTimestamp(numSeconds: number): number {\n const future = new Date();\n future.setSeconds(future.getSeconds() + numSeconds);\n return Math.floor(future.getTime() / 1000);\n}\n\nexport const Validate = {\n subject(subject: string | URL): void {\n if (!subject) throw new Error('No subject set in VAPID details.');\n const u = typeof subject === 'string' ? new URL(subject) : subject;\n if (!['https:', 'mailto:'].includes(u.protocol)) {\n throw new Error(`VAPID subject must be https: or mailto:. Got: ${u.toString()}`);\n }\n if (u.hostname === 'localhost') {\n console.warn('VAPID subject points to localhost; some push services may reject this.');\n }\n },\n\n publicKey(publicKey: string): void {\n if (!publicKey) throw new Error('No VAPID public key set.');\n if (typeof publicKey !== 'string' || !base64url.validate(publicKey)) {\n throw new Error('VAPID public key must be base64url without \"=\" padding.');\n }\n const pk = base64url.toBuffer(publicKey);\n if (pk.length !== 65 || pk[0] !== 0x04) {\n throw new Error('VAPID public key must decode to 65-byte uncompressed P-256.');\n }\n },\n\n privateKey(privateKey: string): void {\n if (!privateKey) throw new Error('No VAPID private key set.');\n if (typeof privateKey !== 'string' || !base64url.validate(privateKey)) {\n throw new Error('VAPID private key must be base64url without \"=\" padding.');\n }\n const sk = base64url.toBuffer(privateKey);\n if (sk.length !== 32) throw new Error('VAPID private key must decode to 32 bytes.');\n },\n\n expiration(expiration: number): void {\n if (!Number.isInteger(expiration)) throw new Error('VAPID expiration must be an integer.');\n if (expiration < 0) throw new Error('VAPID expiration must be positive.');\n const maxExp = getFutureExpirationTimestamp(MAX_EXPIRATION_SECONDS);\n if (expiration >= maxExp) throw new Error('VAPID expiration exceeds 24 hours.');\n },\n\n audience(audience: string | URL): void {\n if (!audience) throw new Error('No audience set for VAPID.');\n const a = typeof audience === 'string' ? new URL(audience) : audience;\n if (!a.origin || a.origin === 'null') {\n throw new Error(`VAPID audience must be an origin. Got: ${a.toString()}`);\n }\n },\n};\n\nexport function GenerateKeys(): { publicKey: string; privateKey: string } {\n const ecdh = crypto.createECDH('prime256v1');\n ecdh.generateKeys();\n\n let pub = ecdh.getPublicKey();\n let priv = ecdh.getPrivateKey();\n\n if (priv.length < 32) priv = Buffer.concat([Buffer.alloc(32 - priv.length, 0x00), priv]);\n if (pub.length < 65) pub = Buffer.concat([Buffer.alloc(65 - pub.length, 0x00), pub]);\n\n return {publicKey: base64url.toString(pub), privateKey: base64url.toString(priv)};\n}\n\nfunction publicKeyToJwk(publicKeyB64Url: string): { x: string; y: string } {\n const pub = base64url.toBuffer(publicKeyB64Url);\n if (pub.length !== 65 || pub[0] !== 0x04) throw new Error('Invalid uncompressed P-256 public key.');\n return {x: base64url.toString(pub.subarray(1, 33)), y: base64url.toString(pub.subarray(33, 65))};\n}\n\nfunction privateKeyToJwk(privateKeyB64Url: string): string {\n const d = base64url.toBuffer(privateKeyB64Url);\n if (d.length !== 32) throw new Error('Invalid P-256 private key scalar.');\n return base64url.toString(d);\n}\n\nfunction derToJose(derSig: Buffer, keySizeBytes = 32): Buffer {\n let off = 0;\n if (derSig[off++] !== 0x30) throw new Error('Invalid DER signature (expected SEQUENCE).');\n\n let seqLen = derSig[off++];\n if (seqLen & 0x80) {\n const n = seqLen & 0x7f;\n off += n;\n }\n\n if (derSig[off++] !== 0x02) throw new Error('Invalid DER signature (expected INTEGER r).');\n const rLen = derSig[off++];\n let r = derSig.subarray(off, off + rLen);\n off += rLen;\n\n if (derSig[off++] !== 0x02) throw new Error('Invalid DER signature (expected INTEGER s).');\n const sLen = derSig[off++];\n let s = derSig.subarray(off, off + sLen);\n\n if (r[0] === 0x00) r = r.subarray(1);\n if (s[0] === 0x00) s = s.subarray(1);\n\n if (r.length > keySizeBytes || s.length > keySizeBytes) throw new Error('Invalid DER signature size.');\n const rPad = Buffer.concat([Buffer.alloc(keySizeBytes - r.length, 0x00), r]);\n const sPad = Buffer.concat([Buffer.alloc(keySizeBytes - s.length, 0x00), s]);\n return Buffer.concat([rPad, sPad]);\n}\n\nfunction signJwtES256(payload: Record<string, unknown>, privateKeyB64Url: string, publicKeyB64Url: string): string {\n const header = {typ: 'JWT', alg: 'ES256'};\n const encodedHeader = base64url.toJSON(header);\n const encodedPayload = base64url.toJSON(payload);\n const signingInput = `${encodedHeader}.${encodedPayload}`;\n\n const {x, y} = publicKeyToJwk(publicKeyB64Url);\n const d = privateKeyToJwk(privateKeyB64Url);\n\n const keyObject = crypto.createPrivateKey({\n key: {kty: 'EC', crv: 'P-256', x, y, d},\n format: 'jwk',\n });\n\n const derSig = crypto.sign('sha256', Buffer.from(signingInput, 'utf8'), {\n key: keyObject,\n dsaEncoding: 'der',\n });\n\n const joseSig = derToJose(derSig, 32);\n const encodedSig = base64url.toString(joseSig);\n return `${signingInput}.${encodedSig}`;\n}\n\nexport function GenerateHeaders(input: {\n audience: string | URL;\n subject: string | URL;\n publicKey: string;\n privateKey: string;\n contentEncoding: SupportedContentEncoding;\n expiration?: number;\n}): Headers {\n Validate.audience(input.audience);\n Validate.subject(input.subject);\n Validate.publicKey(input.publicKey);\n Validate.privateKey(input.privateKey);\n\n const audience = (typeof input.audience === 'string' ? new URL(input.audience) : input.audience).origin;\n const subject = (typeof input.subject === 'string' ? new URL(input.subject) : input.subject).toString();\n\n const exp = input.expiration ?? getFutureExpirationTimestamp(DEFAULT_EXPIRATION_SECONDS);\n Validate.expiration(exp);\n\n const jwt = signJwtES256({aud: audience, exp, sub: subject}, input.privateKey, input.publicKey);\n\n const headers = new Headers();\n if (input.contentEncoding === SupportedContentEncoding.AES_128_GCM) {\n headers.set('Authorization', `vapid t=${jwt}, k=${input.publicKey}`);\n } else if (input.contentEncoding === SupportedContentEncoding.AES_GCM) {\n headers.set('Authorization', `WebPush ${jwt}`);\n headers.set('Crypto-Key', `p256ecdsa=${input.publicKey}`);\n } else {\n throw new Error(`Unsupported content encoding: ${input.contentEncoding}`);\n }\n return headers;\n}\n\n","'use strict';\n\n/** Write UInt16BE. */\nexport function u16be(n: number): Buffer {\n const b = Buffer.alloc(2);\n b.writeUInt16BE(n, 0);\n return b;\n}\n\n/** Write UInt32BE. */\nexport function u32be(n: number): Buffer {\n const b = Buffer.alloc(4);\n b.writeUInt32BE(n >>> 0, 0);\n return b;\n}\n\nexport function xorBytes(a: Buffer, b: Buffer): Buffer {\n if (a.length !== b.length) throw new Error('xorBytes: length mismatch');\n const out = Buffer.alloc(a.length);\n for (let i = 0; i < a.length; i++) out[i] = a[i] ^ b[i];\n return out;\n}\n\n/**\n * Convert a record sequence number into a 96-bit (12-octet) big-endian buffer.\n * RFC8188 defines SEQ as a 96-bit unsigned integer starting at 0.\n */\nexport function seq96be(seq: number): Buffer {\n if (!Number.isSafeInteger(seq) || seq < 0) throw new Error('SEQ must be a non-negative safe integer');\n const out = Buffer.alloc(12, 0x00);\n\n // Write into low 64 bits; high 32 bits remain zero.\n const hi = Math.floor(seq / 0x100000000);\n const lo = seq >>> 0;\n out.writeUInt32BE(hi >>> 0, 4);\n out.writeUInt32BE(lo, 8);\n return out;\n}\n","'use strict';\n\nimport crypto from 'crypto';\nimport { MIN_RS } from '../constants';\nimport { xorBytes, seq96be } from '../utils/binary';\n\n/**\n * Build RFC8188 plaintext records from message bytes.\n *\n * Record plaintext length for full records is `rs - 16` because AES-GCM adds a 16-byte tag.\n * The padding delimiter must be present in every record:\n * - 0x01 for all non-last records\n * - 0x02 for the last record\n */\nexport function buildPlaintextRecords(params: {\n message: Buffer;\n rs: number;\n allowMultipleRecords: boolean;\n finalRecordPadding: number;\n}): Buffer[] {\n const { message, rs, allowMultipleRecords, finalRecordPadding } = params;\n\n if (!Number.isInteger(rs) || rs < MIN_RS) {\n throw new Error(`Invalid rs; must be an integer >= ${MIN_RS}.`);\n }\n\n const maxPlain = rs - 16; // AES-GCM tag length is 16\n if (maxPlain <= 1) throw new Error('Invalid rs (too small)');\n\n const maxDataPerFullRecord = maxPlain - 1; // reserve delimiter\n\n if (!allowMultipleRecords && message.length > maxDataPerFullRecord) {\n throw new Error(\n `Payload too large for a single RFC8188 record: ${message.length} bytes. ` +\n `Max is ${maxDataPerFullRecord} bytes for rs=${rs}.`\n );\n }\n\n if (!Number.isInteger(finalRecordPadding) || finalRecordPadding < 0) {\n throw new Error('finalRecordPadding must be a non-negative integer');\n }\n\n const records: Buffer[] = [];\n let offset = 0;\n\n while (offset < message.length) {\n const remaining = message.length - offset;\n const isLast = remaining <= maxDataPerFullRecord;\n\n const dataLen = isLast ? remaining : maxDataPerFullRecord;\n const data = message.subarray(offset, offset + dataLen);\n offset += dataLen;\n\n const delimiter = Buffer.from([isLast ? 0x02 : 0x01]);\n\n if (!isLast) {\n // Full-sized non-last record.\n const paddingLen = maxPlain - (data.length + 1);\n records.push(Buffer.concat([data, delimiter, Buffer.alloc(paddingLen, 0x00)]));\n } else {\n // Last record can be partial and may include padding after delimiter.\n records.push(Buffer.concat([data, delimiter, Buffer.alloc(finalRecordPadding, 0x00)]));\n }\n }\n\n // Empty message -> final record with only delimiter (0x02)\n if (message.length === 0) records.push(Buffer.from([0x02]));\n\n return records;\n}\n\n/**\n * Encrypt RFC8188 plaintext records using AES-128-GCM.\n *\n * Nonce per record: nonce = baseNonce XOR SEQ (96-bit big-endian SEQ starting at 0).\n * AAD is empty.\n */\nexport function encryptRecords(params: {\n cek: Buffer; // 16 bytes\n baseNonce: Buffer; // 12 bytes\n plaintextRecords: Buffer[];\n}): Buffer {\n const { cek, baseNonce, plaintextRecords } = params;\n if (cek.length !== 16) throw new Error('CEK must be 16 bytes');\n if (baseNonce.length !== 12) throw new Error('baseNonce must be 12 bytes');\n\n const out: Buffer[] = [];\n for (let i = 0; i < plaintextRecords.length; i++) {\n const nonce = xorBytes(baseNonce, seq96be(i));\n const cipher = crypto.createCipheriv('aes-128-gcm', cek, nonce);\n\n const enc = Buffer.concat([cipher.update(plaintextRecords[i]), cipher.final()]);\n out.push(Buffer.concat([enc, cipher.getAuthTag()]));\n }\n return Buffer.concat(out);\n}\n","'use strict';\n\nimport crypto from 'crypto';\nimport { base64url } from '../utils/base64url';\nimport { u16be, u32be } from '../utils/binary';\nimport { buildPlaintextRecords, encryptRecords } from './rfc8188';\n\nfunction hmacSha256(key: Buffer, data: Buffer): Buffer {\n return crypto.createHmac('sha256', key).update(data).digest();\n}\n\nfunction assertSubscriptionKeys(p256dh: string, auth: string): void {\n if (!p256dh || typeof p256dh !== 'string') throw new Error('subscription.keys.p256dh must be a string');\n if (!auth || typeof auth !== 'string') throw new Error('subscription.keys.auth must be a string');\n\n const p256dhBytes = base64url.toBuffer(p256dh);\n if (p256dhBytes.length !== 65 || p256dhBytes[0] !== 0x04) {\n throw new Error('subscription.keys.p256dh must decode to a 65-byte uncompressed P-256 public key');\n }\n\n const authBytes = base64url.toBuffer(auth);\n if (authBytes.length < 16) throw new Error('subscription.keys.auth must decode to at least 16 bytes');\n}\n\n/**\n * Encrypt a push payload using RFC8291 + RFC8188 with aes128gcm.\n *\n * Returns the full RFC8188 body:\n * salt(16) || rs(4) || idlen(1) || keyid(=sender pubkey) || encryptedRecords\n */\nexport function encryptAes128GcmBody(params: {\n p256dh: string;\n auth: string;\n payload: Buffer;\n rs: number;\n allowMultipleRecords: boolean;\n finalRecordPadding: number;\n}): Buffer {\n assertSubscriptionKeys(params.p256dh, params.auth);\n\n const uaPublic = base64url.toBuffer(params.p256dh);\n const authSecret = base64url.toBuffer(params.auth);\n\n // Sender ephemeral key\n const ecdh = crypto.createECDH('prime256v1');\n ecdh.generateKeys();\n const asPublic = ecdh.getPublicKey(); // 65 bytes\n const ecdhSecret = ecdh.computeSecret(uaPublic);\n\n // RFC8291: random salt\n const salt = crypto.randomBytes(16);\n\n // RFC8291: PRK_key = HMAC(auth_secret, ecdh_secret)\n const prkKey = hmacSha256(authSecret, ecdhSecret);\n\n // key_info = \"WebPush: info\" || 0x00 || ua_public || as_public\n const keyInfo = Buffer.concat([Buffer.from('WebPush: info', 'utf8'), Buffer.from([0x00]), uaPublic, asPublic]);\n\n // IKM = HMAC(PRK_key, key_info || 0x01)\n const ikm = hmacSha256(prkKey, Buffer.concat([keyInfo, Buffer.from([0x01])]));\n\n // RFC8188: PRK = HMAC(salt, IKM)\n const prk = hmacSha256(salt, ikm);\n\n // RFC8188: CEK / nonce derivation\n const cekInfo = Buffer.concat([Buffer.from('Content-Encoding: aes128gcm', 'utf8'), Buffer.from([0x00])]);\n const nonceInfo = Buffer.concat([Buffer.from('Content-Encoding: nonce', 'utf8'), Buffer.from([0x00])]);\n\n const cek = hmacSha256(prk, Buffer.concat([cekInfo, Buffer.from([0x01])])).subarray(0, 16);\n const baseNonce = hmacSha256(prk, Buffer.concat([nonceInfo, Buffer.from([0x01])])).subarray(0, 12);\n\n const plaintextRecords = buildPlaintextRecords({\n message: params.payload,\n rs: params.rs,\n allowMultipleRecords: params.allowMultipleRecords,\n finalRecordPadding: params.finalRecordPadding,\n });\n\n const encryptedRecords = encryptRecords({ cek, baseNonce, plaintextRecords });\n\n // RFC8188 header: salt || rs || idlen || keyid\n if (asPublic.length !== 65) throw new Error('Sender public key must be 65 bytes');\n\n return Buffer.concat([salt, u32be(params.rs), Buffer.from([asPublic.length]), asPublic, encryptedRecords]);\n}\n\n/**\n * Legacy aesgcm encryption for interop.\n * Returns ciphertext plus values needed for headers: Encryption (salt) and Crypto-Key (dh).\n */\nexport function encryptAesGcmLegacy(params: {\n p256dh: string;\n auth: string;\n payload: Buffer;\n}): { saltB64Url: string; localPublicKey: Buffer; ciphertext: Buffer } {\n assertSubscriptionKeys(params.p256dh, params.auth);\n\n const receiverPub = base64url.toBuffer(params.p256dh);\n const authSecret = base64url.toBuffer(params.auth);\n\n const ecdh = crypto.createECDH('prime256v1');\n ecdh.generateKeys();\n const senderPub = ecdh.getPublicKey();\n const ecdhSecret = ecdh.computeSecret(receiverPub);\n\n const salt = crypto.randomBytes(16);\n\n const prkKey = hmacSha256(authSecret, ecdhSecret);\n const keyInfo = Buffer.concat([Buffer.from('WebPush: info', 'utf8'), Buffer.from([0x00]), receiverPub, senderPub]);\n const ikm = hmacSha256(prkKey, Buffer.concat([keyInfo, Buffer.from([0x01])]));\n const prk = hmacSha256(salt, ikm);\n\n const cekInfo = Buffer.concat([Buffer.from('Content-Encoding: aesgcm', 'utf8'), Buffer.from([0x00])]);\n const nonceInfo = Buffer.concat([Buffer.from('Content-Encoding: nonce', 'utf8'), Buffer.from([0x00])]);\n\n const cek = hmacSha256(prk, Buffer.concat([cekInfo, Buffer.from([0x01])])).subarray(0, 16);\n const nonce = hmacSha256(prk, Buffer.concat([nonceInfo, Buffer.from([0x01])])).subarray(0, 12);\n\n const padLen = 0;\n const plaintext = Buffer.concat([u16be(padLen), Buffer.alloc(padLen, 0x00), params.payload]);\n\n const cipher = crypto.createCipheriv('aes-128-gcm', cek, nonce);\n const enc = Buffer.concat([cipher.update(plaintext), cipher.final()]);\n const tag = cipher.getAuthTag();\n\n return { saltB64Url: base64url.toString(salt), localPublicKey: senderPub, ciphertext: Buffer.concat([enc, tag]) };\n}\n","'use strict';\n\nimport {\n DEFAULT_RS,\n DEFAULT_TTL,\n MIN_RS,\n SupportedContentEncoding,\n SupportedUrgency,\n} from './constants';\nimport type {\n GenerateRequestOptions,\n PushSubscription,\n WebPushConfig,\n WebPushRequestDetails,\n} from './types';\nimport {base64url} from './utils/base64url';\nimport {encryptAes128GcmBody, encryptAesGcmLegacy} from './crypto/webpush-encryption';\nimport {GenerateHeaders, Validate} from \"./vapid\";\n\nexport class WebPushError extends Error {\n constructor(message: string, public readonly response: Response) {\n super(message);\n }\n}\n\nfunction validateTopic(topic: string): void {\n if (!base64url.validate(topic)) throw new Error('Topic contains invalid characters; must be URL-safe base64 chars.');\n if (topic.length > 32) throw new Error('Topic must be <= 32 characters.');\n}\n\nfunction isGcmEndpoint(endpoint: string): boolean {\n return endpoint.startsWith('https://android.googleapis.com/gcm/send');\n}\n\nfunction isFcmEndpoint(endpoint: string): boolean {\n return endpoint.startsWith('https://fcm.googleapis.com/fcm/send') || endpoint.includes('fcm.googleapis.com');\n}\n\nfunction getAudienceFromEndpoint(endpoint: string): string {\n return new URL(endpoint).origin;\n}\n\nfunction assertSupportedEncoding(e: SupportedContentEncoding): void {\n if (!Object.values(SupportedContentEncoding).includes(e)) throw new Error(`Unsupported content encoding: ${e}`);\n}\n\nfunction assertSupportedUrgency(u: SupportedUrgency): void {\n if (!Object.values(SupportedUrgency).includes(u)) throw new Error(`Unsupported urgency: ${u}`);\n}\n\nexport class WebPush {\n constructor(public readonly config: WebPushConfig) {\n Validate.subject(config.vapid.subject);\n Validate.privateKey(config.vapid.privateKey);\n Validate.publicKey(config.vapid.publicKey);\n\n if (config.gcm?.apiKey != null && config.gcm.apiKey.length === 0) {\n throw new Error('The GCM/FCM API Key should be a non-empty string, null, or undefined.');\n }\n }\n\n /**\n * Generate the request (endpoint + fetch init) to send a push message.\n *\n * - For `aes128gcm`, this produces an RFC8188 payload body (header block + encrypted records).\n * - For Web Push, defaults to a single record per RFC8291; multi-record requires `allowMultipleRecords: true`.\n */\n generateRequest(\n subscription: PushSubscription,\n payload?: string | Buffer | Uint8Array | null,\n options?: GenerateRequestOptions\n ): WebPushRequestDetails {\n if (!subscription?.endpoint || typeof subscription.endpoint !== 'string' || subscription.endpoint.length === 0) {\n throw new Error('You must pass a subscription with a valid endpoint URL.');\n }\n\n const timeToLive = options?.TTL ?? DEFAULT_TTL;\n if (!Number.isFinite(timeToLive) || timeToLive < 0) throw new Error('TTL must be a number >= 0.');\n\n const contentEncoding = options?.contentEncoding ?? SupportedContentEncoding.AES_128_GCM;\n assertSupportedEncoding(contentEncoding);\n\n const urgency = options?.urgency ?? SupportedUrgency.NORMAL;\n assertSupportedUrgency(urgency);\n\n const topic = options?.topic;\n if (topic) validateTopic(topic);\n\n const allowMultipleRecords = options?.allowMultipleRecords ?? false;\n const rs = options?.rs ?? DEFAULT_RS;\n if (!Number.isInteger(rs) || rs < MIN_RS) throw new Error(`rs must be an integer >= ${MIN_RS}.`);\n\n const finalRecordPadding = options?.finalRecordPadding ?? 0;\n\n const extraHeaders = options?.headers ?? {};\n const currentGcmKey = options?.gcmAPIKey ?? this.config.gcm?.apiKey ?? null;\n\n const vapidDetails = options?.vapidDetails === undefined ? this.config.vapid : options.vapidDetails;\n\n const headers: Record<string, string> = {\n ...extraHeaders,\n // Keep protocol-critical defaults authoritative even when custom headers are supplied.\n TTL: String(timeToLive),\n Urgency: urgency,\n };\n\n if (topic) headers.Topic = topic;\n\n let body: Buffer | undefined;\n\n const hasPayload = payload != null && payload !== '';\n if (hasPayload) {\n if (!subscription.keys?.p256dh || !subscription.keys?.auth) {\n throw new Error(\"To send a payload, the subscription must include 'keys.p256dh' and 'keys.auth'.\");\n }\n\n const payloadBuf =\n Buffer.isBuffer(payload) ? payload :\n payload instanceof Uint8Array ? Buffer.from(payload) :\n Buffer.from(payload, 'utf8');\n\n headers['Content-Type'] = 'application/octet-stream';\n\n if (contentEncoding === SupportedContentEncoding.AES_128_GCM) {\n const encBody = encryptAes128GcmBody({\n p256dh: subscription.keys.p256dh,\n auth: subscription.keys.auth,\n payload: payloadBuf,\n rs,\n allowMultipleRecords,\n finalRecordPadding,\n });\n\n body = encBody;\n headers['Content-Encoding'] = SupportedContentEncoding.AES_128_GCM;\n headers['Content-Length'] = String(encBody.length);\n } else {\n const enc = encryptAesGcmLegacy({\n p256dh: subscription.keys.p256dh,\n auth: subscription.keys.auth,\n payload: payloadBuf,\n });\n\n body = enc.ciphertext;\n headers['Content-Encoding'] = SupportedContentEncoding.AES_GCM;\n headers['Content-Length'] = String(enc.ciphertext.length);\n headers['Encryption'] = `salt=${enc.saltB64Url}`;\n headers['Crypto-Key'] = `dh=${base64url.toString(enc.localPublicKey)}`;\n }\n } else {\n headers['Content-Length'] = '0';\n }\n\n // Auth: GCM/FCM key OR VAPID\n const endpoint = subscription.endpoint;\n const gcm = isGcmEndpoint(endpoint);\n const fcm = isFcmEndpoint(endpoint);\n\n if (gcm) {\n if (currentGcmKey) headers.Authorization = `key=${currentGcmKey}`;\n else console.warn('GCM endpoint detected but no GCM API key provided.');\n } else if (vapidDetails) {\n const audience = getAudienceFromEndpoint(endpoint);\n const vapidHeaders = GenerateHeaders({\n audience,\n subject: vapidDetails.subject,\n publicKey: vapidDetails.publicKey,\n privateKey: vapidDetails.privateKey,\n contentEncoding,\n });\n\n const auth = vapidHeaders.get('Authorization');\n if (auth) headers.Authorization = auth;\n\n const ck = vapidHeaders.get('Crypto-Key');\n if (ck && contentEncoding === SupportedContentEncoding.AES_GCM) {\n headers['Crypto-Key'] = headers['Crypto-Key'] ? `${headers['Crypto-Key']}; ${ck}` : ck;\n }\n } else if (fcm && currentGcmKey) {\n headers.Authorization = `key=${currentGcmKey}`;\n }\n\n const init: RequestInit = {\n method: 'POST',\n headers,\n body: body as any,\n };\n\n return {endpoint, init};\n }\n\n /**\n * Method to send notification to subscribed device\n * @param subscription\n * @param payload\n * @param options\n */\n async notify(\n subscription: PushSubscription,\n payload?: string | Buffer | Uint8Array | null,\n options?: GenerateRequestOptions & { throwOnInvalidResponse?: boolean },\n ): Promise<Response> {\n const {endpoint, init} = this.generateRequest(subscription, payload, options);\n const res = await fetch(endpoint, init);\n if (!res.ok && options?.throwOnInvalidResponse) throw new WebPushError('Received unexpected response code', res);\n return res;\n }\n}\n"],"names":["SupportedContentEncoding","SupportedUrgency","DEFAULT_TTL","DEFAULT_RS","MIN_RS","base64url","input","obj","DEFAULT_EXPIRATION_SECONDS","MAX_EXPIRATION_SECONDS","getFutureExpirationTimestamp","numSeconds","future","Validate","subject","u","publicKey","pk","privateKey","expiration","maxExp","audience","a","GenerateKeys","ecdh","crypto","pub","priv","publicKeyToJwk","publicKeyB64Url","privateKeyToJwk","privateKeyB64Url","d","derToJose","derSig","keySizeBytes","off","seqLen","n","rLen","r","sLen","rPad","sPad","signJwtES256","payload","header","encodedHeader","encodedPayload","signingInput","x","y","keyObject","joseSig","encodedSig","GenerateHeaders","exp","jwt","headers","u16be","b","u32be","xorBytes","out","i","seq96be","seq","hi","lo","buildPlaintextRecords","params","message","rs","allowMultipleRecords","finalRecordPadding","maxPlain","maxDataPerFullRecord","records","offset","remaining","isLast","dataLen","data","delimiter","paddingLen","encryptRecords","cek","baseNonce","plaintextRecords","nonce","cipher","enc","hmacSha256","key","assertSubscriptionKeys","p256dh","auth","p256dhBytes","encryptAes128GcmBody","uaPublic","authSecret","asPublic","ecdhSecret","salt","prkKey","keyInfo","ikm","prk","cekInfo","nonceInfo","encryptedRecords","encryptAesGcmLegacy","receiverPub","senderPub","padLen","plaintext","tag","WebPushError","response","validateTopic","topic","isGcmEndpoint","endpoint","isFcmEndpoint","getAudienceFromEndpoint","assertSupportedEncoding","assertSupportedUrgency","WebPush","config","subscription","options","timeToLive","contentEncoding","urgency","extraHeaders","currentGcmKey","vapidDetails","body","payloadBuf","encBody","gcm","fcm","vapidHeaders","ck","init","res"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs.js","names":[],"sources":["../src/constants.ts","../src/utils/base64url.ts","../src/vapid.ts","../src/utils/binary.ts","../src/crypto/rfc8188.ts","../src/crypto/webpush-encryption.ts","../src/webpush.ts"],"sourcesContent":["'use strict';\n\nexport enum SupportedContentEncoding {\n AES_128_GCM = 'aes128gcm',\n}\n\nexport enum SupportedUrgency {\n VERY_LOW = 'very-low',\n LOW = 'low',\n NORMAL = 'normal',\n HIGH = 'high',\n}\n\n/** Default TTL is 4 weeks (seconds). */\nexport const DEFAULT_TTL = 2419200;\n\n/**\n * Default record size (RFC8188 `rs`) for aes128gcm.\n * For Web Push (RFC8291), a single record is required, but `rs` still appears in the body header.\n */\nexport const DEFAULT_RS = 4096;\n\n/** Minimum valid `rs` for aes128gcm per RFC8188 (must be >= 18). */\nexport const MIN_RS = 18;\n","'use strict';\n\nexport const base64url = {\n validate(input: string): boolean {\n // URL-safe base64 without \"=\" padding\n return /^[A-Za-z0-9\\-_]+$/.test(input);\n },\n toBuffer(input: string): Buffer {\n return Buffer.from(input, 'base64url');\n },\n toString(input: Buffer | Uint8Array | string): string {\n const buf = typeof input === 'string' ? Buffer.from(input, 'utf8') : Buffer.from(input);\n return buf.toString('base64url');\n },\n toJSON(obj: any) {\n return this.toString(Buffer.from(JSON.stringify(obj), 'utf8'));\n }\n};\n","'use strict';\n\nimport crypto from 'crypto';\nimport {base64url} from './utils/base64url';\nimport {SupportedContentEncoding} from './constants';\n\nconst DEFAULT_EXPIRATION_SECONDS = 12 * 60 * 60;\nconst MAX_EXPIRATION_SECONDS = 24 * 60 * 60;\n\nfunction getFutureExpirationTimestamp(numSeconds: number): number {\n const future = new Date();\n future.setSeconds(future.getSeconds() + numSeconds);\n return Math.floor(future.getTime() / 1000);\n}\n\nexport const Validate = {\n subject(subject: string | URL): void {\n if (!subject) throw new Error('No subject set in VAPID details.');\n const u = typeof subject === 'string' ? new URL(subject) : subject;\n if (!['https:', 'mailto:'].includes(u.protocol)) {\n throw new Error(`VAPID subject must be https: or mailto:. Got: ${u.toString()}`);\n }\n if (u.hostname === 'localhost') {\n console.warn('VAPID subject points to localhost; some push services may reject this.');\n }\n },\n\n publicKey(publicKey: string): void {\n if (!publicKey) throw new Error('No VAPID public key set.');\n if (typeof publicKey !== 'string' || !base64url.validate(publicKey)) {\n throw new Error('VAPID public key must be base64url without \"=\" padding.');\n }\n const pk = base64url.toBuffer(publicKey);\n if (pk.length !== 65 || pk[0] !== 0x04) {\n throw new Error('VAPID public key must decode to 65-byte uncompressed P-256.');\n }\n },\n\n privateKey(privateKey: string): void {\n if (!privateKey) throw new Error('No VAPID private key set.');\n if (typeof privateKey !== 'string' || !base64url.validate(privateKey)) {\n throw new Error('VAPID private key must be base64url without \"=\" padding.');\n }\n const sk = base64url.toBuffer(privateKey);\n if (sk.length !== 32) throw new Error('VAPID private key must decode to 32 bytes.');\n },\n\n expiration(expiration: number): void {\n if (!Number.isInteger(expiration)) throw new Error('VAPID expiration must be an integer.');\n if (expiration < 0) throw new Error('VAPID expiration must be positive.');\n const maxExp = getFutureExpirationTimestamp(MAX_EXPIRATION_SECONDS);\n if (expiration >= maxExp) throw new Error('VAPID expiration exceeds 24 hours.');\n },\n\n audience(audience: string | URL): void {\n if (!audience) throw new Error('No audience set for VAPID.');\n const a = typeof audience === 'string' ? new URL(audience) : audience;\n if (!a.origin || a.origin === 'null') {\n throw new Error(`VAPID audience must be an origin. Got: ${a.toString()}`);\n }\n },\n};\n\nexport function GenerateKeys(): { publicKey: string; privateKey: string } {\n const ecdh = crypto.createECDH('prime256v1');\n ecdh.generateKeys();\n\n let pub = ecdh.getPublicKey();\n let priv = ecdh.getPrivateKey();\n\n if (priv.length < 32) priv = Buffer.concat([Buffer.alloc(32 - priv.length, 0x00), priv]);\n if (pub.length < 65) pub = Buffer.concat([Buffer.alloc(65 - pub.length, 0x00), pub]);\n\n return {publicKey: base64url.toString(pub), privateKey: base64url.toString(priv)};\n}\n\nfunction publicKeyToJwk(publicKeyB64Url: string): { x: string; y: string } {\n const pub = base64url.toBuffer(publicKeyB64Url);\n if (pub.length !== 65 || pub[0] !== 0x04) throw new Error('Invalid uncompressed P-256 public key.');\n return {x: base64url.toString(pub.subarray(1, 33)), y: base64url.toString(pub.subarray(33, 65))};\n}\n\nfunction privateKeyToJwk(privateKeyB64Url: string): string {\n const d = base64url.toBuffer(privateKeyB64Url);\n if (d.length !== 32) throw new Error('Invalid P-256 private key scalar.');\n return base64url.toString(d);\n}\n\nfunction derToJose(derSig: Buffer, keySizeBytes = 32): Buffer {\n let off = 0;\n if (derSig[off++] !== 0x30) throw new Error('Invalid DER signature (expected SEQUENCE).');\n\n let seqLen = derSig[off++];\n if (seqLen & 0x80) {\n const n = seqLen & 0x7f;\n off += n;\n }\n\n if (derSig[off++] !== 0x02) throw new Error('Invalid DER signature (expected INTEGER r).');\n const rLen = derSig[off++];\n let r = derSig.subarray(off, off + rLen);\n off += rLen;\n\n if (derSig[off++] !== 0x02) throw new Error('Invalid DER signature (expected INTEGER s).');\n const sLen = derSig[off++];\n let s = derSig.subarray(off, off + sLen);\n\n if (r[0] === 0x00) r = r.subarray(1);\n if (s[0] === 0x00) s = s.subarray(1);\n\n if (r.length > keySizeBytes || s.length > keySizeBytes) throw new Error('Invalid DER signature size.');\n const rPad = Buffer.concat([Buffer.alloc(keySizeBytes - r.length, 0x00), r]);\n const sPad = Buffer.concat([Buffer.alloc(keySizeBytes - s.length, 0x00), s]);\n return Buffer.concat([rPad, sPad]);\n}\n\nfunction signJwtES256(payload: Record<string, unknown>, privateKeyB64Url: string, publicKeyB64Url: string): string {\n const header = {typ: 'JWT', alg: 'ES256'};\n const encodedHeader = base64url.toJSON(header);\n const encodedPayload = base64url.toJSON(payload);\n const signingInput = `${encodedHeader}.${encodedPayload}`;\n\n const {x, y} = publicKeyToJwk(publicKeyB64Url);\n const d = privateKeyToJwk(privateKeyB64Url);\n\n const keyObject = crypto.createPrivateKey({\n key: {kty: 'EC', crv: 'P-256', x, y, d},\n format: 'jwk',\n });\n\n const derSig = crypto.sign('sha256', Buffer.from(signingInput, 'utf8'), {\n key: keyObject,\n dsaEncoding: 'der',\n });\n\n const joseSig = derToJose(derSig, 32);\n const encodedSig = base64url.toString(joseSig);\n return `${signingInput}.${encodedSig}`;\n}\n\nexport function GenerateHeaders(input: {\n audience: string | URL;\n subject: string | URL;\n publicKey: string;\n privateKey: string;\n contentEncoding: SupportedContentEncoding;\n expiration?: number;\n}): Headers {\n Validate.audience(input.audience);\n Validate.subject(input.subject);\n Validate.publicKey(input.publicKey);\n Validate.privateKey(input.privateKey);\n\n const audience = (typeof input.audience === 'string' ? new URL(input.audience) : input.audience).origin;\n const subject = (typeof input.subject === 'string' ? new URL(input.subject) : input.subject).toString();\n\n const exp = input.expiration ?? getFutureExpirationTimestamp(DEFAULT_EXPIRATION_SECONDS);\n Validate.expiration(exp);\n\n const jwt = signJwtES256({aud: audience, exp, sub: subject}, input.privateKey, input.publicKey);\n\n const headers = new Headers();\n if (input.contentEncoding !== SupportedContentEncoding.AES_128_GCM) {\n throw new Error(`Unsupported content encoding: ${input.contentEncoding}`);\n }\n headers.set('Authorization', `vapid t=${jwt}, k=${input.publicKey}`);\n return headers;\n}\n\n","'use strict';\n\n/** Write UInt16BE. */\nexport function u16be(n: number): Buffer {\n const b = Buffer.alloc(2);\n b.writeUInt16BE(n, 0);\n return b;\n}\n\n/** Write UInt32BE. */\nexport function u32be(n: number): Buffer {\n const b = Buffer.alloc(4);\n b.writeUInt32BE(n >>> 0, 0);\n return b;\n}\n\nexport function xorBytes(a: Buffer, b: Buffer): Buffer {\n if (a.length !== b.length) throw new Error('xorBytes: length mismatch');\n const out = Buffer.alloc(a.length);\n for (let i = 0; i < a.length; i++) out[i] = a[i] ^ b[i];\n return out;\n}\n\n/**\n * Convert a record sequence number into a 96-bit (12-octet) big-endian buffer.\n * RFC8188 defines SEQ as a 96-bit unsigned integer starting at 0.\n */\nexport function seq96be(seq: number): Buffer {\n if (!Number.isSafeInteger(seq) || seq < 0) throw new Error('SEQ must be a non-negative safe integer');\n const out = Buffer.alloc(12, 0x00);\n\n // Write into low 64 bits; high 32 bits remain zero.\n const hi = Math.floor(seq / 0x100000000);\n const lo = seq >>> 0;\n out.writeUInt32BE(hi >>> 0, 4);\n out.writeUInt32BE(lo, 8);\n return out;\n}\n","'use strict';\n\nimport crypto from 'crypto';\nimport { MIN_RS } from '../constants';\nimport { xorBytes, seq96be } from '../utils/binary';\n\n/**\n * Build RFC8188 plaintext records from message bytes.\n *\n * Record plaintext length for full records is `rs - 16` because AES-GCM adds a 16-byte tag.\n * The padding delimiter must be present in every record:\n * - 0x01 for all non-last records\n * - 0x02 for the last record\n */\nexport function buildPlaintextRecords(params: {\n message: Buffer;\n rs: number;\n allowMultipleRecords: boolean;\n finalRecordPadding: number;\n}): Buffer[] {\n const { message, rs, allowMultipleRecords, finalRecordPadding } = params;\n\n if (!Number.isInteger(rs) || rs < MIN_RS) {\n throw new Error(`Invalid rs; must be an integer >= ${MIN_RS}.`);\n }\n\n const maxPlain = rs - 16; // AES-GCM tag length is 16\n if (maxPlain <= 1) throw new Error('Invalid rs (too small)');\n\n const maxDataPerFullRecord = maxPlain - 1; // reserve delimiter\n\n if (!allowMultipleRecords && message.length > maxDataPerFullRecord) {\n throw new Error(\n `Payload too large for a single RFC8188 record: ${message.length} bytes. ` +\n `Max is ${maxDataPerFullRecord} bytes for rs=${rs}.`\n );\n }\n\n if (!Number.isInteger(finalRecordPadding) || finalRecordPadding < 0) {\n throw new Error('finalRecordPadding must be a non-negative integer');\n }\n\n const records: Buffer[] = [];\n let offset = 0;\n\n while (offset < message.length) {\n const remaining = message.length - offset;\n const isLast = remaining <= maxDataPerFullRecord;\n\n const dataLen = isLast ? remaining : maxDataPerFullRecord;\n const data = message.subarray(offset, offset + dataLen);\n offset += dataLen;\n\n const delimiter = Buffer.from([isLast ? 0x02 : 0x01]);\n\n if (!isLast) {\n // Full-sized non-last record.\n const paddingLen = maxPlain - (data.length + 1);\n records.push(Buffer.concat([data, delimiter, Buffer.alloc(paddingLen, 0x00)]));\n } else {\n // Last record can be partial and may include padding after delimiter.\n records.push(Buffer.concat([data, delimiter, Buffer.alloc(finalRecordPadding, 0x00)]));\n }\n }\n\n // Empty message -> final record with only delimiter (0x02)\n if (message.length === 0) records.push(Buffer.from([0x02]));\n\n return records;\n}\n\n/**\n * Encrypt RFC8188 plaintext records using AES-128-GCM.\n *\n * Nonce per record: nonce = baseNonce XOR SEQ (96-bit big-endian SEQ starting at 0).\n * AAD is empty.\n */\nexport function encryptRecords(params: {\n cek: Buffer; // 16 bytes\n baseNonce: Buffer; // 12 bytes\n plaintextRecords: Buffer[];\n}): Buffer {\n const { cek, baseNonce, plaintextRecords } = params;\n if (cek.length !== 16) throw new Error('CEK must be 16 bytes');\n if (baseNonce.length !== 12) throw new Error('baseNonce must be 12 bytes');\n\n const out: Buffer[] = [];\n for (let i = 0; i < plaintextRecords.length; i++) {\n const nonce = xorBytes(baseNonce, seq96be(i));\n const cipher = crypto.createCipheriv('aes-128-gcm', cek, nonce);\n\n const enc = Buffer.concat([cipher.update(plaintextRecords[i]), cipher.final()]);\n out.push(Buffer.concat([enc, cipher.getAuthTag()]));\n }\n return Buffer.concat(out);\n}\n","'use strict';\n\nimport crypto from 'crypto';\nimport { base64url } from '../utils/base64url';\nimport { u32be } from '../utils/binary';\nimport { buildPlaintextRecords, encryptRecords } from './rfc8188';\n\nfunction hmacSha256(key: Buffer, data: Buffer): Buffer {\n return crypto.createHmac('sha256', key).update(data).digest();\n}\n\nfunction assertSubscriptionKeys(p256dh: string, auth: string): void {\n if (!p256dh || typeof p256dh !== 'string') throw new Error('subscription.keys.p256dh must be a string');\n if (!auth || typeof auth !== 'string') throw new Error('subscription.keys.auth must be a string');\n\n const p256dhBytes = base64url.toBuffer(p256dh);\n if (p256dhBytes.length !== 65 || p256dhBytes[0] !== 0x04) {\n throw new Error('subscription.keys.p256dh must decode to a 65-byte uncompressed P-256 public key');\n }\n\n const authBytes = base64url.toBuffer(auth);\n if (authBytes.length < 16) throw new Error('subscription.keys.auth must decode to at least 16 bytes');\n}\n\n/**\n * Encrypt a push payload using RFC8291 + RFC8188 with aes128gcm.\n *\n * Returns the full RFC8188 body:\n * salt(16) || rs(4) || idlen(1) || keyid(=sender pubkey) || encryptedRecords\n */\nexport function encryptAes128GcmBody(params: {\n p256dh: string;\n auth: string;\n payload: Buffer;\n rs: number;\n allowMultipleRecords: boolean;\n finalRecordPadding: number;\n}): Buffer {\n assertSubscriptionKeys(params.p256dh, params.auth);\n\n const uaPublic = base64url.toBuffer(params.p256dh);\n const authSecret = base64url.toBuffer(params.auth);\n\n // Sender ephemeral key\n const ecdh = crypto.createECDH('prime256v1');\n ecdh.generateKeys();\n const asPublic = ecdh.getPublicKey(); // 65 bytes\n const ecdhSecret = ecdh.computeSecret(uaPublic);\n\n // RFC8291: random salt\n const salt = crypto.randomBytes(16);\n\n // RFC8291: PRK_key = HMAC(auth_secret, ecdh_secret)\n const prkKey = hmacSha256(authSecret, ecdhSecret);\n\n // key_info = \"WebPush: info\" || 0x00 || ua_public || as_public\n const keyInfo = Buffer.concat([Buffer.from('WebPush: info', 'utf8'), Buffer.from([0x00]), uaPublic, asPublic]);\n\n // IKM = HMAC(PRK_key, key_info || 0x01)\n const ikm = hmacSha256(prkKey, Buffer.concat([keyInfo, Buffer.from([0x01])]));\n\n // RFC8188: PRK = HMAC(salt, IKM)\n const prk = hmacSha256(salt, ikm);\n\n // RFC8188: CEK / nonce derivation\n const cekInfo = Buffer.concat([Buffer.from('Content-Encoding: aes128gcm', 'utf8'), Buffer.from([0x00])]);\n const nonceInfo = Buffer.concat([Buffer.from('Content-Encoding: nonce', 'utf8'), Buffer.from([0x00])]);\n\n const cek = hmacSha256(prk, Buffer.concat([cekInfo, Buffer.from([0x01])])).subarray(0, 16);\n const baseNonce = hmacSha256(prk, Buffer.concat([nonceInfo, Buffer.from([0x01])])).subarray(0, 12);\n\n const plaintextRecords = buildPlaintextRecords({\n message: params.payload,\n rs: params.rs,\n allowMultipleRecords: params.allowMultipleRecords,\n finalRecordPadding: params.finalRecordPadding,\n });\n\n const encryptedRecords = encryptRecords({ cek, baseNonce, plaintextRecords });\n\n // RFC8188 header: salt || rs || idlen || keyid\n if (asPublic.length !== 65) throw new Error('Sender public key must be 65 bytes');\n\n return Buffer.concat([salt, u32be(params.rs), Buffer.from([asPublic.length]), asPublic, encryptedRecords]);\n}\n","'use strict';\n\nimport {\n DEFAULT_RS,\n DEFAULT_TTL,\n MIN_RS,\n SupportedContentEncoding,\n SupportedUrgency,\n} from './constants';\nimport type {\n GenerateRequestOptions,\n PushSubscription,\n WebPushConfig,\n WebPushRequestDetails,\n} from './types';\nimport {base64url} from './utils/base64url';\nimport {encryptAes128GcmBody} from './crypto/webpush-encryption';\nimport {GenerateHeaders, Validate} from \"./vapid\";\n\nexport class WebPushError extends Error {\n constructor(message: string, public readonly response: Response) {\n super(message);\n }\n}\n\nfunction validateTopic(topic: string): void {\n if (!base64url.validate(topic)) throw new Error('Topic contains invalid characters; must be URL-safe base64 chars.');\n if (topic.length > 32) throw new Error('Topic must be <= 32 characters.');\n}\n\nfunction isGcmEndpoint(endpoint: string): boolean {\n return endpoint.startsWith('https://android.googleapis.com/gcm/send');\n}\n\nfunction isFcmEndpoint(endpoint: string): boolean {\n return endpoint.startsWith('https://fcm.googleapis.com/fcm/send') || endpoint.includes('fcm.googleapis.com');\n}\n\nfunction getAudienceFromEndpoint(endpoint: string): string {\n return new URL(endpoint).origin;\n}\n\nfunction assertSupportedEncoding(e: SupportedContentEncoding): void {\n if (!Object.values(SupportedContentEncoding).includes(e)) throw new Error(`Unsupported content encoding: ${e}`);\n}\n\nfunction assertSupportedUrgency(u: SupportedUrgency): void {\n if (!Object.values(SupportedUrgency).includes(u)) throw new Error(`Unsupported urgency: ${u}`);\n}\n\nexport class WebPush {\n constructor(public readonly config: WebPushConfig) {\n Validate.subject(config.vapid.subject);\n Validate.privateKey(config.vapid.privateKey);\n Validate.publicKey(config.vapid.publicKey);\n\n if (config.gcm?.apiKey != null && config.gcm.apiKey.length === 0) {\n throw new Error('The GCM/FCM API Key should be a non-empty string, null, or undefined.');\n }\n }\n\n /**\n * Generate the request (endpoint + fetch init) to send a push message.\n *\n * - For `aes128gcm`, this produces an RFC8188 payload body (header block + encrypted records).\n * - For Web Push, defaults to a single record per RFC8291; multi-record requires `allowMultipleRecords: true`.\n */\n generateRequest(\n subscription: PushSubscription,\n payload?: string | Buffer | Uint8Array | null,\n options?: GenerateRequestOptions\n ): WebPushRequestDetails {\n if (!subscription?.endpoint || typeof subscription.endpoint !== 'string' || subscription.endpoint.length === 0) {\n throw new Error('You must pass a subscription with a valid endpoint URL.');\n }\n\n const timeToLive = options?.TTL ?? DEFAULT_TTL;\n if (!Number.isFinite(timeToLive) || timeToLive < 0) throw new Error('TTL must be a number >= 0.');\n\n const contentEncoding = options?.contentEncoding ?? SupportedContentEncoding.AES_128_GCM;\n assertSupportedEncoding(contentEncoding);\n\n const urgency = options?.urgency ?? SupportedUrgency.NORMAL;\n assertSupportedUrgency(urgency);\n\n const topic = options?.topic;\n if (topic) validateTopic(topic);\n\n const allowMultipleRecords = options?.allowMultipleRecords ?? false;\n const rs = options?.rs ?? DEFAULT_RS;\n if (!Number.isInteger(rs) || rs < MIN_RS) throw new Error(`rs must be an integer >= ${MIN_RS}.`);\n\n const finalRecordPadding = options?.finalRecordPadding ?? 0;\n\n const extraHeaders = options?.headers ?? {};\n const currentGcmKey = options?.gcmAPIKey ?? this.config.gcm?.apiKey ?? null;\n\n const vapidDetails = options?.vapidDetails === undefined ? this.config.vapid : options.vapidDetails;\n\n const headers: Record<string, string> = {\n ...extraHeaders,\n // Keep protocol-critical defaults authoritative even when custom headers are supplied.\n TTL: String(timeToLive),\n Urgency: urgency,\n };\n\n if (topic) headers.Topic = topic;\n\n let body: Buffer | undefined;\n\n const hasPayload = payload != null && payload !== '';\n if (hasPayload) {\n if (!subscription.keys?.p256dh || !subscription.keys?.auth) {\n throw new Error(\"To send a payload, the subscription must include 'keys.p256dh' and 'keys.auth'.\");\n }\n\n const payloadBuf =\n Buffer.isBuffer(payload) ? payload :\n payload instanceof Uint8Array ? Buffer.from(payload) :\n Buffer.from(payload, 'utf8');\n\n headers['Content-Type'] = 'application/octet-stream';\n\n const encBody = encryptAes128GcmBody({\n p256dh: subscription.keys.p256dh,\n auth: subscription.keys.auth,\n payload: payloadBuf,\n rs,\n allowMultipleRecords,\n finalRecordPadding,\n });\n\n body = encBody;\n headers['Content-Encoding'] = SupportedContentEncoding.AES_128_GCM;\n headers['Content-Length'] = String(encBody.length);\n } else {\n headers['Content-Length'] = '0';\n }\n\n // Auth: GCM/FCM key OR VAPID\n const endpoint = subscription.endpoint;\n const gcm = isGcmEndpoint(endpoint);\n const fcm = isFcmEndpoint(endpoint);\n\n if (gcm) {\n if (currentGcmKey) headers.Authorization = `key=${currentGcmKey}`;\n else console.warn('GCM endpoint detected but no GCM API key provided.');\n } else if (vapidDetails) {\n const audience = getAudienceFromEndpoint(endpoint);\n const vapidHeaders = GenerateHeaders({\n audience,\n subject: vapidDetails.subject,\n publicKey: vapidDetails.publicKey,\n privateKey: vapidDetails.privateKey,\n contentEncoding,\n });\n\n const auth = vapidHeaders.get('Authorization');\n if (auth) headers.Authorization = auth;\n } else if (fcm && currentGcmKey) {\n headers.Authorization = `key=${currentGcmKey}`;\n }\n\n const init: RequestInit = {\n method: 'POST',\n headers,\n body: body as any,\n };\n\n return {endpoint, init};\n }\n\n /**\n * Method to send notification to subscribed device\n * @param subscription\n * @param payload\n * @param options\n */\n async notify(\n subscription: PushSubscription,\n payload?: string | Buffer | Uint8Array | null,\n options?: GenerateRequestOptions & { throwOnInvalidResponse?: boolean },\n ): Promise<Response> {\n const {endpoint, init} = this.generateRequest(subscription, payload, options);\n const res = await fetch(endpoint, init);\n if (!res.ok && options?.throwOnInvalidResponse) throw new WebPushError('Received unexpected response code', res);\n return res;\n }\n}\n"],"mappings":"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"}
package/dist/index.es.js CHANGED
@@ -1,331 +1,326 @@
1
- import m from "crypto";
2
- var b = /* @__PURE__ */ ((e) => (e.AES_GCM = "aesgcm", e.AES_128_GCM = "aes128gcm", e))(b || {}), R = /* @__PURE__ */ ((e) => (e.VERY_LOW = "very-low", e.LOW = "low", e.NORMAL = "normal", e.HIGH = "high", e))(R || {});
3
- const L = 2419200, N = 4096, k = 18, f = {
4
- validate(e) {
5
- return /^[A-Za-z0-9\-_]+$/.test(e);
6
- },
7
- toBuffer(e) {
8
- return Buffer.from(e, "base64url");
9
- },
10
- toString(e) {
11
- return (typeof e == "string" ? Buffer.from(e, "utf8") : Buffer.from(e)).toString("base64url");
12
- },
13
- toJSON(e) {
14
- return this.toString(Buffer.from(JSON.stringify(e), "utf8"));
15
- }
16
- }, _ = 720 * 60, M = 1440 * 60;
17
- function T(e) {
18
- const t = /* @__PURE__ */ new Date();
19
- return t.setSeconds(t.getSeconds() + e), Math.floor(t.getTime() / 1e3);
1
+ import e from "crypto";
2
+ //#region \0rolldown/runtime.js
3
+ var t = Object.defineProperty, n = (e, n) => {
4
+ let r = {};
5
+ for (var i in e) t(r, i, {
6
+ get: e[i],
7
+ enumerable: !0
8
+ });
9
+ return n || t(r, Symbol.toStringTag, { value: "Module" }), r;
10
+ }, r = /* @__PURE__ */ function(e) {
11
+ return e.AES_128_GCM = "aes128gcm", e;
12
+ }({}), i = /* @__PURE__ */ function(e) {
13
+ return e.VERY_LOW = "very-low", e.LOW = "low", e.NORMAL = "normal", e.HIGH = "high", e;
14
+ }({}), a = 2419200, o = 4096, s = 18, c = {
15
+ validate(e) {
16
+ return /^[A-Za-z0-9\-_]+$/.test(e);
17
+ },
18
+ toBuffer(e) {
19
+ return Buffer.from(e, "base64url");
20
+ },
21
+ toString(e) {
22
+ return (typeof e == "string" ? Buffer.from(e, "utf8") : Buffer.from(e)).toString("base64url");
23
+ },
24
+ toJSON(e) {
25
+ return this.toString(Buffer.from(JSON.stringify(e), "utf8"));
26
+ }
27
+ }, l = /* @__PURE__ */ n({
28
+ GenerateHeaders: () => y,
29
+ GenerateKeys: () => m,
30
+ Validate: () => p
31
+ }), u = 43200, d = 86400;
32
+ function f(e) {
33
+ let t = /* @__PURE__ */ new Date();
34
+ return t.setSeconds(t.getSeconds() + e), Math.floor(t.getTime() / 1e3);
20
35
  }
21
- const E = {
22
- subject(e) {
23
- if (!e) throw new Error("No subject set in VAPID details.");
24
- const t = typeof e == "string" ? new URL(e) : e;
25
- if (!["https:", "mailto:"].includes(t.protocol))
26
- throw new Error(`VAPID subject must be https: or mailto:. Got: ${t.toString()}`);
27
- t.hostname === "localhost" && console.warn("VAPID subject points to localhost; some push services may reject this.");
28
- },
29
- publicKey(e) {
30
- if (!e) throw new Error("No VAPID public key set.");
31
- if (typeof e != "string" || !f.validate(e))
32
- throw new Error('VAPID public key must be base64url without "=" padding.');
33
- const t = f.toBuffer(e);
34
- if (t.length !== 65 || t[0] !== 4)
35
- throw new Error("VAPID public key must decode to 65-byte uncompressed P-256.");
36
- },
37
- privateKey(e) {
38
- if (!e) throw new Error("No VAPID private key set.");
39
- if (typeof e != "string" || !f.validate(e))
40
- throw new Error('VAPID private key must be base64url without "=" padding.');
41
- if (f.toBuffer(e).length !== 32) throw new Error("VAPID private key must decode to 32 bytes.");
42
- },
43
- expiration(e) {
44
- if (!Number.isInteger(e)) throw new Error("VAPID expiration must be an integer.");
45
- if (e < 0) throw new Error("VAPID expiration must be positive.");
46
- const t = T(M);
47
- if (e >= t) throw new Error("VAPID expiration exceeds 24 hours.");
48
- },
49
- audience(e) {
50
- if (!e) throw new Error("No audience set for VAPID.");
51
- const t = typeof e == "string" ? new URL(e) : e;
52
- if (!t.origin || t.origin === "null")
53
- throw new Error(`VAPID audience must be an origin. Got: ${t.toString()}`);
54
- }
36
+ var p = {
37
+ subject(e) {
38
+ if (!e) throw Error("No subject set in VAPID details.");
39
+ let t = typeof e == "string" ? new URL(e) : e;
40
+ if (!["https:", "mailto:"].includes(t.protocol)) throw Error(`VAPID subject must be https: or mailto:. Got: ${t.toString()}`);
41
+ t.hostname === "localhost" && console.warn("VAPID subject points to localhost; some push services may reject this.");
42
+ },
43
+ publicKey(e) {
44
+ if (!e) throw Error("No VAPID public key set.");
45
+ if (typeof e != "string" || !c.validate(e)) throw Error("VAPID public key must be base64url without \"=\" padding.");
46
+ let t = c.toBuffer(e);
47
+ if (t.length !== 65 || t[0] !== 4) throw Error("VAPID public key must decode to 65-byte uncompressed P-256.");
48
+ },
49
+ privateKey(e) {
50
+ if (!e) throw Error("No VAPID private key set.");
51
+ if (typeof e != "string" || !c.validate(e)) throw Error("VAPID private key must be base64url without \"=\" padding.");
52
+ if (c.toBuffer(e).length !== 32) throw Error("VAPID private key must decode to 32 bytes.");
53
+ },
54
+ expiration(e) {
55
+ if (!Number.isInteger(e)) throw Error("VAPID expiration must be an integer.");
56
+ if (e < 0) throw Error("VAPID expiration must be positive.");
57
+ if (e >= f(d)) throw Error("VAPID expiration exceeds 24 hours.");
58
+ },
59
+ audience(e) {
60
+ if (!e) throw Error("No audience set for VAPID.");
61
+ let t = typeof e == "string" ? new URL(e) : e;
62
+ if (!t.origin || t.origin === "null") throw Error(`VAPID audience must be an origin. Got: ${t.toString()}`);
63
+ }
55
64
  };
56
- function $() {
57
- const e = m.createECDH("prime256v1");
58
- e.generateKeys();
59
- let t = e.getPublicKey(), r = e.getPrivateKey();
60
- return r.length < 32 && (r = Buffer.concat([Buffer.alloc(32 - r.length, 0), r])), t.length < 65 && (t = Buffer.concat([Buffer.alloc(65 - t.length, 0), t])), { publicKey: f.toString(t), privateKey: f.toString(r) };
65
+ function m() {
66
+ let t = e.createECDH("prime256v1");
67
+ t.generateKeys();
68
+ let n = t.getPublicKey(), r = t.getPrivateKey();
69
+ return r.length < 32 && (r = Buffer.concat([Buffer.alloc(32 - r.length, 0), r])), n.length < 65 && (n = Buffer.concat([Buffer.alloc(65 - n.length, 0), n])), {
70
+ publicKey: c.toString(n),
71
+ privateKey: c.toString(r)
72
+ };
61
73
  }
62
- function G(e) {
63
- const t = f.toBuffer(e);
64
- if (t.length !== 65 || t[0] !== 4) throw new Error("Invalid uncompressed P-256 public key.");
65
- return { x: f.toString(t.subarray(1, 33)), y: f.toString(t.subarray(33, 65)) };
74
+ function h(e) {
75
+ let t = c.toBuffer(e);
76
+ if (t.length !== 65 || t[0] !== 4) throw Error("Invalid uncompressed P-256 public key.");
77
+ return {
78
+ x: c.toString(t.subarray(1, 33)),
79
+ y: c.toString(t.subarray(33, 65))
80
+ };
66
81
  }
67
- function j(e) {
68
- const t = f.toBuffer(e);
69
- if (t.length !== 32) throw new Error("Invalid P-256 private key scalar.");
70
- return f.toString(t);
71
- }
72
- function U(e, t = 32) {
73
- let r = 0;
74
- if (e[r++] !== 48) throw new Error("Invalid DER signature (expected SEQUENCE).");
75
- let n = e[r++];
76
- if (n & 128) {
77
- const l = n & 127;
78
- r += l;
79
- }
80
- if (e[r++] !== 2) throw new Error("Invalid DER signature (expected INTEGER r).");
81
- const c = e[r++];
82
- let o = e.subarray(r, r + c);
83
- if (r += c, e[r++] !== 2) throw new Error("Invalid DER signature (expected INTEGER s).");
84
- const i = e[r++];
85
- let s = e.subarray(r, r + i);
86
- if (o[0] === 0 && (o = o.subarray(1)), s[0] === 0 && (s = s.subarray(1)), o.length > t || s.length > t) throw new Error("Invalid DER signature size.");
87
- const u = Buffer.concat([Buffer.alloc(t - o.length, 0), o]), d = Buffer.concat([Buffer.alloc(t - s.length, 0), s]);
88
- return Buffer.concat([u, d]);
89
- }
90
- function O(e, t, r) {
91
- const n = { typ: "JWT", alg: "ES256" }, c = f.toJSON(n), o = f.toJSON(e), i = `${c}.${o}`, { x: s, y: u } = G(r), d = j(t), l = m.createPrivateKey({
92
- key: { kty: "EC", crv: "P-256", x: s, y: u, d },
93
- format: "jwk"
94
- }), y = m.sign("sha256", Buffer.from(i, "utf8"), {
95
- key: l,
96
- dsaEncoding: "der"
97
- }), h = U(y, 32), g = f.toString(h);
98
- return `${i}.${g}`;
99
- }
100
- function D(e) {
101
- E.audience(e.audience), E.subject(e.subject), E.publicKey(e.publicKey), E.privateKey(e.privateKey);
102
- const t = (typeof e.audience == "string" ? new URL(e.audience) : e.audience).origin, r = (typeof e.subject == "string" ? new URL(e.subject) : e.subject).toString(), n = e.expiration ?? T(_);
103
- E.expiration(n);
104
- const c = O({ aud: t, exp: n, sub: r }, e.privateKey, e.publicKey), o = new Headers();
105
- if (e.contentEncoding === b.AES_128_GCM)
106
- o.set("Authorization", `vapid t=${c}, k=${e.publicKey}`);
107
- else if (e.contentEncoding === b.AES_GCM)
108
- o.set("Authorization", `WebPush ${c}`), o.set("Crypto-Key", `p256ecdsa=${e.publicKey}`);
109
- else
110
- throw new Error(`Unsupported content encoding: ${e.contentEncoding}`);
111
- return o;
112
- }
113
- const se = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
114
- __proto__: null,
115
- GenerateHeaders: D,
116
- GenerateKeys: $,
117
- Validate: E
118
- }, Symbol.toStringTag, { value: "Module" }));
119
- function V(e) {
120
- const t = Buffer.alloc(2);
121
- return t.writeUInt16BE(e, 0), t;
82
+ function g(e) {
83
+ let t = c.toBuffer(e);
84
+ if (t.length !== 32) throw Error("Invalid P-256 private key scalar.");
85
+ return c.toString(t);
122
86
  }
123
- function H(e) {
124
- const t = Buffer.alloc(4);
125
- return t.writeUInt32BE(e >>> 0, 0), t;
87
+ function _(e, t = 32) {
88
+ let n = 0;
89
+ if (e[n++] !== 48) throw Error("Invalid DER signature (expected SEQUENCE).");
90
+ let r = e[n++];
91
+ if (r & 128) {
92
+ let e = r & 127;
93
+ n += e;
94
+ }
95
+ if (e[n++] !== 2) throw Error("Invalid DER signature (expected INTEGER r).");
96
+ let i = e[n++], a = e.subarray(n, n + i);
97
+ if (n += i, e[n++] !== 2) throw Error("Invalid DER signature (expected INTEGER s).");
98
+ let o = e[n++], s = e.subarray(n, n + o);
99
+ if (a[0] === 0 && (a = a.subarray(1)), s[0] === 0 && (s = s.subarray(1)), a.length > t || s.length > t) throw Error("Invalid DER signature size.");
100
+ let c = Buffer.concat([Buffer.alloc(t - a.length, 0), a]), l = Buffer.concat([Buffer.alloc(t - s.length, 0), s]);
101
+ return Buffer.concat([c, l]);
126
102
  }
127
- function F(e, t) {
128
- if (e.length !== t.length) throw new Error("xorBytes: length mismatch");
129
- const r = Buffer.alloc(e.length);
130
- for (let n = 0; n < e.length; n++) r[n] = e[n] ^ t[n];
131
- return r;
103
+ function v(t, n, r) {
104
+ let i = `${c.toJSON({
105
+ typ: "JWT",
106
+ alg: "ES256"
107
+ })}.${c.toJSON(t)}`, { x: a, y: o } = h(r), s = g(n), l = e.createPrivateKey({
108
+ key: {
109
+ kty: "EC",
110
+ crv: "P-256",
111
+ x: a,
112
+ y: o,
113
+ d: s
114
+ },
115
+ format: "jwk"
116
+ }), u = _(e.sign("sha256", Buffer.from(i, "utf8"), {
117
+ key: l,
118
+ dsaEncoding: "der"
119
+ }), 32);
120
+ return `${i}.${c.toString(u)}`;
132
121
  }
133
- function W(e) {
134
- if (!Number.isSafeInteger(e) || e < 0) throw new Error("SEQ must be a non-negative safe integer");
135
- const t = Buffer.alloc(12, 0), r = Math.floor(e / 4294967296), n = e >>> 0;
136
- return t.writeUInt32BE(r >>> 0, 4), t.writeUInt32BE(n, 8), t;
122
+ function y(e) {
123
+ p.audience(e.audience), p.subject(e.subject), p.publicKey(e.publicKey), p.privateKey(e.privateKey);
124
+ let t = (typeof e.audience == "string" ? new URL(e.audience) : e.audience).origin, n = (typeof e.subject == "string" ? new URL(e.subject) : e.subject).toString(), i = e.expiration ?? f(u);
125
+ p.expiration(i);
126
+ let a = v({
127
+ aud: t,
128
+ exp: i,
129
+ sub: n
130
+ }, e.privateKey, e.publicKey), o = new Headers();
131
+ if (e.contentEncoding !== r.AES_128_GCM) throw Error(`Unsupported content encoding: ${e.contentEncoding}`);
132
+ return o.set("Authorization", `vapid t=${a}, k=${e.publicKey}`), o;
137
133
  }
138
- function J(e) {
139
- const { message: t, rs: r, allowMultipleRecords: n, finalRecordPadding: c } = e;
140
- if (!Number.isInteger(r) || r < k)
141
- throw new Error(`Invalid rs; must be an integer >= ${k}.`);
142
- const o = r - 16;
143
- if (o <= 1) throw new Error("Invalid rs (too small)");
144
- const i = o - 1;
145
- if (!n && t.length > i)
146
- throw new Error(
147
- `Payload too large for a single RFC8188 record: ${t.length} bytes. Max is ${i} bytes for rs=${r}.`
148
- );
149
- if (!Number.isInteger(c) || c < 0)
150
- throw new Error("finalRecordPadding must be a non-negative integer");
151
- const s = [];
152
- let u = 0;
153
- for (; u < t.length; ) {
154
- const d = t.length - u, l = d <= i, y = l ? d : i, h = t.subarray(u, u + y);
155
- u += y;
156
- const g = Buffer.from([l ? 2 : 1]);
157
- if (l)
158
- s.push(Buffer.concat([h, g, Buffer.alloc(c, 0)]));
159
- else {
160
- const a = o - (h.length + 1);
161
- s.push(Buffer.concat([h, g, Buffer.alloc(a, 0)]));
162
- }
163
- }
164
- return t.length === 0 && s.push(Buffer.from([2])), s;
134
+ //#endregion
135
+ //#region src/utils/binary.ts
136
+ function b(e) {
137
+ let t = Buffer.alloc(4);
138
+ return t.writeUInt32BE(e >>> 0, 0), t;
165
139
  }
166
- function z(e) {
167
- const { cek: t, baseNonce: r, plaintextRecords: n } = e;
168
- if (t.length !== 16) throw new Error("CEK must be 16 bytes");
169
- if (r.length !== 12) throw new Error("baseNonce must be 12 bytes");
170
- const c = [];
171
- for (let o = 0; o < n.length; o++) {
172
- const i = F(r, W(o)), s = m.createCipheriv("aes-128-gcm", t, i), u = Buffer.concat([s.update(n[o]), s.final()]);
173
- c.push(Buffer.concat([u, s.getAuthTag()]));
174
- }
175
- return Buffer.concat(c);
140
+ function x(e, t) {
141
+ if (e.length !== t.length) throw Error("xorBytes: length mismatch");
142
+ let n = Buffer.alloc(e.length);
143
+ for (let r = 0; r < e.length; r++) n[r] = e[r] ^ t[r];
144
+ return n;
176
145
  }
177
- function w(e, t) {
178
- return m.createHmac("sha256", e).update(t).digest();
146
+ function S(e) {
147
+ if (!Number.isSafeInteger(e) || e < 0) throw Error("SEQ must be a non-negative safe integer");
148
+ let t = Buffer.alloc(12, 0), n = Math.floor(e / 4294967296), r = e >>> 0;
149
+ return t.writeUInt32BE(n >>> 0, 4), t.writeUInt32BE(r, 8), t;
179
150
  }
180
- function x(e, t) {
181
- if (!e || typeof e != "string") throw new Error("subscription.keys.p256dh must be a string");
182
- if (!t || typeof t != "string") throw new Error("subscription.keys.auth must be a string");
183
- const r = f.toBuffer(e);
184
- if (r.length !== 65 || r[0] !== 4)
185
- throw new Error("subscription.keys.p256dh must decode to a 65-byte uncompressed P-256 public key");
186
- if (f.toBuffer(t).length < 16) throw new Error("subscription.keys.auth must decode to at least 16 bytes");
151
+ //#endregion
152
+ //#region src/crypto/rfc8188.ts
153
+ function C(e) {
154
+ let { message: t, rs: n, allowMultipleRecords: r, finalRecordPadding: i } = e;
155
+ if (!Number.isInteger(n) || n < 18) throw Error("Invalid rs; must be an integer >= 18.");
156
+ let a = n - 16;
157
+ if (a <= 1) throw Error("Invalid rs (too small)");
158
+ let o = a - 1;
159
+ if (!r && t.length > o) throw Error(`Payload too large for a single RFC8188 record: ${t.length} bytes. Max is ${o} bytes for rs=${n}.`);
160
+ if (!Number.isInteger(i) || i < 0) throw Error("finalRecordPadding must be a non-negative integer");
161
+ let s = [], c = 0;
162
+ for (; c < t.length;) {
163
+ let e = t.length - c, n = e <= o, r = n ? e : o, l = t.subarray(c, c + r);
164
+ c += r;
165
+ let u = Buffer.from([n ? 2 : 1]);
166
+ if (n) s.push(Buffer.concat([
167
+ l,
168
+ u,
169
+ Buffer.alloc(i, 0)
170
+ ]));
171
+ else {
172
+ let e = a - (l.length + 1);
173
+ s.push(Buffer.concat([
174
+ l,
175
+ u,
176
+ Buffer.alloc(e, 0)
177
+ ]));
178
+ }
179
+ }
180
+ return t.length === 0 && s.push(Buffer.from([2])), s;
187
181
  }
188
- function X(e) {
189
- x(e.p256dh, e.auth);
190
- const t = f.toBuffer(e.p256dh), r = f.toBuffer(e.auth), n = m.createECDH("prime256v1");
191
- n.generateKeys();
192
- const c = n.getPublicKey(), o = n.computeSecret(t), i = m.randomBytes(16), s = w(r, o), u = Buffer.concat([Buffer.from("WebPush: info", "utf8"), Buffer.from([0]), t, c]), d = w(s, Buffer.concat([u, Buffer.from([1])])), l = w(i, d), y = Buffer.concat([Buffer.from("Content-Encoding: aes128gcm", "utf8"), Buffer.from([0])]), h = Buffer.concat([Buffer.from("Content-Encoding: nonce", "utf8"), Buffer.from([0])]), g = w(l, Buffer.concat([y, Buffer.from([1])])).subarray(0, 16), a = w(l, Buffer.concat([h, Buffer.from([1])])).subarray(0, 12), B = J({
193
- message: e.payload,
194
- rs: e.rs,
195
- allowMultipleRecords: e.allowMultipleRecords,
196
- finalRecordPadding: e.finalRecordPadding
197
- }), P = z({ cek: g, baseNonce: a, plaintextRecords: B });
198
- if (c.length !== 65) throw new Error("Sender public key must be 65 bytes");
199
- return Buffer.concat([i, H(e.rs), Buffer.from([c.length]), c, P]);
182
+ function w(t) {
183
+ let { cek: n, baseNonce: r, plaintextRecords: i } = t;
184
+ if (n.length !== 16) throw Error("CEK must be 16 bytes");
185
+ if (r.length !== 12) throw Error("baseNonce must be 12 bytes");
186
+ let a = [];
187
+ for (let t = 0; t < i.length; t++) {
188
+ let o = x(r, S(t)), s = e.createCipheriv("aes-128-gcm", n, o), c = Buffer.concat([s.update(i[t]), s.final()]);
189
+ a.push(Buffer.concat([c, s.getAuthTag()]));
190
+ }
191
+ return Buffer.concat(a);
200
192
  }
201
- function Q(e) {
202
- x(e.p256dh, e.auth);
203
- const t = f.toBuffer(e.p256dh), r = f.toBuffer(e.auth), n = m.createECDH("prime256v1");
204
- n.generateKeys();
205
- const c = n.getPublicKey(), o = n.computeSecret(t), i = m.randomBytes(16), s = w(r, o), u = Buffer.concat([Buffer.from("WebPush: info", "utf8"), Buffer.from([0]), t, c]), d = w(s, Buffer.concat([u, Buffer.from([1])])), l = w(i, d), y = Buffer.concat([Buffer.from("Content-Encoding: aesgcm", "utf8"), Buffer.from([0])]), h = Buffer.concat([Buffer.from("Content-Encoding: nonce", "utf8"), Buffer.from([0])]), g = w(l, Buffer.concat([y, Buffer.from([1])])).subarray(0, 16), a = w(l, Buffer.concat([h, Buffer.from([1])])).subarray(0, 12), B = 0, P = Buffer.concat([V(B), Buffer.alloc(B, 0), e.payload]), v = m.createCipheriv("aes-128-gcm", g, a), A = Buffer.concat([v.update(P), v.final()]), S = v.getAuthTag();
206
- return { saltB64Url: f.toString(i), localPublicKey: c, ciphertext: Buffer.concat([A, S]) };
193
+ //#endregion
194
+ //#region src/crypto/webpush-encryption.ts
195
+ function T(t, n) {
196
+ return e.createHmac("sha256", t).update(n).digest();
207
197
  }
208
- class Y extends Error {
209
- constructor(t, r) {
210
- super(t), this.response = r;
211
- }
198
+ function E(e, t) {
199
+ if (!e || typeof e != "string") throw Error("subscription.keys.p256dh must be a string");
200
+ if (!t || typeof t != "string") throw Error("subscription.keys.auth must be a string");
201
+ let n = c.toBuffer(e);
202
+ if (n.length !== 65 || n[0] !== 4) throw Error("subscription.keys.p256dh must decode to a 65-byte uncompressed P-256 public key");
203
+ if (c.toBuffer(t).length < 16) throw Error("subscription.keys.auth must decode to at least 16 bytes");
212
204
  }
213
- function q(e) {
214
- if (!f.validate(e)) throw new Error("Topic contains invalid characters; must be URL-safe base64 chars.");
215
- if (e.length > 32) throw new Error("Topic must be <= 32 characters.");
205
+ function D(t) {
206
+ E(t.p256dh, t.auth);
207
+ let n = c.toBuffer(t.p256dh), r = c.toBuffer(t.auth), i = e.createECDH("prime256v1");
208
+ i.generateKeys();
209
+ let a = i.getPublicKey(), o = i.computeSecret(n), s = e.randomBytes(16), l = T(r, o), u = Buffer.concat([
210
+ Buffer.from("WebPush: info", "utf8"),
211
+ Buffer.from([0]),
212
+ n,
213
+ a
214
+ ]), d = T(s, T(l, Buffer.concat([u, Buffer.from([1])]))), f = Buffer.concat([Buffer.from("Content-Encoding: aes128gcm", "utf8"), Buffer.from([0])]), p = Buffer.concat([Buffer.from("Content-Encoding: nonce", "utf8"), Buffer.from([0])]), m = w({
215
+ cek: T(d, Buffer.concat([f, Buffer.from([1])])).subarray(0, 16),
216
+ baseNonce: T(d, Buffer.concat([p, Buffer.from([1])])).subarray(0, 12),
217
+ plaintextRecords: C({
218
+ message: t.payload,
219
+ rs: t.rs,
220
+ allowMultipleRecords: t.allowMultipleRecords,
221
+ finalRecordPadding: t.finalRecordPadding
222
+ })
223
+ });
224
+ if (a.length !== 65) throw Error("Sender public key must be 65 bytes");
225
+ return Buffer.concat([
226
+ s,
227
+ b(t.rs),
228
+ Buffer.from([a.length]),
229
+ a,
230
+ m
231
+ ]);
216
232
  }
217
- function Z(e) {
218
- return e.startsWith("https://android.googleapis.com/gcm/send");
233
+ //#endregion
234
+ //#region src/webpush.ts
235
+ var O = class extends Error {
236
+ constructor(e, t) {
237
+ super(e), this.response = t;
238
+ }
239
+ };
240
+ function k(e) {
241
+ if (!c.validate(e)) throw Error("Topic contains invalid characters; must be URL-safe base64 chars.");
242
+ if (e.length > 32) throw Error("Topic must be <= 32 characters.");
219
243
  }
220
- function ee(e) {
221
- return e.startsWith("https://fcm.googleapis.com/fcm/send") || e.includes("fcm.googleapis.com");
244
+ function A(e) {
245
+ return e.startsWith("https://android.googleapis.com/gcm/send");
222
246
  }
223
- function te(e) {
224
- return new URL(e).origin;
247
+ function j(e) {
248
+ return e.startsWith("https://fcm.googleapis.com/fcm/send") || e.includes("fcm.googleapis.com");
225
249
  }
226
- function re(e) {
227
- if (!Object.values(b).includes(e)) throw new Error(`Unsupported content encoding: ${e}`);
250
+ function M(e) {
251
+ return new URL(e).origin;
228
252
  }
229
- function ne(e) {
230
- if (!Object.values(R).includes(e)) throw new Error(`Unsupported urgency: ${e}`);
253
+ function N(e) {
254
+ if (!Object.values(r).includes(e)) throw Error(`Unsupported content encoding: ${e}`);
231
255
  }
232
- class ie {
233
- constructor(t) {
234
- if (this.config = t, E.subject(t.vapid.subject), E.privateKey(t.vapid.privateKey), E.publicKey(t.vapid.publicKey), t.gcm?.apiKey != null && t.gcm.apiKey.length === 0)
235
- throw new Error("The GCM/FCM API Key should be a non-empty string, null, or undefined.");
236
- }
237
- /**
238
- * Generate the request (endpoint + fetch init) to send a push message.
239
- *
240
- * - For `aes128gcm`, this produces an RFC8188 payload body (header block + encrypted records).
241
- * - For Web Push, defaults to a single record per RFC8291; multi-record requires `allowMultipleRecords: true`.
242
- */
243
- generateRequest(t, r, n) {
244
- if (!t?.endpoint || typeof t.endpoint != "string" || t.endpoint.length === 0)
245
- throw new Error("You must pass a subscription with a valid endpoint URL.");
246
- const c = n?.TTL ?? L;
247
- if (!Number.isFinite(c) || c < 0) throw new Error("TTL must be a number >= 0.");
248
- const o = n?.contentEncoding ?? b.AES_128_GCM;
249
- re(o);
250
- const i = n?.urgency ?? R.NORMAL;
251
- ne(i);
252
- const s = n?.topic;
253
- s && q(s);
254
- const u = n?.allowMultipleRecords ?? !1, d = n?.rs ?? N;
255
- if (!Number.isInteger(d) || d < k) throw new Error(`rs must be an integer >= ${k}.`);
256
- const l = n?.finalRecordPadding ?? 0, y = n?.headers ?? {}, h = n?.gcmAPIKey ?? this.config.gcm?.apiKey ?? null, g = n?.vapidDetails === void 0 ? this.config.vapid : n.vapidDetails, a = {
257
- ...y,
258
- // Keep protocol-critical defaults authoritative even when custom headers are supplied.
259
- TTL: String(c),
260
- Urgency: i
261
- };
262
- s && (a.Topic = s);
263
- let B;
264
- if (r != null && r !== "") {
265
- if (!t.keys?.p256dh || !t.keys?.auth)
266
- throw new Error("To send a payload, the subscription must include 'keys.p256dh' and 'keys.auth'.");
267
- const I = Buffer.isBuffer(r) ? r : r instanceof Uint8Array ? Buffer.from(r) : Buffer.from(r, "utf8");
268
- if (a["Content-Type"] = "application/octet-stream", o === b.AES_128_GCM) {
269
- const p = X({
270
- p256dh: t.keys.p256dh,
271
- auth: t.keys.auth,
272
- payload: I,
273
- rs: d,
274
- allowMultipleRecords: u,
275
- finalRecordPadding: l
276
- });
277
- B = p, a["Content-Encoding"] = b.AES_128_GCM, a["Content-Length"] = String(p.length);
278
- } else {
279
- const p = Q({
280
- p256dh: t.keys.p256dh,
281
- auth: t.keys.auth,
282
- payload: I
283
- });
284
- B = p.ciphertext, a["Content-Encoding"] = b.AES_GCM, a["Content-Length"] = String(p.ciphertext.length), a.Encryption = `salt=${p.saltB64Url}`, a["Crypto-Key"] = `dh=${f.toString(p.localPublicKey)}`;
285
- }
286
- } else
287
- a["Content-Length"] = "0";
288
- const v = t.endpoint, A = Z(v), S = ee(v);
289
- if (A)
290
- h ? a.Authorization = `key=${h}` : console.warn("GCM endpoint detected but no GCM API key provided.");
291
- else if (g) {
292
- const I = te(v), p = D({
293
- audience: I,
294
- subject: g.subject,
295
- publicKey: g.publicKey,
296
- privateKey: g.privateKey,
297
- contentEncoding: o
298
- }), C = p.get("Authorization");
299
- C && (a.Authorization = C);
300
- const K = p.get("Crypto-Key");
301
- K && o === b.AES_GCM && (a["Crypto-Key"] = a["Crypto-Key"] ? `${a["Crypto-Key"]}; ${K}` : K);
302
- } else S && h && (a.Authorization = `key=${h}`);
303
- return { endpoint: v, init: {
304
- method: "POST",
305
- headers: a,
306
- body: B
307
- } };
308
- }
309
- /**
310
- * Method to send notification to subscribed device
311
- * @param subscription
312
- * @param payload
313
- * @param options
314
- */
315
- async notify(t, r, n) {
316
- const { endpoint: c, init: o } = this.generateRequest(t, r, n), i = await fetch(c, o);
317
- if (!i.ok && n?.throwOnInvalidResponse) throw new Y("Received unexpected response code", i);
318
- return i;
319
- }
256
+ function P(e) {
257
+ if (!Object.values(i).includes(e)) throw Error(`Unsupported urgency: ${e}`);
320
258
  }
321
- export {
322
- N as DEFAULT_RS,
323
- L as DEFAULT_TTL,
324
- k as MIN_RS,
325
- b as SupportedContentEncoding,
326
- R as SupportedUrgency,
327
- se as VAPID,
328
- ie as WebPush,
329
- Y as WebPushError
259
+ var F = class {
260
+ constructor(e) {
261
+ if (this.config = e, p.subject(e.vapid.subject), p.privateKey(e.vapid.privateKey), p.publicKey(e.vapid.publicKey), e.gcm?.apiKey != null && e.gcm.apiKey.length === 0) throw Error("The GCM/FCM API Key should be a non-empty string, null, or undefined.");
262
+ }
263
+ generateRequest(e, t, n) {
264
+ if (!e?.endpoint || typeof e.endpoint != "string" || e.endpoint.length === 0) throw Error("You must pass a subscription with a valid endpoint URL.");
265
+ let a = n?.TTL ?? 2419200;
266
+ if (!Number.isFinite(a) || a < 0) throw Error("TTL must be a number >= 0.");
267
+ let o = n?.contentEncoding ?? r.AES_128_GCM;
268
+ N(o);
269
+ let s = n?.urgency ?? i.NORMAL;
270
+ P(s);
271
+ let c = n?.topic;
272
+ c && k(c);
273
+ let l = n?.allowMultipleRecords ?? !1, u = n?.rs ?? 4096;
274
+ if (!Number.isInteger(u) || u < 18) throw Error("rs must be an integer >= 18.");
275
+ let d = n?.finalRecordPadding ?? 0, f = n?.headers ?? {}, p = n?.gcmAPIKey ?? this.config.gcm?.apiKey ?? null, m = n?.vapidDetails === void 0 ? this.config.vapid : n.vapidDetails, h = {
276
+ ...f,
277
+ TTL: String(a),
278
+ Urgency: s
279
+ };
280
+ c && (h.Topic = c);
281
+ let g;
282
+ if (t != null && t !== "") {
283
+ if (!e.keys?.p256dh || !e.keys?.auth) throw Error("To send a payload, the subscription must include 'keys.p256dh' and 'keys.auth'.");
284
+ let n = Buffer.isBuffer(t) ? t : t instanceof Uint8Array ? Buffer.from(t) : Buffer.from(t, "utf8");
285
+ h["Content-Type"] = "application/octet-stream";
286
+ let i = D({
287
+ p256dh: e.keys.p256dh,
288
+ auth: e.keys.auth,
289
+ payload: n,
290
+ rs: u,
291
+ allowMultipleRecords: l,
292
+ finalRecordPadding: d
293
+ });
294
+ g = i, h["Content-Encoding"] = r.AES_128_GCM, h["Content-Length"] = String(i.length);
295
+ } else h["Content-Length"] = "0";
296
+ let _ = e.endpoint, v = A(_), b = j(_);
297
+ if (v) p ? h.Authorization = `key=${p}` : console.warn("GCM endpoint detected but no GCM API key provided.");
298
+ else if (m) {
299
+ let e = y({
300
+ audience: M(_),
301
+ subject: m.subject,
302
+ publicKey: m.publicKey,
303
+ privateKey: m.privateKey,
304
+ contentEncoding: o
305
+ }).get("Authorization");
306
+ e && (h.Authorization = e);
307
+ } else b && p && (h.Authorization = `key=${p}`);
308
+ return {
309
+ endpoint: _,
310
+ init: {
311
+ method: "POST",
312
+ headers: h,
313
+ body: g
314
+ }
315
+ };
316
+ }
317
+ async notify(e, t, n) {
318
+ let { endpoint: r, init: i } = this.generateRequest(e, t, n), a = await fetch(r, i);
319
+ if (!a.ok && n?.throwOnInvalidResponse) throw new O("Received unexpected response code", a);
320
+ return a;
321
+ }
330
322
  };
331
- //# sourceMappingURL=index.es.js.map
323
+ //#endregion
324
+ export { o as DEFAULT_RS, a as DEFAULT_TTL, s as MIN_RS, r as SupportedContentEncoding, i as SupportedUrgency, l as VAPID, F as WebPush, O as WebPushError };
325
+
326
+ //# sourceMappingURL=index.es.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.es.js","sources":["../src/constants.ts","../src/utils/base64url.ts","../src/vapid.ts","../src/utils/binary.ts","../src/crypto/rfc8188.ts","../src/crypto/webpush-encryption.ts","../src/webpush.ts"],"sourcesContent":["'use strict';\n\nexport enum SupportedContentEncoding {\n AES_GCM = 'aesgcm',\n AES_128_GCM = 'aes128gcm',\n}\n\nexport enum SupportedUrgency {\n VERY_LOW = 'very-low',\n LOW = 'low',\n NORMAL = 'normal',\n HIGH = 'high',\n}\n\n/** Default TTL is 4 weeks (seconds). */\nexport const DEFAULT_TTL = 2419200;\n\n/**\n * Default record size (RFC8188 `rs`) for aes128gcm.\n * For Web Push (RFC8291), a single record is required, but `rs` still appears in the body header.\n */\nexport const DEFAULT_RS = 4096;\n\n/** Minimum valid `rs` for aes128gcm per RFC8188 (must be >= 18). */\nexport const MIN_RS = 18;\n","'use strict';\n\nexport const base64url = {\n validate(input: string): boolean {\n // URL-safe base64 without \"=\" padding\n return /^[A-Za-z0-9\\-_]+$/.test(input);\n },\n toBuffer(input: string): Buffer {\n return Buffer.from(input, 'base64url');\n },\n toString(input: Buffer | Uint8Array | string): string {\n const buf = typeof input === 'string' ? Buffer.from(input, 'utf8') : Buffer.from(input);\n return buf.toString('base64url');\n },\n toJSON(obj: any) {\n return this.toString(Buffer.from(JSON.stringify(obj), 'utf8'));\n }\n};\n","'use strict';\n\nimport crypto from 'crypto';\nimport {base64url} from './utils/base64url';\nimport {SupportedContentEncoding} from './constants';\n\nconst DEFAULT_EXPIRATION_SECONDS = 12 * 60 * 60;\nconst MAX_EXPIRATION_SECONDS = 24 * 60 * 60;\n\nfunction getFutureExpirationTimestamp(numSeconds: number): number {\n const future = new Date();\n future.setSeconds(future.getSeconds() + numSeconds);\n return Math.floor(future.getTime() / 1000);\n}\n\nexport const Validate = {\n subject(subject: string | URL): void {\n if (!subject) throw new Error('No subject set in VAPID details.');\n const u = typeof subject === 'string' ? new URL(subject) : subject;\n if (!['https:', 'mailto:'].includes(u.protocol)) {\n throw new Error(`VAPID subject must be https: or mailto:. Got: ${u.toString()}`);\n }\n if (u.hostname === 'localhost') {\n console.warn('VAPID subject points to localhost; some push services may reject this.');\n }\n },\n\n publicKey(publicKey: string): void {\n if (!publicKey) throw new Error('No VAPID public key set.');\n if (typeof publicKey !== 'string' || !base64url.validate(publicKey)) {\n throw new Error('VAPID public key must be base64url without \"=\" padding.');\n }\n const pk = base64url.toBuffer(publicKey);\n if (pk.length !== 65 || pk[0] !== 0x04) {\n throw new Error('VAPID public key must decode to 65-byte uncompressed P-256.');\n }\n },\n\n privateKey(privateKey: string): void {\n if (!privateKey) throw new Error('No VAPID private key set.');\n if (typeof privateKey !== 'string' || !base64url.validate(privateKey)) {\n throw new Error('VAPID private key must be base64url without \"=\" padding.');\n }\n const sk = base64url.toBuffer(privateKey);\n if (sk.length !== 32) throw new Error('VAPID private key must decode to 32 bytes.');\n },\n\n expiration(expiration: number): void {\n if (!Number.isInteger(expiration)) throw new Error('VAPID expiration must be an integer.');\n if (expiration < 0) throw new Error('VAPID expiration must be positive.');\n const maxExp = getFutureExpirationTimestamp(MAX_EXPIRATION_SECONDS);\n if (expiration >= maxExp) throw new Error('VAPID expiration exceeds 24 hours.');\n },\n\n audience(audience: string | URL): void {\n if (!audience) throw new Error('No audience set for VAPID.');\n const a = typeof audience === 'string' ? new URL(audience) : audience;\n if (!a.origin || a.origin === 'null') {\n throw new Error(`VAPID audience must be an origin. Got: ${a.toString()}`);\n }\n },\n};\n\nexport function GenerateKeys(): { publicKey: string; privateKey: string } {\n const ecdh = crypto.createECDH('prime256v1');\n ecdh.generateKeys();\n\n let pub = ecdh.getPublicKey();\n let priv = ecdh.getPrivateKey();\n\n if (priv.length < 32) priv = Buffer.concat([Buffer.alloc(32 - priv.length, 0x00), priv]);\n if (pub.length < 65) pub = Buffer.concat([Buffer.alloc(65 - pub.length, 0x00), pub]);\n\n return {publicKey: base64url.toString(pub), privateKey: base64url.toString(priv)};\n}\n\nfunction publicKeyToJwk(publicKeyB64Url: string): { x: string; y: string } {\n const pub = base64url.toBuffer(publicKeyB64Url);\n if (pub.length !== 65 || pub[0] !== 0x04) throw new Error('Invalid uncompressed P-256 public key.');\n return {x: base64url.toString(pub.subarray(1, 33)), y: base64url.toString(pub.subarray(33, 65))};\n}\n\nfunction privateKeyToJwk(privateKeyB64Url: string): string {\n const d = base64url.toBuffer(privateKeyB64Url);\n if (d.length !== 32) throw new Error('Invalid P-256 private key scalar.');\n return base64url.toString(d);\n}\n\nfunction derToJose(derSig: Buffer, keySizeBytes = 32): Buffer {\n let off = 0;\n if (derSig[off++] !== 0x30) throw new Error('Invalid DER signature (expected SEQUENCE).');\n\n let seqLen = derSig[off++];\n if (seqLen & 0x80) {\n const n = seqLen & 0x7f;\n off += n;\n }\n\n if (derSig[off++] !== 0x02) throw new Error('Invalid DER signature (expected INTEGER r).');\n const rLen = derSig[off++];\n let r = derSig.subarray(off, off + rLen);\n off += rLen;\n\n if (derSig[off++] !== 0x02) throw new Error('Invalid DER signature (expected INTEGER s).');\n const sLen = derSig[off++];\n let s = derSig.subarray(off, off + sLen);\n\n if (r[0] === 0x00) r = r.subarray(1);\n if (s[0] === 0x00) s = s.subarray(1);\n\n if (r.length > keySizeBytes || s.length > keySizeBytes) throw new Error('Invalid DER signature size.');\n const rPad = Buffer.concat([Buffer.alloc(keySizeBytes - r.length, 0x00), r]);\n const sPad = Buffer.concat([Buffer.alloc(keySizeBytes - s.length, 0x00), s]);\n return Buffer.concat([rPad, sPad]);\n}\n\nfunction signJwtES256(payload: Record<string, unknown>, privateKeyB64Url: string, publicKeyB64Url: string): string {\n const header = {typ: 'JWT', alg: 'ES256'};\n const encodedHeader = base64url.toJSON(header);\n const encodedPayload = base64url.toJSON(payload);\n const signingInput = `${encodedHeader}.${encodedPayload}`;\n\n const {x, y} = publicKeyToJwk(publicKeyB64Url);\n const d = privateKeyToJwk(privateKeyB64Url);\n\n const keyObject = crypto.createPrivateKey({\n key: {kty: 'EC', crv: 'P-256', x, y, d},\n format: 'jwk',\n });\n\n const derSig = crypto.sign('sha256', Buffer.from(signingInput, 'utf8'), {\n key: keyObject,\n dsaEncoding: 'der',\n });\n\n const joseSig = derToJose(derSig, 32);\n const encodedSig = base64url.toString(joseSig);\n return `${signingInput}.${encodedSig}`;\n}\n\nexport function GenerateHeaders(input: {\n audience: string | URL;\n subject: string | URL;\n publicKey: string;\n privateKey: string;\n contentEncoding: SupportedContentEncoding;\n expiration?: number;\n}): Headers {\n Validate.audience(input.audience);\n Validate.subject(input.subject);\n Validate.publicKey(input.publicKey);\n Validate.privateKey(input.privateKey);\n\n const audience = (typeof input.audience === 'string' ? new URL(input.audience) : input.audience).origin;\n const subject = (typeof input.subject === 'string' ? new URL(input.subject) : input.subject).toString();\n\n const exp = input.expiration ?? getFutureExpirationTimestamp(DEFAULT_EXPIRATION_SECONDS);\n Validate.expiration(exp);\n\n const jwt = signJwtES256({aud: audience, exp, sub: subject}, input.privateKey, input.publicKey);\n\n const headers = new Headers();\n if (input.contentEncoding === SupportedContentEncoding.AES_128_GCM) {\n headers.set('Authorization', `vapid t=${jwt}, k=${input.publicKey}`);\n } else if (input.contentEncoding === SupportedContentEncoding.AES_GCM) {\n headers.set('Authorization', `WebPush ${jwt}`);\n headers.set('Crypto-Key', `p256ecdsa=${input.publicKey}`);\n } else {\n throw new Error(`Unsupported content encoding: ${input.contentEncoding}`);\n }\n return headers;\n}\n\n","'use strict';\n\n/** Write UInt16BE. */\nexport function u16be(n: number): Buffer {\n const b = Buffer.alloc(2);\n b.writeUInt16BE(n, 0);\n return b;\n}\n\n/** Write UInt32BE. */\nexport function u32be(n: number): Buffer {\n const b = Buffer.alloc(4);\n b.writeUInt32BE(n >>> 0, 0);\n return b;\n}\n\nexport function xorBytes(a: Buffer, b: Buffer): Buffer {\n if (a.length !== b.length) throw new Error('xorBytes: length mismatch');\n const out = Buffer.alloc(a.length);\n for (let i = 0; i < a.length; i++) out[i] = a[i] ^ b[i];\n return out;\n}\n\n/**\n * Convert a record sequence number into a 96-bit (12-octet) big-endian buffer.\n * RFC8188 defines SEQ as a 96-bit unsigned integer starting at 0.\n */\nexport function seq96be(seq: number): Buffer {\n if (!Number.isSafeInteger(seq) || seq < 0) throw new Error('SEQ must be a non-negative safe integer');\n const out = Buffer.alloc(12, 0x00);\n\n // Write into low 64 bits; high 32 bits remain zero.\n const hi = Math.floor(seq / 0x100000000);\n const lo = seq >>> 0;\n out.writeUInt32BE(hi >>> 0, 4);\n out.writeUInt32BE(lo, 8);\n return out;\n}\n","'use strict';\n\nimport crypto from 'crypto';\nimport { MIN_RS } from '../constants';\nimport { xorBytes, seq96be } from '../utils/binary';\n\n/**\n * Build RFC8188 plaintext records from message bytes.\n *\n * Record plaintext length for full records is `rs - 16` because AES-GCM adds a 16-byte tag.\n * The padding delimiter must be present in every record:\n * - 0x01 for all non-last records\n * - 0x02 for the last record\n */\nexport function buildPlaintextRecords(params: {\n message: Buffer;\n rs: number;\n allowMultipleRecords: boolean;\n finalRecordPadding: number;\n}): Buffer[] {\n const { message, rs, allowMultipleRecords, finalRecordPadding } = params;\n\n if (!Number.isInteger(rs) || rs < MIN_RS) {\n throw new Error(`Invalid rs; must be an integer >= ${MIN_RS}.`);\n }\n\n const maxPlain = rs - 16; // AES-GCM tag length is 16\n if (maxPlain <= 1) throw new Error('Invalid rs (too small)');\n\n const maxDataPerFullRecord = maxPlain - 1; // reserve delimiter\n\n if (!allowMultipleRecords && message.length > maxDataPerFullRecord) {\n throw new Error(\n `Payload too large for a single RFC8188 record: ${message.length} bytes. ` +\n `Max is ${maxDataPerFullRecord} bytes for rs=${rs}.`\n );\n }\n\n if (!Number.isInteger(finalRecordPadding) || finalRecordPadding < 0) {\n throw new Error('finalRecordPadding must be a non-negative integer');\n }\n\n const records: Buffer[] = [];\n let offset = 0;\n\n while (offset < message.length) {\n const remaining = message.length - offset;\n const isLast = remaining <= maxDataPerFullRecord;\n\n const dataLen = isLast ? remaining : maxDataPerFullRecord;\n const data = message.subarray(offset, offset + dataLen);\n offset += dataLen;\n\n const delimiter = Buffer.from([isLast ? 0x02 : 0x01]);\n\n if (!isLast) {\n // Full-sized non-last record.\n const paddingLen = maxPlain - (data.length + 1);\n records.push(Buffer.concat([data, delimiter, Buffer.alloc(paddingLen, 0x00)]));\n } else {\n // Last record can be partial and may include padding after delimiter.\n records.push(Buffer.concat([data, delimiter, Buffer.alloc(finalRecordPadding, 0x00)]));\n }\n }\n\n // Empty message -> final record with only delimiter (0x02)\n if (message.length === 0) records.push(Buffer.from([0x02]));\n\n return records;\n}\n\n/**\n * Encrypt RFC8188 plaintext records using AES-128-GCM.\n *\n * Nonce per record: nonce = baseNonce XOR SEQ (96-bit big-endian SEQ starting at 0).\n * AAD is empty.\n */\nexport function encryptRecords(params: {\n cek: Buffer; // 16 bytes\n baseNonce: Buffer; // 12 bytes\n plaintextRecords: Buffer[];\n}): Buffer {\n const { cek, baseNonce, plaintextRecords } = params;\n if (cek.length !== 16) throw new Error('CEK must be 16 bytes');\n if (baseNonce.length !== 12) throw new Error('baseNonce must be 12 bytes');\n\n const out: Buffer[] = [];\n for (let i = 0; i < plaintextRecords.length; i++) {\n const nonce = xorBytes(baseNonce, seq96be(i));\n const cipher = crypto.createCipheriv('aes-128-gcm', cek, nonce);\n\n const enc = Buffer.concat([cipher.update(plaintextRecords[i]), cipher.final()]);\n out.push(Buffer.concat([enc, cipher.getAuthTag()]));\n }\n return Buffer.concat(out);\n}\n","'use strict';\n\nimport crypto from 'crypto';\nimport { base64url } from '../utils/base64url';\nimport { u16be, u32be } from '../utils/binary';\nimport { buildPlaintextRecords, encryptRecords } from './rfc8188';\n\nfunction hmacSha256(key: Buffer, data: Buffer): Buffer {\n return crypto.createHmac('sha256', key).update(data).digest();\n}\n\nfunction assertSubscriptionKeys(p256dh: string, auth: string): void {\n if (!p256dh || typeof p256dh !== 'string') throw new Error('subscription.keys.p256dh must be a string');\n if (!auth || typeof auth !== 'string') throw new Error('subscription.keys.auth must be a string');\n\n const p256dhBytes = base64url.toBuffer(p256dh);\n if (p256dhBytes.length !== 65 || p256dhBytes[0] !== 0x04) {\n throw new Error('subscription.keys.p256dh must decode to a 65-byte uncompressed P-256 public key');\n }\n\n const authBytes = base64url.toBuffer(auth);\n if (authBytes.length < 16) throw new Error('subscription.keys.auth must decode to at least 16 bytes');\n}\n\n/**\n * Encrypt a push payload using RFC8291 + RFC8188 with aes128gcm.\n *\n * Returns the full RFC8188 body:\n * salt(16) || rs(4) || idlen(1) || keyid(=sender pubkey) || encryptedRecords\n */\nexport function encryptAes128GcmBody(params: {\n p256dh: string;\n auth: string;\n payload: Buffer;\n rs: number;\n allowMultipleRecords: boolean;\n finalRecordPadding: number;\n}): Buffer {\n assertSubscriptionKeys(params.p256dh, params.auth);\n\n const uaPublic = base64url.toBuffer(params.p256dh);\n const authSecret = base64url.toBuffer(params.auth);\n\n // Sender ephemeral key\n const ecdh = crypto.createECDH('prime256v1');\n ecdh.generateKeys();\n const asPublic = ecdh.getPublicKey(); // 65 bytes\n const ecdhSecret = ecdh.computeSecret(uaPublic);\n\n // RFC8291: random salt\n const salt = crypto.randomBytes(16);\n\n // RFC8291: PRK_key = HMAC(auth_secret, ecdh_secret)\n const prkKey = hmacSha256(authSecret, ecdhSecret);\n\n // key_info = \"WebPush: info\" || 0x00 || ua_public || as_public\n const keyInfo = Buffer.concat([Buffer.from('WebPush: info', 'utf8'), Buffer.from([0x00]), uaPublic, asPublic]);\n\n // IKM = HMAC(PRK_key, key_info || 0x01)\n const ikm = hmacSha256(prkKey, Buffer.concat([keyInfo, Buffer.from([0x01])]));\n\n // RFC8188: PRK = HMAC(salt, IKM)\n const prk = hmacSha256(salt, ikm);\n\n // RFC8188: CEK / nonce derivation\n const cekInfo = Buffer.concat([Buffer.from('Content-Encoding: aes128gcm', 'utf8'), Buffer.from([0x00])]);\n const nonceInfo = Buffer.concat([Buffer.from('Content-Encoding: nonce', 'utf8'), Buffer.from([0x00])]);\n\n const cek = hmacSha256(prk, Buffer.concat([cekInfo, Buffer.from([0x01])])).subarray(0, 16);\n const baseNonce = hmacSha256(prk, Buffer.concat([nonceInfo, Buffer.from([0x01])])).subarray(0, 12);\n\n const plaintextRecords = buildPlaintextRecords({\n message: params.payload,\n rs: params.rs,\n allowMultipleRecords: params.allowMultipleRecords,\n finalRecordPadding: params.finalRecordPadding,\n });\n\n const encryptedRecords = encryptRecords({ cek, baseNonce, plaintextRecords });\n\n // RFC8188 header: salt || rs || idlen || keyid\n if (asPublic.length !== 65) throw new Error('Sender public key must be 65 bytes');\n\n return Buffer.concat([salt, u32be(params.rs), Buffer.from([asPublic.length]), asPublic, encryptedRecords]);\n}\n\n/**\n * Legacy aesgcm encryption for interop.\n * Returns ciphertext plus values needed for headers: Encryption (salt) and Crypto-Key (dh).\n */\nexport function encryptAesGcmLegacy(params: {\n p256dh: string;\n auth: string;\n payload: Buffer;\n}): { saltB64Url: string; localPublicKey: Buffer; ciphertext: Buffer } {\n assertSubscriptionKeys(params.p256dh, params.auth);\n\n const receiverPub = base64url.toBuffer(params.p256dh);\n const authSecret = base64url.toBuffer(params.auth);\n\n const ecdh = crypto.createECDH('prime256v1');\n ecdh.generateKeys();\n const senderPub = ecdh.getPublicKey();\n const ecdhSecret = ecdh.computeSecret(receiverPub);\n\n const salt = crypto.randomBytes(16);\n\n const prkKey = hmacSha256(authSecret, ecdhSecret);\n const keyInfo = Buffer.concat([Buffer.from('WebPush: info', 'utf8'), Buffer.from([0x00]), receiverPub, senderPub]);\n const ikm = hmacSha256(prkKey, Buffer.concat([keyInfo, Buffer.from([0x01])]));\n const prk = hmacSha256(salt, ikm);\n\n const cekInfo = Buffer.concat([Buffer.from('Content-Encoding: aesgcm', 'utf8'), Buffer.from([0x00])]);\n const nonceInfo = Buffer.concat([Buffer.from('Content-Encoding: nonce', 'utf8'), Buffer.from([0x00])]);\n\n const cek = hmacSha256(prk, Buffer.concat([cekInfo, Buffer.from([0x01])])).subarray(0, 16);\n const nonce = hmacSha256(prk, Buffer.concat([nonceInfo, Buffer.from([0x01])])).subarray(0, 12);\n\n const padLen = 0;\n const plaintext = Buffer.concat([u16be(padLen), Buffer.alloc(padLen, 0x00), params.payload]);\n\n const cipher = crypto.createCipheriv('aes-128-gcm', cek, nonce);\n const enc = Buffer.concat([cipher.update(plaintext), cipher.final()]);\n const tag = cipher.getAuthTag();\n\n return { saltB64Url: base64url.toString(salt), localPublicKey: senderPub, ciphertext: Buffer.concat([enc, tag]) };\n}\n","'use strict';\n\nimport {\n DEFAULT_RS,\n DEFAULT_TTL,\n MIN_RS,\n SupportedContentEncoding,\n SupportedUrgency,\n} from './constants';\nimport type {\n GenerateRequestOptions,\n PushSubscription,\n WebPushConfig,\n WebPushRequestDetails,\n} from './types';\nimport {base64url} from './utils/base64url';\nimport {encryptAes128GcmBody, encryptAesGcmLegacy} from './crypto/webpush-encryption';\nimport {GenerateHeaders, Validate} from \"./vapid\";\n\nexport class WebPushError extends Error {\n constructor(message: string, public readonly response: Response) {\n super(message);\n }\n}\n\nfunction validateTopic(topic: string): void {\n if (!base64url.validate(topic)) throw new Error('Topic contains invalid characters; must be URL-safe base64 chars.');\n if (topic.length > 32) throw new Error('Topic must be <= 32 characters.');\n}\n\nfunction isGcmEndpoint(endpoint: string): boolean {\n return endpoint.startsWith('https://android.googleapis.com/gcm/send');\n}\n\nfunction isFcmEndpoint(endpoint: string): boolean {\n return endpoint.startsWith('https://fcm.googleapis.com/fcm/send') || endpoint.includes('fcm.googleapis.com');\n}\n\nfunction getAudienceFromEndpoint(endpoint: string): string {\n return new URL(endpoint).origin;\n}\n\nfunction assertSupportedEncoding(e: SupportedContentEncoding): void {\n if (!Object.values(SupportedContentEncoding).includes(e)) throw new Error(`Unsupported content encoding: ${e}`);\n}\n\nfunction assertSupportedUrgency(u: SupportedUrgency): void {\n if (!Object.values(SupportedUrgency).includes(u)) throw new Error(`Unsupported urgency: ${u}`);\n}\n\nexport class WebPush {\n constructor(public readonly config: WebPushConfig) {\n Validate.subject(config.vapid.subject);\n Validate.privateKey(config.vapid.privateKey);\n Validate.publicKey(config.vapid.publicKey);\n\n if (config.gcm?.apiKey != null && config.gcm.apiKey.length === 0) {\n throw new Error('The GCM/FCM API Key should be a non-empty string, null, or undefined.');\n }\n }\n\n /**\n * Generate the request (endpoint + fetch init) to send a push message.\n *\n * - For `aes128gcm`, this produces an RFC8188 payload body (header block + encrypted records).\n * - For Web Push, defaults to a single record per RFC8291; multi-record requires `allowMultipleRecords: true`.\n */\n generateRequest(\n subscription: PushSubscription,\n payload?: string | Buffer | Uint8Array | null,\n options?: GenerateRequestOptions\n ): WebPushRequestDetails {\n if (!subscription?.endpoint || typeof subscription.endpoint !== 'string' || subscription.endpoint.length === 0) {\n throw new Error('You must pass a subscription with a valid endpoint URL.');\n }\n\n const timeToLive = options?.TTL ?? DEFAULT_TTL;\n if (!Number.isFinite(timeToLive) || timeToLive < 0) throw new Error('TTL must be a number >= 0.');\n\n const contentEncoding = options?.contentEncoding ?? SupportedContentEncoding.AES_128_GCM;\n assertSupportedEncoding(contentEncoding);\n\n const urgency = options?.urgency ?? SupportedUrgency.NORMAL;\n assertSupportedUrgency(urgency);\n\n const topic = options?.topic;\n if (topic) validateTopic(topic);\n\n const allowMultipleRecords = options?.allowMultipleRecords ?? false;\n const rs = options?.rs ?? DEFAULT_RS;\n if (!Number.isInteger(rs) || rs < MIN_RS) throw new Error(`rs must be an integer >= ${MIN_RS}.`);\n\n const finalRecordPadding = options?.finalRecordPadding ?? 0;\n\n const extraHeaders = options?.headers ?? {};\n const currentGcmKey = options?.gcmAPIKey ?? this.config.gcm?.apiKey ?? null;\n\n const vapidDetails = options?.vapidDetails === undefined ? this.config.vapid : options.vapidDetails;\n\n const headers: Record<string, string> = {\n ...extraHeaders,\n // Keep protocol-critical defaults authoritative even when custom headers are supplied.\n TTL: String(timeToLive),\n Urgency: urgency,\n };\n\n if (topic) headers.Topic = topic;\n\n let body: Buffer | undefined;\n\n const hasPayload = payload != null && payload !== '';\n if (hasPayload) {\n if (!subscription.keys?.p256dh || !subscription.keys?.auth) {\n throw new Error(\"To send a payload, the subscription must include 'keys.p256dh' and 'keys.auth'.\");\n }\n\n const payloadBuf =\n Buffer.isBuffer(payload) ? payload :\n payload instanceof Uint8Array ? Buffer.from(payload) :\n Buffer.from(payload, 'utf8');\n\n headers['Content-Type'] = 'application/octet-stream';\n\n if (contentEncoding === SupportedContentEncoding.AES_128_GCM) {\n const encBody = encryptAes128GcmBody({\n p256dh: subscription.keys.p256dh,\n auth: subscription.keys.auth,\n payload: payloadBuf,\n rs,\n allowMultipleRecords,\n finalRecordPadding,\n });\n\n body = encBody;\n headers['Content-Encoding'] = SupportedContentEncoding.AES_128_GCM;\n headers['Content-Length'] = String(encBody.length);\n } else {\n const enc = encryptAesGcmLegacy({\n p256dh: subscription.keys.p256dh,\n auth: subscription.keys.auth,\n payload: payloadBuf,\n });\n\n body = enc.ciphertext;\n headers['Content-Encoding'] = SupportedContentEncoding.AES_GCM;\n headers['Content-Length'] = String(enc.ciphertext.length);\n headers['Encryption'] = `salt=${enc.saltB64Url}`;\n headers['Crypto-Key'] = `dh=${base64url.toString(enc.localPublicKey)}`;\n }\n } else {\n headers['Content-Length'] = '0';\n }\n\n // Auth: GCM/FCM key OR VAPID\n const endpoint = subscription.endpoint;\n const gcm = isGcmEndpoint(endpoint);\n const fcm = isFcmEndpoint(endpoint);\n\n if (gcm) {\n if (currentGcmKey) headers.Authorization = `key=${currentGcmKey}`;\n else console.warn('GCM endpoint detected but no GCM API key provided.');\n } else if (vapidDetails) {\n const audience = getAudienceFromEndpoint(endpoint);\n const vapidHeaders = GenerateHeaders({\n audience,\n subject: vapidDetails.subject,\n publicKey: vapidDetails.publicKey,\n privateKey: vapidDetails.privateKey,\n contentEncoding,\n });\n\n const auth = vapidHeaders.get('Authorization');\n if (auth) headers.Authorization = auth;\n\n const ck = vapidHeaders.get('Crypto-Key');\n if (ck && contentEncoding === SupportedContentEncoding.AES_GCM) {\n headers['Crypto-Key'] = headers['Crypto-Key'] ? `${headers['Crypto-Key']}; ${ck}` : ck;\n }\n } else if (fcm && currentGcmKey) {\n headers.Authorization = `key=${currentGcmKey}`;\n }\n\n const init: RequestInit = {\n method: 'POST',\n headers,\n body: body as any,\n };\n\n return {endpoint, init};\n }\n\n /**\n * Method to send notification to subscribed device\n * @param subscription\n * @param payload\n * @param options\n */\n async notify(\n subscription: PushSubscription,\n payload?: string | Buffer | Uint8Array | null,\n options?: GenerateRequestOptions & { throwOnInvalidResponse?: boolean },\n ): Promise<Response> {\n const {endpoint, init} = this.generateRequest(subscription, payload, options);\n const res = await fetch(endpoint, init);\n if (!res.ok && options?.throwOnInvalidResponse) throw new WebPushError('Received unexpected response code', res);\n return res;\n }\n}\n"],"names":["SupportedContentEncoding","SupportedUrgency","DEFAULT_TTL","DEFAULT_RS","MIN_RS","base64url","input","obj","DEFAULT_EXPIRATION_SECONDS","MAX_EXPIRATION_SECONDS","getFutureExpirationTimestamp","numSeconds","future","Validate","subject","u","publicKey","pk","privateKey","expiration","maxExp","audience","a","GenerateKeys","ecdh","crypto","pub","priv","publicKeyToJwk","publicKeyB64Url","privateKeyToJwk","privateKeyB64Url","d","derToJose","derSig","keySizeBytes","off","seqLen","n","rLen","r","sLen","rPad","sPad","signJwtES256","payload","header","encodedHeader","encodedPayload","signingInput","x","y","keyObject","joseSig","encodedSig","GenerateHeaders","exp","jwt","headers","u16be","b","u32be","xorBytes","out","i","seq96be","seq","hi","lo","buildPlaintextRecords","params","message","rs","allowMultipleRecords","finalRecordPadding","maxPlain","maxDataPerFullRecord","records","offset","remaining","isLast","dataLen","data","delimiter","paddingLen","encryptRecords","cek","baseNonce","plaintextRecords","nonce","cipher","enc","hmacSha256","key","assertSubscriptionKeys","p256dh","auth","p256dhBytes","encryptAes128GcmBody","uaPublic","authSecret","asPublic","ecdhSecret","salt","prkKey","keyInfo","ikm","prk","cekInfo","nonceInfo","encryptedRecords","encryptAesGcmLegacy","receiverPub","senderPub","padLen","plaintext","tag","WebPushError","response","validateTopic","topic","isGcmEndpoint","endpoint","isFcmEndpoint","getAudienceFromEndpoint","assertSupportedEncoding","assertSupportedUrgency","WebPush","config","subscription","options","timeToLive","contentEncoding","urgency","extraHeaders","currentGcmKey","vapidDetails","body","payloadBuf","encBody","gcm","fcm","vapidHeaders","ck","init","res"],"mappings":"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"}
1
+ {"version":3,"file":"index.es.js","names":[],"sources":["../src/constants.ts","../src/utils/base64url.ts","../src/vapid.ts","../src/utils/binary.ts","../src/crypto/rfc8188.ts","../src/crypto/webpush-encryption.ts","../src/webpush.ts"],"sourcesContent":["'use strict';\n\nexport enum SupportedContentEncoding {\n AES_128_GCM = 'aes128gcm',\n}\n\nexport enum SupportedUrgency {\n VERY_LOW = 'very-low',\n LOW = 'low',\n NORMAL = 'normal',\n HIGH = 'high',\n}\n\n/** Default TTL is 4 weeks (seconds). */\nexport const DEFAULT_TTL = 2419200;\n\n/**\n * Default record size (RFC8188 `rs`) for aes128gcm.\n * For Web Push (RFC8291), a single record is required, but `rs` still appears in the body header.\n */\nexport const DEFAULT_RS = 4096;\n\n/** Minimum valid `rs` for aes128gcm per RFC8188 (must be >= 18). */\nexport const MIN_RS = 18;\n","'use strict';\n\nexport const base64url = {\n validate(input: string): boolean {\n // URL-safe base64 without \"=\" padding\n return /^[A-Za-z0-9\\-_]+$/.test(input);\n },\n toBuffer(input: string): Buffer {\n return Buffer.from(input, 'base64url');\n },\n toString(input: Buffer | Uint8Array | string): string {\n const buf = typeof input === 'string' ? Buffer.from(input, 'utf8') : Buffer.from(input);\n return buf.toString('base64url');\n },\n toJSON(obj: any) {\n return this.toString(Buffer.from(JSON.stringify(obj), 'utf8'));\n }\n};\n","'use strict';\n\nimport crypto from 'crypto';\nimport {base64url} from './utils/base64url';\nimport {SupportedContentEncoding} from './constants';\n\nconst DEFAULT_EXPIRATION_SECONDS = 12 * 60 * 60;\nconst MAX_EXPIRATION_SECONDS = 24 * 60 * 60;\n\nfunction getFutureExpirationTimestamp(numSeconds: number): number {\n const future = new Date();\n future.setSeconds(future.getSeconds() + numSeconds);\n return Math.floor(future.getTime() / 1000);\n}\n\nexport const Validate = {\n subject(subject: string | URL): void {\n if (!subject) throw new Error('No subject set in VAPID details.');\n const u = typeof subject === 'string' ? new URL(subject) : subject;\n if (!['https:', 'mailto:'].includes(u.protocol)) {\n throw new Error(`VAPID subject must be https: or mailto:. Got: ${u.toString()}`);\n }\n if (u.hostname === 'localhost') {\n console.warn('VAPID subject points to localhost; some push services may reject this.');\n }\n },\n\n publicKey(publicKey: string): void {\n if (!publicKey) throw new Error('No VAPID public key set.');\n if (typeof publicKey !== 'string' || !base64url.validate(publicKey)) {\n throw new Error('VAPID public key must be base64url without \"=\" padding.');\n }\n const pk = base64url.toBuffer(publicKey);\n if (pk.length !== 65 || pk[0] !== 0x04) {\n throw new Error('VAPID public key must decode to 65-byte uncompressed P-256.');\n }\n },\n\n privateKey(privateKey: string): void {\n if (!privateKey) throw new Error('No VAPID private key set.');\n if (typeof privateKey !== 'string' || !base64url.validate(privateKey)) {\n throw new Error('VAPID private key must be base64url without \"=\" padding.');\n }\n const sk = base64url.toBuffer(privateKey);\n if (sk.length !== 32) throw new Error('VAPID private key must decode to 32 bytes.');\n },\n\n expiration(expiration: number): void {\n if (!Number.isInteger(expiration)) throw new Error('VAPID expiration must be an integer.');\n if (expiration < 0) throw new Error('VAPID expiration must be positive.');\n const maxExp = getFutureExpirationTimestamp(MAX_EXPIRATION_SECONDS);\n if (expiration >= maxExp) throw new Error('VAPID expiration exceeds 24 hours.');\n },\n\n audience(audience: string | URL): void {\n if (!audience) throw new Error('No audience set for VAPID.');\n const a = typeof audience === 'string' ? new URL(audience) : audience;\n if (!a.origin || a.origin === 'null') {\n throw new Error(`VAPID audience must be an origin. Got: ${a.toString()}`);\n }\n },\n};\n\nexport function GenerateKeys(): { publicKey: string; privateKey: string } {\n const ecdh = crypto.createECDH('prime256v1');\n ecdh.generateKeys();\n\n let pub = ecdh.getPublicKey();\n let priv = ecdh.getPrivateKey();\n\n if (priv.length < 32) priv = Buffer.concat([Buffer.alloc(32 - priv.length, 0x00), priv]);\n if (pub.length < 65) pub = Buffer.concat([Buffer.alloc(65 - pub.length, 0x00), pub]);\n\n return {publicKey: base64url.toString(pub), privateKey: base64url.toString(priv)};\n}\n\nfunction publicKeyToJwk(publicKeyB64Url: string): { x: string; y: string } {\n const pub = base64url.toBuffer(publicKeyB64Url);\n if (pub.length !== 65 || pub[0] !== 0x04) throw new Error('Invalid uncompressed P-256 public key.');\n return {x: base64url.toString(pub.subarray(1, 33)), y: base64url.toString(pub.subarray(33, 65))};\n}\n\nfunction privateKeyToJwk(privateKeyB64Url: string): string {\n const d = base64url.toBuffer(privateKeyB64Url);\n if (d.length !== 32) throw new Error('Invalid P-256 private key scalar.');\n return base64url.toString(d);\n}\n\nfunction derToJose(derSig: Buffer, keySizeBytes = 32): Buffer {\n let off = 0;\n if (derSig[off++] !== 0x30) throw new Error('Invalid DER signature (expected SEQUENCE).');\n\n let seqLen = derSig[off++];\n if (seqLen & 0x80) {\n const n = seqLen & 0x7f;\n off += n;\n }\n\n if (derSig[off++] !== 0x02) throw new Error('Invalid DER signature (expected INTEGER r).');\n const rLen = derSig[off++];\n let r = derSig.subarray(off, off + rLen);\n off += rLen;\n\n if (derSig[off++] !== 0x02) throw new Error('Invalid DER signature (expected INTEGER s).');\n const sLen = derSig[off++];\n let s = derSig.subarray(off, off + sLen);\n\n if (r[0] === 0x00) r = r.subarray(1);\n if (s[0] === 0x00) s = s.subarray(1);\n\n if (r.length > keySizeBytes || s.length > keySizeBytes) throw new Error('Invalid DER signature size.');\n const rPad = Buffer.concat([Buffer.alloc(keySizeBytes - r.length, 0x00), r]);\n const sPad = Buffer.concat([Buffer.alloc(keySizeBytes - s.length, 0x00), s]);\n return Buffer.concat([rPad, sPad]);\n}\n\nfunction signJwtES256(payload: Record<string, unknown>, privateKeyB64Url: string, publicKeyB64Url: string): string {\n const header = {typ: 'JWT', alg: 'ES256'};\n const encodedHeader = base64url.toJSON(header);\n const encodedPayload = base64url.toJSON(payload);\n const signingInput = `${encodedHeader}.${encodedPayload}`;\n\n const {x, y} = publicKeyToJwk(publicKeyB64Url);\n const d = privateKeyToJwk(privateKeyB64Url);\n\n const keyObject = crypto.createPrivateKey({\n key: {kty: 'EC', crv: 'P-256', x, y, d},\n format: 'jwk',\n });\n\n const derSig = crypto.sign('sha256', Buffer.from(signingInput, 'utf8'), {\n key: keyObject,\n dsaEncoding: 'der',\n });\n\n const joseSig = derToJose(derSig, 32);\n const encodedSig = base64url.toString(joseSig);\n return `${signingInput}.${encodedSig}`;\n}\n\nexport function GenerateHeaders(input: {\n audience: string | URL;\n subject: string | URL;\n publicKey: string;\n privateKey: string;\n contentEncoding: SupportedContentEncoding;\n expiration?: number;\n}): Headers {\n Validate.audience(input.audience);\n Validate.subject(input.subject);\n Validate.publicKey(input.publicKey);\n Validate.privateKey(input.privateKey);\n\n const audience = (typeof input.audience === 'string' ? new URL(input.audience) : input.audience).origin;\n const subject = (typeof input.subject === 'string' ? new URL(input.subject) : input.subject).toString();\n\n const exp = input.expiration ?? getFutureExpirationTimestamp(DEFAULT_EXPIRATION_SECONDS);\n Validate.expiration(exp);\n\n const jwt = signJwtES256({aud: audience, exp, sub: subject}, input.privateKey, input.publicKey);\n\n const headers = new Headers();\n if (input.contentEncoding !== SupportedContentEncoding.AES_128_GCM) {\n throw new Error(`Unsupported content encoding: ${input.contentEncoding}`);\n }\n headers.set('Authorization', `vapid t=${jwt}, k=${input.publicKey}`);\n return headers;\n}\n\n","'use strict';\n\n/** Write UInt16BE. */\nexport function u16be(n: number): Buffer {\n const b = Buffer.alloc(2);\n b.writeUInt16BE(n, 0);\n return b;\n}\n\n/** Write UInt32BE. */\nexport function u32be(n: number): Buffer {\n const b = Buffer.alloc(4);\n b.writeUInt32BE(n >>> 0, 0);\n return b;\n}\n\nexport function xorBytes(a: Buffer, b: Buffer): Buffer {\n if (a.length !== b.length) throw new Error('xorBytes: length mismatch');\n const out = Buffer.alloc(a.length);\n for (let i = 0; i < a.length; i++) out[i] = a[i] ^ b[i];\n return out;\n}\n\n/**\n * Convert a record sequence number into a 96-bit (12-octet) big-endian buffer.\n * RFC8188 defines SEQ as a 96-bit unsigned integer starting at 0.\n */\nexport function seq96be(seq: number): Buffer {\n if (!Number.isSafeInteger(seq) || seq < 0) throw new Error('SEQ must be a non-negative safe integer');\n const out = Buffer.alloc(12, 0x00);\n\n // Write into low 64 bits; high 32 bits remain zero.\n const hi = Math.floor(seq / 0x100000000);\n const lo = seq >>> 0;\n out.writeUInt32BE(hi >>> 0, 4);\n out.writeUInt32BE(lo, 8);\n return out;\n}\n","'use strict';\n\nimport crypto from 'crypto';\nimport { MIN_RS } from '../constants';\nimport { xorBytes, seq96be } from '../utils/binary';\n\n/**\n * Build RFC8188 plaintext records from message bytes.\n *\n * Record plaintext length for full records is `rs - 16` because AES-GCM adds a 16-byte tag.\n * The padding delimiter must be present in every record:\n * - 0x01 for all non-last records\n * - 0x02 for the last record\n */\nexport function buildPlaintextRecords(params: {\n message: Buffer;\n rs: number;\n allowMultipleRecords: boolean;\n finalRecordPadding: number;\n}): Buffer[] {\n const { message, rs, allowMultipleRecords, finalRecordPadding } = params;\n\n if (!Number.isInteger(rs) || rs < MIN_RS) {\n throw new Error(`Invalid rs; must be an integer >= ${MIN_RS}.`);\n }\n\n const maxPlain = rs - 16; // AES-GCM tag length is 16\n if (maxPlain <= 1) throw new Error('Invalid rs (too small)');\n\n const maxDataPerFullRecord = maxPlain - 1; // reserve delimiter\n\n if (!allowMultipleRecords && message.length > maxDataPerFullRecord) {\n throw new Error(\n `Payload too large for a single RFC8188 record: ${message.length} bytes. ` +\n `Max is ${maxDataPerFullRecord} bytes for rs=${rs}.`\n );\n }\n\n if (!Number.isInteger(finalRecordPadding) || finalRecordPadding < 0) {\n throw new Error('finalRecordPadding must be a non-negative integer');\n }\n\n const records: Buffer[] = [];\n let offset = 0;\n\n while (offset < message.length) {\n const remaining = message.length - offset;\n const isLast = remaining <= maxDataPerFullRecord;\n\n const dataLen = isLast ? remaining : maxDataPerFullRecord;\n const data = message.subarray(offset, offset + dataLen);\n offset += dataLen;\n\n const delimiter = Buffer.from([isLast ? 0x02 : 0x01]);\n\n if (!isLast) {\n // Full-sized non-last record.\n const paddingLen = maxPlain - (data.length + 1);\n records.push(Buffer.concat([data, delimiter, Buffer.alloc(paddingLen, 0x00)]));\n } else {\n // Last record can be partial and may include padding after delimiter.\n records.push(Buffer.concat([data, delimiter, Buffer.alloc(finalRecordPadding, 0x00)]));\n }\n }\n\n // Empty message -> final record with only delimiter (0x02)\n if (message.length === 0) records.push(Buffer.from([0x02]));\n\n return records;\n}\n\n/**\n * Encrypt RFC8188 plaintext records using AES-128-GCM.\n *\n * Nonce per record: nonce = baseNonce XOR SEQ (96-bit big-endian SEQ starting at 0).\n * AAD is empty.\n */\nexport function encryptRecords(params: {\n cek: Buffer; // 16 bytes\n baseNonce: Buffer; // 12 bytes\n plaintextRecords: Buffer[];\n}): Buffer {\n const { cek, baseNonce, plaintextRecords } = params;\n if (cek.length !== 16) throw new Error('CEK must be 16 bytes');\n if (baseNonce.length !== 12) throw new Error('baseNonce must be 12 bytes');\n\n const out: Buffer[] = [];\n for (let i = 0; i < plaintextRecords.length; i++) {\n const nonce = xorBytes(baseNonce, seq96be(i));\n const cipher = crypto.createCipheriv('aes-128-gcm', cek, nonce);\n\n const enc = Buffer.concat([cipher.update(plaintextRecords[i]), cipher.final()]);\n out.push(Buffer.concat([enc, cipher.getAuthTag()]));\n }\n return Buffer.concat(out);\n}\n","'use strict';\n\nimport crypto from 'crypto';\nimport { base64url } from '../utils/base64url';\nimport { u32be } from '../utils/binary';\nimport { buildPlaintextRecords, encryptRecords } from './rfc8188';\n\nfunction hmacSha256(key: Buffer, data: Buffer): Buffer {\n return crypto.createHmac('sha256', key).update(data).digest();\n}\n\nfunction assertSubscriptionKeys(p256dh: string, auth: string): void {\n if (!p256dh || typeof p256dh !== 'string') throw new Error('subscription.keys.p256dh must be a string');\n if (!auth || typeof auth !== 'string') throw new Error('subscription.keys.auth must be a string');\n\n const p256dhBytes = base64url.toBuffer(p256dh);\n if (p256dhBytes.length !== 65 || p256dhBytes[0] !== 0x04) {\n throw new Error('subscription.keys.p256dh must decode to a 65-byte uncompressed P-256 public key');\n }\n\n const authBytes = base64url.toBuffer(auth);\n if (authBytes.length < 16) throw new Error('subscription.keys.auth must decode to at least 16 bytes');\n}\n\n/**\n * Encrypt a push payload using RFC8291 + RFC8188 with aes128gcm.\n *\n * Returns the full RFC8188 body:\n * salt(16) || rs(4) || idlen(1) || keyid(=sender pubkey) || encryptedRecords\n */\nexport function encryptAes128GcmBody(params: {\n p256dh: string;\n auth: string;\n payload: Buffer;\n rs: number;\n allowMultipleRecords: boolean;\n finalRecordPadding: number;\n}): Buffer {\n assertSubscriptionKeys(params.p256dh, params.auth);\n\n const uaPublic = base64url.toBuffer(params.p256dh);\n const authSecret = base64url.toBuffer(params.auth);\n\n // Sender ephemeral key\n const ecdh = crypto.createECDH('prime256v1');\n ecdh.generateKeys();\n const asPublic = ecdh.getPublicKey(); // 65 bytes\n const ecdhSecret = ecdh.computeSecret(uaPublic);\n\n // RFC8291: random salt\n const salt = crypto.randomBytes(16);\n\n // RFC8291: PRK_key = HMAC(auth_secret, ecdh_secret)\n const prkKey = hmacSha256(authSecret, ecdhSecret);\n\n // key_info = \"WebPush: info\" || 0x00 || ua_public || as_public\n const keyInfo = Buffer.concat([Buffer.from('WebPush: info', 'utf8'), Buffer.from([0x00]), uaPublic, asPublic]);\n\n // IKM = HMAC(PRK_key, key_info || 0x01)\n const ikm = hmacSha256(prkKey, Buffer.concat([keyInfo, Buffer.from([0x01])]));\n\n // RFC8188: PRK = HMAC(salt, IKM)\n const prk = hmacSha256(salt, ikm);\n\n // RFC8188: CEK / nonce derivation\n const cekInfo = Buffer.concat([Buffer.from('Content-Encoding: aes128gcm', 'utf8'), Buffer.from([0x00])]);\n const nonceInfo = Buffer.concat([Buffer.from('Content-Encoding: nonce', 'utf8'), Buffer.from([0x00])]);\n\n const cek = hmacSha256(prk, Buffer.concat([cekInfo, Buffer.from([0x01])])).subarray(0, 16);\n const baseNonce = hmacSha256(prk, Buffer.concat([nonceInfo, Buffer.from([0x01])])).subarray(0, 12);\n\n const plaintextRecords = buildPlaintextRecords({\n message: params.payload,\n rs: params.rs,\n allowMultipleRecords: params.allowMultipleRecords,\n finalRecordPadding: params.finalRecordPadding,\n });\n\n const encryptedRecords = encryptRecords({ cek, baseNonce, plaintextRecords });\n\n // RFC8188 header: salt || rs || idlen || keyid\n if (asPublic.length !== 65) throw new Error('Sender public key must be 65 bytes');\n\n return Buffer.concat([salt, u32be(params.rs), Buffer.from([asPublic.length]), asPublic, encryptedRecords]);\n}\n","'use strict';\n\nimport {\n DEFAULT_RS,\n DEFAULT_TTL,\n MIN_RS,\n SupportedContentEncoding,\n SupportedUrgency,\n} from './constants';\nimport type {\n GenerateRequestOptions,\n PushSubscription,\n WebPushConfig,\n WebPushRequestDetails,\n} from './types';\nimport {base64url} from './utils/base64url';\nimport {encryptAes128GcmBody} from './crypto/webpush-encryption';\nimport {GenerateHeaders, Validate} from \"./vapid\";\n\nexport class WebPushError extends Error {\n constructor(message: string, public readonly response: Response) {\n super(message);\n }\n}\n\nfunction validateTopic(topic: string): void {\n if (!base64url.validate(topic)) throw new Error('Topic contains invalid characters; must be URL-safe base64 chars.');\n if (topic.length > 32) throw new Error('Topic must be <= 32 characters.');\n}\n\nfunction isGcmEndpoint(endpoint: string): boolean {\n return endpoint.startsWith('https://android.googleapis.com/gcm/send');\n}\n\nfunction isFcmEndpoint(endpoint: string): boolean {\n return endpoint.startsWith('https://fcm.googleapis.com/fcm/send') || endpoint.includes('fcm.googleapis.com');\n}\n\nfunction getAudienceFromEndpoint(endpoint: string): string {\n return new URL(endpoint).origin;\n}\n\nfunction assertSupportedEncoding(e: SupportedContentEncoding): void {\n if (!Object.values(SupportedContentEncoding).includes(e)) throw new Error(`Unsupported content encoding: ${e}`);\n}\n\nfunction assertSupportedUrgency(u: SupportedUrgency): void {\n if (!Object.values(SupportedUrgency).includes(u)) throw new Error(`Unsupported urgency: ${u}`);\n}\n\nexport class WebPush {\n constructor(public readonly config: WebPushConfig) {\n Validate.subject(config.vapid.subject);\n Validate.privateKey(config.vapid.privateKey);\n Validate.publicKey(config.vapid.publicKey);\n\n if (config.gcm?.apiKey != null && config.gcm.apiKey.length === 0) {\n throw new Error('The GCM/FCM API Key should be a non-empty string, null, or undefined.');\n }\n }\n\n /**\n * Generate the request (endpoint + fetch init) to send a push message.\n *\n * - For `aes128gcm`, this produces an RFC8188 payload body (header block + encrypted records).\n * - For Web Push, defaults to a single record per RFC8291; multi-record requires `allowMultipleRecords: true`.\n */\n generateRequest(\n subscription: PushSubscription,\n payload?: string | Buffer | Uint8Array | null,\n options?: GenerateRequestOptions\n ): WebPushRequestDetails {\n if (!subscription?.endpoint || typeof subscription.endpoint !== 'string' || subscription.endpoint.length === 0) {\n throw new Error('You must pass a subscription with a valid endpoint URL.');\n }\n\n const timeToLive = options?.TTL ?? DEFAULT_TTL;\n if (!Number.isFinite(timeToLive) || timeToLive < 0) throw new Error('TTL must be a number >= 0.');\n\n const contentEncoding = options?.contentEncoding ?? SupportedContentEncoding.AES_128_GCM;\n assertSupportedEncoding(contentEncoding);\n\n const urgency = options?.urgency ?? SupportedUrgency.NORMAL;\n assertSupportedUrgency(urgency);\n\n const topic = options?.topic;\n if (topic) validateTopic(topic);\n\n const allowMultipleRecords = options?.allowMultipleRecords ?? false;\n const rs = options?.rs ?? DEFAULT_RS;\n if (!Number.isInteger(rs) || rs < MIN_RS) throw new Error(`rs must be an integer >= ${MIN_RS}.`);\n\n const finalRecordPadding = options?.finalRecordPadding ?? 0;\n\n const extraHeaders = options?.headers ?? {};\n const currentGcmKey = options?.gcmAPIKey ?? this.config.gcm?.apiKey ?? null;\n\n const vapidDetails = options?.vapidDetails === undefined ? this.config.vapid : options.vapidDetails;\n\n const headers: Record<string, string> = {\n ...extraHeaders,\n // Keep protocol-critical defaults authoritative even when custom headers are supplied.\n TTL: String(timeToLive),\n Urgency: urgency,\n };\n\n if (topic) headers.Topic = topic;\n\n let body: Buffer | undefined;\n\n const hasPayload = payload != null && payload !== '';\n if (hasPayload) {\n if (!subscription.keys?.p256dh || !subscription.keys?.auth) {\n throw new Error(\"To send a payload, the subscription must include 'keys.p256dh' and 'keys.auth'.\");\n }\n\n const payloadBuf =\n Buffer.isBuffer(payload) ? payload :\n payload instanceof Uint8Array ? Buffer.from(payload) :\n Buffer.from(payload, 'utf8');\n\n headers['Content-Type'] = 'application/octet-stream';\n\n const encBody = encryptAes128GcmBody({\n p256dh: subscription.keys.p256dh,\n auth: subscription.keys.auth,\n payload: payloadBuf,\n rs,\n allowMultipleRecords,\n finalRecordPadding,\n });\n\n body = encBody;\n headers['Content-Encoding'] = SupportedContentEncoding.AES_128_GCM;\n headers['Content-Length'] = String(encBody.length);\n } else {\n headers['Content-Length'] = '0';\n }\n\n // Auth: GCM/FCM key OR VAPID\n const endpoint = subscription.endpoint;\n const gcm = isGcmEndpoint(endpoint);\n const fcm = isFcmEndpoint(endpoint);\n\n if (gcm) {\n if (currentGcmKey) headers.Authorization = `key=${currentGcmKey}`;\n else console.warn('GCM endpoint detected but no GCM API key provided.');\n } else if (vapidDetails) {\n const audience = getAudienceFromEndpoint(endpoint);\n const vapidHeaders = GenerateHeaders({\n audience,\n subject: vapidDetails.subject,\n publicKey: vapidDetails.publicKey,\n privateKey: vapidDetails.privateKey,\n contentEncoding,\n });\n\n const auth = vapidHeaders.get('Authorization');\n if (auth) headers.Authorization = auth;\n } else if (fcm && currentGcmKey) {\n headers.Authorization = `key=${currentGcmKey}`;\n }\n\n const init: RequestInit = {\n method: 'POST',\n headers,\n body: body as any,\n };\n\n return {endpoint, init};\n }\n\n /**\n * Method to send notification to subscribed device\n * @param subscription\n * @param payload\n * @param options\n */\n async notify(\n subscription: PushSubscription,\n payload?: string | Buffer | Uint8Array | null,\n options?: GenerateRequestOptions & { throwOnInvalidResponse?: boolean },\n ): Promise<Response> {\n const {endpoint, init} = this.generateRequest(subscription, payload, options);\n const res = await fetch(endpoint, init);\n if (!res.ok && options?.throwOnInvalidResponse) throw new WebPushError('Received unexpected response code', res);\n return res;\n }\n}\n"],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -27,7 +27,7 @@ export type GenerateRequestOptions = {
27
27
  urgency?: SupportedUrgency;
28
28
  topic?: string;
29
29
  /**
30
- * RFC8188 compliance knob:
30
+ * RFC8188 compliance knob (aes128gcm only):
31
31
  * - Web Push RFC8291 requires a single record; this stays `false` by default.
32
32
  * - Set to `true` only if you intentionally want multi-record RFC8188 payload bodies.
33
33
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-webpush",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "A dependency-free Web Push implementation for Node.js",
5
5
  "main": "./dist/index.es.js",
6
6
  "module": "./dist/index.es.js",
@@ -40,10 +40,13 @@
40
40
  "author": "Alexander Slaa",
41
41
  "license": "Apache-2.0",
42
42
  "type": "module",
43
+ "engines": {
44
+ "node": ">=20"
45
+ },
43
46
  "bugs": {
44
- "url": "https://github.com/AlexanderSlaa/node-webpush/issues"
47
+ "url": "https://github.com/sourceregistry/node-webpush/issues"
45
48
  },
46
- "homepage": "https://alexanderslaa.github.io/node-webpush/",
49
+ "homepage": "https://sourceregistry.github.io/node-webpush/",
47
50
  "publishConfig": {
48
51
  "access": "public"
49
52
  },
@@ -52,16 +55,16 @@
52
55
  "@semantic-release/commit-analyzer": "^13.0.1",
53
56
  "@semantic-release/git": "^10.0.1",
54
57
  "@semantic-release/npm": "^13.1.5",
55
- "@semantic-release/release-notes-generator": "^14.1.0",
56
- "@types/node": "^25.3.5",
57
- "@vitest/coverage-v8": "^4.0.18",
58
- "@vitest/ui": "^4.0.18",
59
- "tsx": "^4.20.4",
60
- "typescript": "^5.9.3",
61
- "vite": "^7.3.1",
62
- "vite-plugin-dts": "^4.5.4",
63
- "vitest": "^4.0.18",
64
- "typedoc": "^0.28.17"
58
+ "@semantic-release/release-notes-generator": "^14.1.1",
59
+ "@types/node": "^26.2.0",
60
+ "@vitest/coverage-v8": "^4.1.11",
61
+ "@vitest/ui": "^4.1.11",
62
+ "tsx": "^4.23.12",
63
+ "typedoc": "^0.28.20",
64
+ "typescript": "^6.0.3",
65
+ "vite": "^8.2.2",
66
+ "vite-plugin-dts": "^5.0.3",
67
+ "vitest": "^4.1.11"
65
68
  },
66
69
  "release": {
67
70
  "branches": [