node-webpush 1.1.2 → 1.2.0
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 +17 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +2 -1
- package/dist/index.es.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/webpush.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -129,6 +129,7 @@ type WebPushConfig = {
|
|
|
129
129
|
subject: string | URL; // must be https: or mailto:
|
|
130
130
|
};
|
|
131
131
|
gcm?: { apiKey?: string | null };
|
|
132
|
+
fetch?: typeof fetch; // override the fetch notify() uses; defaults to global fetch
|
|
132
133
|
};
|
|
133
134
|
```
|
|
134
135
|
|
|
@@ -137,6 +138,22 @@ Constructing `WebPush` validates:
|
|
|
137
138
|
* VAPID subject format (`https:` or `mailto:`)
|
|
138
139
|
* VAPID key sizes and base64url encoding
|
|
139
140
|
* GCM/FCM key if provided (must be non-empty)
|
|
141
|
+
* `fetch` if provided (must be a function)
|
|
142
|
+
|
|
143
|
+
By default, `notify()` sends the request with the global `fetch`. Override it — for a
|
|
144
|
+
runtime without one, or to plug in retries/logging/a proxying fetch — via `config.fetch`
|
|
145
|
+
(applies to every call) or `options.fetch` on an individual `notify()` call (takes
|
|
146
|
+
precedence over `config.fetch`):
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
const webpush = new WebPush({
|
|
150
|
+
vapid: {/* ... */},
|
|
151
|
+
fetch: myInstrumentedFetch, // used by every notify() call unless overridden below
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// One-off override for a single call:
|
|
155
|
+
await webpush.notify(subscription, "hello", {fetch: myInstrumentedFetch});
|
|
156
|
+
```
|
|
140
157
|
|
|
141
158
|
---
|
|
142
159
|
|
package/dist/index.cjs.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
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;
|
|
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.`);if(e.fetch!=null&&typeof e.fetch!=`function`)throw Error(`config.fetch must be a function.`)}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(n?.fetch??this.config.fetch??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
2
|
//# sourceMappingURL=index.cjs.js.map
|
package/dist/index.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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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"}
|
|
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 if (config.fetch != null && typeof config.fetch !== 'function') {\n throw new Error('config.fetch must be a function.');\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; fetch?: typeof fetch },\n ): Promise<Response> {\n const {endpoint, init} = this.generateRequest(subscription, payload, options);\n const fetchImpl = options?.fetch ?? this.config.fetch ?? fetch;\n const res = await fetchImpl(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
|
@@ -259,6 +259,7 @@ function P(e) {
|
|
|
259
259
|
var F = class {
|
|
260
260
|
constructor(e) {
|
|
261
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
|
+
if (e.fetch != null && typeof e.fetch != "function") throw Error("config.fetch must be a function.");
|
|
262
263
|
}
|
|
263
264
|
generateRequest(e, t, n) {
|
|
264
265
|
if (!e?.endpoint || typeof e.endpoint != "string" || e.endpoint.length === 0) throw Error("You must pass a subscription with a valid endpoint URL.");
|
|
@@ -315,7 +316,7 @@ var F = class {
|
|
|
315
316
|
};
|
|
316
317
|
}
|
|
317
318
|
async notify(e, t, n) {
|
|
318
|
-
let { endpoint: r, init: i } = this.generateRequest(e, t, n), a = await fetch(r, i);
|
|
319
|
+
let { endpoint: r, init: i } = this.generateRequest(e, t, n), a = await (n?.fetch ?? this.config.fetch ?? fetch)(r, i);
|
|
319
320
|
if (!a.ok && n?.throwOnInvalidResponse) throw new O("Received unexpected response code", a);
|
|
320
321
|
return a;
|
|
321
322
|
}
|
package/dist/index.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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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"}
|
|
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 if (config.fetch != null && typeof config.fetch !== 'function') {\n throw new Error('config.fetch must be a function.');\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; fetch?: typeof fetch },\n ): Promise<Response> {\n const {endpoint, init} = this.generateRequest(subscription, payload, options);\n const fetchImpl = options?.fetch ?? this.config.fetch ?? fetch;\n const res = await fetchImpl(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
|
@@ -17,6 +17,12 @@ export type WebPushConfig = {
|
|
|
17
17
|
gcm?: {
|
|
18
18
|
apiKey?: string | null;
|
|
19
19
|
};
|
|
20
|
+
/**
|
|
21
|
+
* Override for the `fetch` implementation `notify()` uses to send the request.
|
|
22
|
+
* Defaults to the global `fetch`. Useful in runtimes without one, or to plug in
|
|
23
|
+
* a wrapped/instrumented fetch (retries, logging, proxying, etc.).
|
|
24
|
+
*/
|
|
25
|
+
fetch?: typeof fetch;
|
|
20
26
|
};
|
|
21
27
|
export type GenerateRequestOptions = {
|
|
22
28
|
headers?: Record<string, string>;
|
package/dist/webpush.d.ts
CHANGED