node-webpush 1.1.1 → 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 +25 -9
- package/dist/constants.d.ts +0 -1
- package/dist/crypto/webpush-encryption.d.ts +0 -13
- package/dist/index.cjs.js +2 -2
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +304 -308
- package/dist/index.es.js.map +1 -1
- package/dist/types.d.ts +7 -1
- package/dist/webpush.d.ts +1 -0
- package/package.json +16 -13
package/dist/index.es.js
CHANGED
|
@@ -1,331 +1,327 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
1
|
+
import e from "crypto";
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var t = Object.defineProperty, n = (e, n) => {
|
|
4
|
+
let r = {};
|
|
5
|
+
for (var i in e) t(r, i, {
|
|
6
|
+
get: e[i],
|
|
7
|
+
enumerable: !0
|
|
8
|
+
});
|
|
9
|
+
return n || t(r, Symbol.toStringTag, { value: "Module" }), r;
|
|
10
|
+
}, r = /* @__PURE__ */ function(e) {
|
|
11
|
+
return e.AES_128_GCM = "aes128gcm", e;
|
|
12
|
+
}({}), i = /* @__PURE__ */ function(e) {
|
|
13
|
+
return e.VERY_LOW = "very-low", e.LOW = "low", e.NORMAL = "normal", e.HIGH = "high", e;
|
|
14
|
+
}({}), a = 2419200, o = 4096, s = 18, c = {
|
|
15
|
+
validate(e) {
|
|
16
|
+
return /^[A-Za-z0-9\-_]+$/.test(e);
|
|
17
|
+
},
|
|
18
|
+
toBuffer(e) {
|
|
19
|
+
return Buffer.from(e, "base64url");
|
|
20
|
+
},
|
|
21
|
+
toString(e) {
|
|
22
|
+
return (typeof e == "string" ? Buffer.from(e, "utf8") : Buffer.from(e)).toString("base64url");
|
|
23
|
+
},
|
|
24
|
+
toJSON(e) {
|
|
25
|
+
return this.toString(Buffer.from(JSON.stringify(e), "utf8"));
|
|
26
|
+
}
|
|
27
|
+
}, l = /* @__PURE__ */ n({
|
|
28
|
+
GenerateHeaders: () => y,
|
|
29
|
+
GenerateKeys: () => m,
|
|
30
|
+
Validate: () => p
|
|
31
|
+
}), u = 43200, d = 86400;
|
|
32
|
+
function f(e) {
|
|
33
|
+
let t = /* @__PURE__ */ new Date();
|
|
34
|
+
return t.setSeconds(t.getSeconds() + e), Math.floor(t.getTime() / 1e3);
|
|
20
35
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
audience(e) {
|
|
50
|
-
if (!e) throw new Error("No audience set for VAPID.");
|
|
51
|
-
const t = typeof e == "string" ? new URL(e) : e;
|
|
52
|
-
if (!t.origin || t.origin === "null")
|
|
53
|
-
throw new Error(`VAPID audience must be an origin. Got: ${t.toString()}`);
|
|
54
|
-
}
|
|
36
|
+
var p = {
|
|
37
|
+
subject(e) {
|
|
38
|
+
if (!e) throw Error("No subject set in VAPID details.");
|
|
39
|
+
let t = typeof e == "string" ? new URL(e) : e;
|
|
40
|
+
if (!["https:", "mailto:"].includes(t.protocol)) throw Error(`VAPID subject must be https: or mailto:. Got: ${t.toString()}`);
|
|
41
|
+
t.hostname === "localhost" && console.warn("VAPID subject points to localhost; some push services may reject this.");
|
|
42
|
+
},
|
|
43
|
+
publicKey(e) {
|
|
44
|
+
if (!e) throw Error("No VAPID public key set.");
|
|
45
|
+
if (typeof e != "string" || !c.validate(e)) throw Error("VAPID public key must be base64url without \"=\" padding.");
|
|
46
|
+
let t = c.toBuffer(e);
|
|
47
|
+
if (t.length !== 65 || t[0] !== 4) throw Error("VAPID public key must decode to 65-byte uncompressed P-256.");
|
|
48
|
+
},
|
|
49
|
+
privateKey(e) {
|
|
50
|
+
if (!e) throw Error("No VAPID private key set.");
|
|
51
|
+
if (typeof e != "string" || !c.validate(e)) throw Error("VAPID private key must be base64url without \"=\" padding.");
|
|
52
|
+
if (c.toBuffer(e).length !== 32) throw Error("VAPID private key must decode to 32 bytes.");
|
|
53
|
+
},
|
|
54
|
+
expiration(e) {
|
|
55
|
+
if (!Number.isInteger(e)) throw Error("VAPID expiration must be an integer.");
|
|
56
|
+
if (e < 0) throw Error("VAPID expiration must be positive.");
|
|
57
|
+
if (e >= f(d)) throw Error("VAPID expiration exceeds 24 hours.");
|
|
58
|
+
},
|
|
59
|
+
audience(e) {
|
|
60
|
+
if (!e) throw Error("No audience set for VAPID.");
|
|
61
|
+
let t = typeof e == "string" ? new URL(e) : e;
|
|
62
|
+
if (!t.origin || t.origin === "null") throw Error(`VAPID audience must be an origin. Got: ${t.toString()}`);
|
|
63
|
+
}
|
|
55
64
|
};
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
function m() {
|
|
66
|
+
let t = e.createECDH("prime256v1");
|
|
67
|
+
t.generateKeys();
|
|
68
|
+
let n = t.getPublicKey(), r = t.getPrivateKey();
|
|
69
|
+
return r.length < 32 && (r = Buffer.concat([Buffer.alloc(32 - r.length, 0), r])), n.length < 65 && (n = Buffer.concat([Buffer.alloc(65 - n.length, 0), n])), {
|
|
70
|
+
publicKey: c.toString(n),
|
|
71
|
+
privateKey: c.toString(r)
|
|
72
|
+
};
|
|
61
73
|
}
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
74
|
+
function h(e) {
|
|
75
|
+
let t = c.toBuffer(e);
|
|
76
|
+
if (t.length !== 65 || t[0] !== 4) throw Error("Invalid uncompressed P-256 public key.");
|
|
77
|
+
return {
|
|
78
|
+
x: c.toString(t.subarray(1, 33)),
|
|
79
|
+
y: c.toString(t.subarray(33, 65))
|
|
80
|
+
};
|
|
66
81
|
}
|
|
67
|
-
function
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
function U(e, t = 32) {
|
|
73
|
-
let r = 0;
|
|
74
|
-
if (e[r++] !== 48) throw new Error("Invalid DER signature (expected SEQUENCE).");
|
|
75
|
-
let n = e[r++];
|
|
76
|
-
if (n & 128) {
|
|
77
|
-
const l = n & 127;
|
|
78
|
-
r += l;
|
|
79
|
-
}
|
|
80
|
-
if (e[r++] !== 2) throw new Error("Invalid DER signature (expected INTEGER r).");
|
|
81
|
-
const c = e[r++];
|
|
82
|
-
let o = e.subarray(r, r + c);
|
|
83
|
-
if (r += c, e[r++] !== 2) throw new Error("Invalid DER signature (expected INTEGER s).");
|
|
84
|
-
const i = e[r++];
|
|
85
|
-
let s = e.subarray(r, r + i);
|
|
86
|
-
if (o[0] === 0 && (o = o.subarray(1)), s[0] === 0 && (s = s.subarray(1)), o.length > t || s.length > t) throw new Error("Invalid DER signature size.");
|
|
87
|
-
const u = Buffer.concat([Buffer.alloc(t - o.length, 0), o]), d = Buffer.concat([Buffer.alloc(t - s.length, 0), s]);
|
|
88
|
-
return Buffer.concat([u, d]);
|
|
89
|
-
}
|
|
90
|
-
function O(e, t, r) {
|
|
91
|
-
const n = { typ: "JWT", alg: "ES256" }, c = f.toJSON(n), o = f.toJSON(e), i = `${c}.${o}`, { x: s, y: u } = G(r), d = j(t), l = m.createPrivateKey({
|
|
92
|
-
key: { kty: "EC", crv: "P-256", x: s, y: u, d },
|
|
93
|
-
format: "jwk"
|
|
94
|
-
}), y = m.sign("sha256", Buffer.from(i, "utf8"), {
|
|
95
|
-
key: l,
|
|
96
|
-
dsaEncoding: "der"
|
|
97
|
-
}), h = U(y, 32), g = f.toString(h);
|
|
98
|
-
return `${i}.${g}`;
|
|
99
|
-
}
|
|
100
|
-
function D(e) {
|
|
101
|
-
E.audience(e.audience), E.subject(e.subject), E.publicKey(e.publicKey), E.privateKey(e.privateKey);
|
|
102
|
-
const t = (typeof e.audience == "string" ? new URL(e.audience) : e.audience).origin, r = (typeof e.subject == "string" ? new URL(e.subject) : e.subject).toString(), n = e.expiration ?? T(_);
|
|
103
|
-
E.expiration(n);
|
|
104
|
-
const c = O({ aud: t, exp: n, sub: r }, e.privateKey, e.publicKey), o = new Headers();
|
|
105
|
-
if (e.contentEncoding === b.AES_128_GCM)
|
|
106
|
-
o.set("Authorization", `vapid t=${c}, k=${e.publicKey}`);
|
|
107
|
-
else if (e.contentEncoding === b.AES_GCM)
|
|
108
|
-
o.set("Authorization", `WebPush ${c}`), o.set("Crypto-Key", `p256ecdsa=${e.publicKey}`);
|
|
109
|
-
else
|
|
110
|
-
throw new Error(`Unsupported content encoding: ${e.contentEncoding}`);
|
|
111
|
-
return o;
|
|
112
|
-
}
|
|
113
|
-
const se = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
114
|
-
__proto__: null,
|
|
115
|
-
GenerateHeaders: D,
|
|
116
|
-
GenerateKeys: $,
|
|
117
|
-
Validate: E
|
|
118
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
119
|
-
function V(e) {
|
|
120
|
-
const t = Buffer.alloc(2);
|
|
121
|
-
return t.writeUInt16BE(e, 0), t;
|
|
82
|
+
function g(e) {
|
|
83
|
+
let t = c.toBuffer(e);
|
|
84
|
+
if (t.length !== 32) throw Error("Invalid P-256 private key scalar.");
|
|
85
|
+
return c.toString(t);
|
|
122
86
|
}
|
|
123
|
-
function
|
|
124
|
-
|
|
125
|
-
|
|
87
|
+
function _(e, t = 32) {
|
|
88
|
+
let n = 0;
|
|
89
|
+
if (e[n++] !== 48) throw Error("Invalid DER signature (expected SEQUENCE).");
|
|
90
|
+
let r = e[n++];
|
|
91
|
+
if (r & 128) {
|
|
92
|
+
let e = r & 127;
|
|
93
|
+
n += e;
|
|
94
|
+
}
|
|
95
|
+
if (e[n++] !== 2) throw Error("Invalid DER signature (expected INTEGER r).");
|
|
96
|
+
let i = e[n++], a = e.subarray(n, n + i);
|
|
97
|
+
if (n += i, e[n++] !== 2) throw Error("Invalid DER signature (expected INTEGER s).");
|
|
98
|
+
let o = e[n++], s = e.subarray(n, n + o);
|
|
99
|
+
if (a[0] === 0 && (a = a.subarray(1)), s[0] === 0 && (s = s.subarray(1)), a.length > t || s.length > t) throw Error("Invalid DER signature size.");
|
|
100
|
+
let c = Buffer.concat([Buffer.alloc(t - a.length, 0), a]), l = Buffer.concat([Buffer.alloc(t - s.length, 0), s]);
|
|
101
|
+
return Buffer.concat([c, l]);
|
|
126
102
|
}
|
|
127
|
-
function
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
103
|
+
function v(t, n, r) {
|
|
104
|
+
let i = `${c.toJSON({
|
|
105
|
+
typ: "JWT",
|
|
106
|
+
alg: "ES256"
|
|
107
|
+
})}.${c.toJSON(t)}`, { x: a, y: o } = h(r), s = g(n), l = e.createPrivateKey({
|
|
108
|
+
key: {
|
|
109
|
+
kty: "EC",
|
|
110
|
+
crv: "P-256",
|
|
111
|
+
x: a,
|
|
112
|
+
y: o,
|
|
113
|
+
d: s
|
|
114
|
+
},
|
|
115
|
+
format: "jwk"
|
|
116
|
+
}), u = _(e.sign("sha256", Buffer.from(i, "utf8"), {
|
|
117
|
+
key: l,
|
|
118
|
+
dsaEncoding: "der"
|
|
119
|
+
}), 32);
|
|
120
|
+
return `${i}.${c.toString(u)}`;
|
|
132
121
|
}
|
|
133
|
-
function
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
122
|
+
function y(e) {
|
|
123
|
+
p.audience(e.audience), p.subject(e.subject), p.publicKey(e.publicKey), p.privateKey(e.privateKey);
|
|
124
|
+
let t = (typeof e.audience == "string" ? new URL(e.audience) : e.audience).origin, n = (typeof e.subject == "string" ? new URL(e.subject) : e.subject).toString(), i = e.expiration ?? f(u);
|
|
125
|
+
p.expiration(i);
|
|
126
|
+
let a = v({
|
|
127
|
+
aud: t,
|
|
128
|
+
exp: i,
|
|
129
|
+
sub: n
|
|
130
|
+
}, e.privateKey, e.publicKey), o = new Headers();
|
|
131
|
+
if (e.contentEncoding !== r.AES_128_GCM) throw Error(`Unsupported content encoding: ${e.contentEncoding}`);
|
|
132
|
+
return o.set("Authorization", `vapid t=${a}, k=${e.publicKey}`), o;
|
|
137
133
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (o <= 1) throw new Error("Invalid rs (too small)");
|
|
144
|
-
const i = o - 1;
|
|
145
|
-
if (!n && t.length > i)
|
|
146
|
-
throw new Error(
|
|
147
|
-
`Payload too large for a single RFC8188 record: ${t.length} bytes. Max is ${i} bytes for rs=${r}.`
|
|
148
|
-
);
|
|
149
|
-
if (!Number.isInteger(c) || c < 0)
|
|
150
|
-
throw new Error("finalRecordPadding must be a non-negative integer");
|
|
151
|
-
const s = [];
|
|
152
|
-
let u = 0;
|
|
153
|
-
for (; u < t.length; ) {
|
|
154
|
-
const d = t.length - u, l = d <= i, y = l ? d : i, h = t.subarray(u, u + y);
|
|
155
|
-
u += y;
|
|
156
|
-
const g = Buffer.from([l ? 2 : 1]);
|
|
157
|
-
if (l)
|
|
158
|
-
s.push(Buffer.concat([h, g, Buffer.alloc(c, 0)]));
|
|
159
|
-
else {
|
|
160
|
-
const a = o - (h.length + 1);
|
|
161
|
-
s.push(Buffer.concat([h, g, Buffer.alloc(a, 0)]));
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return t.length === 0 && s.push(Buffer.from([2])), s;
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region src/utils/binary.ts
|
|
136
|
+
function b(e) {
|
|
137
|
+
let t = Buffer.alloc(4);
|
|
138
|
+
return t.writeUInt32BE(e >>> 0, 0), t;
|
|
165
139
|
}
|
|
166
|
-
function
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
for (let o = 0; o < n.length; o++) {
|
|
172
|
-
const i = F(r, W(o)), s = m.createCipheriv("aes-128-gcm", t, i), u = Buffer.concat([s.update(n[o]), s.final()]);
|
|
173
|
-
c.push(Buffer.concat([u, s.getAuthTag()]));
|
|
174
|
-
}
|
|
175
|
-
return Buffer.concat(c);
|
|
140
|
+
function x(e, t) {
|
|
141
|
+
if (e.length !== t.length) throw Error("xorBytes: length mismatch");
|
|
142
|
+
let n = Buffer.alloc(e.length);
|
|
143
|
+
for (let r = 0; r < e.length; r++) n[r] = e[r] ^ t[r];
|
|
144
|
+
return n;
|
|
176
145
|
}
|
|
177
|
-
function
|
|
178
|
-
|
|
146
|
+
function S(e) {
|
|
147
|
+
if (!Number.isSafeInteger(e) || e < 0) throw Error("SEQ must be a non-negative safe integer");
|
|
148
|
+
let t = Buffer.alloc(12, 0), n = Math.floor(e / 4294967296), r = e >>> 0;
|
|
149
|
+
return t.writeUInt32BE(n >>> 0, 4), t.writeUInt32BE(r, 8), t;
|
|
179
150
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/crypto/rfc8188.ts
|
|
153
|
+
function C(e) {
|
|
154
|
+
let { message: t, rs: n, allowMultipleRecords: r, finalRecordPadding: i } = e;
|
|
155
|
+
if (!Number.isInteger(n) || n < 18) throw Error("Invalid rs; must be an integer >= 18.");
|
|
156
|
+
let a = n - 16;
|
|
157
|
+
if (a <= 1) throw Error("Invalid rs (too small)");
|
|
158
|
+
let o = a - 1;
|
|
159
|
+
if (!r && t.length > o) throw Error(`Payload too large for a single RFC8188 record: ${t.length} bytes. Max is ${o} bytes for rs=${n}.`);
|
|
160
|
+
if (!Number.isInteger(i) || i < 0) throw Error("finalRecordPadding must be a non-negative integer");
|
|
161
|
+
let s = [], c = 0;
|
|
162
|
+
for (; c < t.length;) {
|
|
163
|
+
let e = t.length - c, n = e <= o, r = n ? e : o, l = t.subarray(c, c + r);
|
|
164
|
+
c += r;
|
|
165
|
+
let u = Buffer.from([n ? 2 : 1]);
|
|
166
|
+
if (n) s.push(Buffer.concat([
|
|
167
|
+
l,
|
|
168
|
+
u,
|
|
169
|
+
Buffer.alloc(i, 0)
|
|
170
|
+
]));
|
|
171
|
+
else {
|
|
172
|
+
let e = a - (l.length + 1);
|
|
173
|
+
s.push(Buffer.concat([
|
|
174
|
+
l,
|
|
175
|
+
u,
|
|
176
|
+
Buffer.alloc(e, 0)
|
|
177
|
+
]));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return t.length === 0 && s.push(Buffer.from([2])), s;
|
|
187
181
|
}
|
|
188
|
-
function
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (c.length !== 65) throw new Error("Sender public key must be 65 bytes");
|
|
199
|
-
return Buffer.concat([i, H(e.rs), Buffer.from([c.length]), c, P]);
|
|
182
|
+
function w(t) {
|
|
183
|
+
let { cek: n, baseNonce: r, plaintextRecords: i } = t;
|
|
184
|
+
if (n.length !== 16) throw Error("CEK must be 16 bytes");
|
|
185
|
+
if (r.length !== 12) throw Error("baseNonce must be 12 bytes");
|
|
186
|
+
let a = [];
|
|
187
|
+
for (let t = 0; t < i.length; t++) {
|
|
188
|
+
let o = x(r, S(t)), s = e.createCipheriv("aes-128-gcm", n, o), c = Buffer.concat([s.update(i[t]), s.final()]);
|
|
189
|
+
a.push(Buffer.concat([c, s.getAuthTag()]));
|
|
190
|
+
}
|
|
191
|
+
return Buffer.concat(a);
|
|
200
192
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const c = n.getPublicKey(), o = n.computeSecret(t), i = m.randomBytes(16), s = w(r, o), u = Buffer.concat([Buffer.from("WebPush: info", "utf8"), Buffer.from([0]), t, c]), d = w(s, Buffer.concat([u, Buffer.from([1])])), l = w(i, d), y = Buffer.concat([Buffer.from("Content-Encoding: aesgcm", "utf8"), Buffer.from([0])]), h = Buffer.concat([Buffer.from("Content-Encoding: nonce", "utf8"), Buffer.from([0])]), g = w(l, Buffer.concat([y, Buffer.from([1])])).subarray(0, 16), a = w(l, Buffer.concat([h, Buffer.from([1])])).subarray(0, 12), B = 0, P = Buffer.concat([V(B), Buffer.alloc(B, 0), e.payload]), v = m.createCipheriv("aes-128-gcm", g, a), A = Buffer.concat([v.update(P), v.final()]), S = v.getAuthTag();
|
|
206
|
-
return { saltB64Url: f.toString(i), localPublicKey: c, ciphertext: Buffer.concat([A, S]) };
|
|
193
|
+
//#endregion
|
|
194
|
+
//#region src/crypto/webpush-encryption.ts
|
|
195
|
+
function T(t, n) {
|
|
196
|
+
return e.createHmac("sha256", t).update(n).digest();
|
|
207
197
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
198
|
+
function E(e, t) {
|
|
199
|
+
if (!e || typeof e != "string") throw Error("subscription.keys.p256dh must be a string");
|
|
200
|
+
if (!t || typeof t != "string") throw Error("subscription.keys.auth must be a string");
|
|
201
|
+
let n = c.toBuffer(e);
|
|
202
|
+
if (n.length !== 65 || n[0] !== 4) throw Error("subscription.keys.p256dh must decode to a 65-byte uncompressed P-256 public key");
|
|
203
|
+
if (c.toBuffer(t).length < 16) throw Error("subscription.keys.auth must decode to at least 16 bytes");
|
|
212
204
|
}
|
|
213
|
-
function
|
|
214
|
-
|
|
215
|
-
|
|
205
|
+
function D(t) {
|
|
206
|
+
E(t.p256dh, t.auth);
|
|
207
|
+
let n = c.toBuffer(t.p256dh), r = c.toBuffer(t.auth), i = e.createECDH("prime256v1");
|
|
208
|
+
i.generateKeys();
|
|
209
|
+
let a = i.getPublicKey(), o = i.computeSecret(n), s = e.randomBytes(16), l = T(r, o), u = Buffer.concat([
|
|
210
|
+
Buffer.from("WebPush: info", "utf8"),
|
|
211
|
+
Buffer.from([0]),
|
|
212
|
+
n,
|
|
213
|
+
a
|
|
214
|
+
]), d = T(s, T(l, Buffer.concat([u, Buffer.from([1])]))), f = Buffer.concat([Buffer.from("Content-Encoding: aes128gcm", "utf8"), Buffer.from([0])]), p = Buffer.concat([Buffer.from("Content-Encoding: nonce", "utf8"), Buffer.from([0])]), m = w({
|
|
215
|
+
cek: T(d, Buffer.concat([f, Buffer.from([1])])).subarray(0, 16),
|
|
216
|
+
baseNonce: T(d, Buffer.concat([p, Buffer.from([1])])).subarray(0, 12),
|
|
217
|
+
plaintextRecords: C({
|
|
218
|
+
message: t.payload,
|
|
219
|
+
rs: t.rs,
|
|
220
|
+
allowMultipleRecords: t.allowMultipleRecords,
|
|
221
|
+
finalRecordPadding: t.finalRecordPadding
|
|
222
|
+
})
|
|
223
|
+
});
|
|
224
|
+
if (a.length !== 65) throw Error("Sender public key must be 65 bytes");
|
|
225
|
+
return Buffer.concat([
|
|
226
|
+
s,
|
|
227
|
+
b(t.rs),
|
|
228
|
+
Buffer.from([a.length]),
|
|
229
|
+
a,
|
|
230
|
+
m
|
|
231
|
+
]);
|
|
216
232
|
}
|
|
217
|
-
|
|
218
|
-
|
|
233
|
+
//#endregion
|
|
234
|
+
//#region src/webpush.ts
|
|
235
|
+
var O = class extends Error {
|
|
236
|
+
constructor(e, t) {
|
|
237
|
+
super(e), this.response = t;
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
function k(e) {
|
|
241
|
+
if (!c.validate(e)) throw Error("Topic contains invalid characters; must be URL-safe base64 chars.");
|
|
242
|
+
if (e.length > 32) throw Error("Topic must be <= 32 characters.");
|
|
219
243
|
}
|
|
220
|
-
function
|
|
221
|
-
|
|
244
|
+
function A(e) {
|
|
245
|
+
return e.startsWith("https://android.googleapis.com/gcm/send");
|
|
222
246
|
}
|
|
223
|
-
function
|
|
224
|
-
|
|
247
|
+
function j(e) {
|
|
248
|
+
return e.startsWith("https://fcm.googleapis.com/fcm/send") || e.includes("fcm.googleapis.com");
|
|
225
249
|
}
|
|
226
|
-
function
|
|
227
|
-
|
|
250
|
+
function M(e) {
|
|
251
|
+
return new URL(e).origin;
|
|
228
252
|
}
|
|
229
|
-
function
|
|
230
|
-
|
|
253
|
+
function N(e) {
|
|
254
|
+
if (!Object.values(r).includes(e)) throw Error(`Unsupported content encoding: ${e}`);
|
|
231
255
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (this.config = t, E.subject(t.vapid.subject), E.privateKey(t.vapid.privateKey), E.publicKey(t.vapid.publicKey), t.gcm?.apiKey != null && t.gcm.apiKey.length === 0)
|
|
235
|
-
throw new Error("The GCM/FCM API Key should be a non-empty string, null, or undefined.");
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* Generate the request (endpoint + fetch init) to send a push message.
|
|
239
|
-
*
|
|
240
|
-
* - For `aes128gcm`, this produces an RFC8188 payload body (header block + encrypted records).
|
|
241
|
-
* - For Web Push, defaults to a single record per RFC8291; multi-record requires `allowMultipleRecords: true`.
|
|
242
|
-
*/
|
|
243
|
-
generateRequest(t, r, n) {
|
|
244
|
-
if (!t?.endpoint || typeof t.endpoint != "string" || t.endpoint.length === 0)
|
|
245
|
-
throw new Error("You must pass a subscription with a valid endpoint URL.");
|
|
246
|
-
const c = n?.TTL ?? L;
|
|
247
|
-
if (!Number.isFinite(c) || c < 0) throw new Error("TTL must be a number >= 0.");
|
|
248
|
-
const o = n?.contentEncoding ?? b.AES_128_GCM;
|
|
249
|
-
re(o);
|
|
250
|
-
const i = n?.urgency ?? R.NORMAL;
|
|
251
|
-
ne(i);
|
|
252
|
-
const s = n?.topic;
|
|
253
|
-
s && q(s);
|
|
254
|
-
const u = n?.allowMultipleRecords ?? !1, d = n?.rs ?? N;
|
|
255
|
-
if (!Number.isInteger(d) || d < k) throw new Error(`rs must be an integer >= ${k}.`);
|
|
256
|
-
const l = n?.finalRecordPadding ?? 0, y = n?.headers ?? {}, h = n?.gcmAPIKey ?? this.config.gcm?.apiKey ?? null, g = n?.vapidDetails === void 0 ? this.config.vapid : n.vapidDetails, a = {
|
|
257
|
-
...y,
|
|
258
|
-
// Keep protocol-critical defaults authoritative even when custom headers are supplied.
|
|
259
|
-
TTL: String(c),
|
|
260
|
-
Urgency: i
|
|
261
|
-
};
|
|
262
|
-
s && (a.Topic = s);
|
|
263
|
-
let B;
|
|
264
|
-
if (r != null && r !== "") {
|
|
265
|
-
if (!t.keys?.p256dh || !t.keys?.auth)
|
|
266
|
-
throw new Error("To send a payload, the subscription must include 'keys.p256dh' and 'keys.auth'.");
|
|
267
|
-
const I = Buffer.isBuffer(r) ? r : r instanceof Uint8Array ? Buffer.from(r) : Buffer.from(r, "utf8");
|
|
268
|
-
if (a["Content-Type"] = "application/octet-stream", o === b.AES_128_GCM) {
|
|
269
|
-
const p = X({
|
|
270
|
-
p256dh: t.keys.p256dh,
|
|
271
|
-
auth: t.keys.auth,
|
|
272
|
-
payload: I,
|
|
273
|
-
rs: d,
|
|
274
|
-
allowMultipleRecords: u,
|
|
275
|
-
finalRecordPadding: l
|
|
276
|
-
});
|
|
277
|
-
B = p, a["Content-Encoding"] = b.AES_128_GCM, a["Content-Length"] = String(p.length);
|
|
278
|
-
} else {
|
|
279
|
-
const p = Q({
|
|
280
|
-
p256dh: t.keys.p256dh,
|
|
281
|
-
auth: t.keys.auth,
|
|
282
|
-
payload: I
|
|
283
|
-
});
|
|
284
|
-
B = p.ciphertext, a["Content-Encoding"] = b.AES_GCM, a["Content-Length"] = String(p.ciphertext.length), a.Encryption = `salt=${p.saltB64Url}`, a["Crypto-Key"] = `dh=${f.toString(p.localPublicKey)}`;
|
|
285
|
-
}
|
|
286
|
-
} else
|
|
287
|
-
a["Content-Length"] = "0";
|
|
288
|
-
const v = t.endpoint, A = Z(v), S = ee(v);
|
|
289
|
-
if (A)
|
|
290
|
-
h ? a.Authorization = `key=${h}` : console.warn("GCM endpoint detected but no GCM API key provided.");
|
|
291
|
-
else if (g) {
|
|
292
|
-
const I = te(v), p = D({
|
|
293
|
-
audience: I,
|
|
294
|
-
subject: g.subject,
|
|
295
|
-
publicKey: g.publicKey,
|
|
296
|
-
privateKey: g.privateKey,
|
|
297
|
-
contentEncoding: o
|
|
298
|
-
}), C = p.get("Authorization");
|
|
299
|
-
C && (a.Authorization = C);
|
|
300
|
-
const K = p.get("Crypto-Key");
|
|
301
|
-
K && o === b.AES_GCM && (a["Crypto-Key"] = a["Crypto-Key"] ? `${a["Crypto-Key"]}; ${K}` : K);
|
|
302
|
-
} else S && h && (a.Authorization = `key=${h}`);
|
|
303
|
-
return { endpoint: v, init: {
|
|
304
|
-
method: "POST",
|
|
305
|
-
headers: a,
|
|
306
|
-
body: B
|
|
307
|
-
} };
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* Method to send notification to subscribed device
|
|
311
|
-
* @param subscription
|
|
312
|
-
* @param payload
|
|
313
|
-
* @param options
|
|
314
|
-
*/
|
|
315
|
-
async notify(t, r, n) {
|
|
316
|
-
const { endpoint: c, init: o } = this.generateRequest(t, r, n), i = await fetch(c, o);
|
|
317
|
-
if (!i.ok && n?.throwOnInvalidResponse) throw new Y("Received unexpected response code", i);
|
|
318
|
-
return i;
|
|
319
|
-
}
|
|
256
|
+
function P(e) {
|
|
257
|
+
if (!Object.values(i).includes(e)) throw Error(`Unsupported urgency: ${e}`);
|
|
320
258
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
259
|
+
var F = class {
|
|
260
|
+
constructor(e) {
|
|
261
|
+
if (this.config = e, p.subject(e.vapid.subject), p.privateKey(e.vapid.privateKey), p.publicKey(e.vapid.publicKey), e.gcm?.apiKey != null && e.gcm.apiKey.length === 0) throw Error("The GCM/FCM API Key should be a non-empty string, null, or undefined.");
|
|
262
|
+
if (e.fetch != null && typeof e.fetch != "function") throw Error("config.fetch must be a function.");
|
|
263
|
+
}
|
|
264
|
+
generateRequest(e, t, n) {
|
|
265
|
+
if (!e?.endpoint || typeof e.endpoint != "string" || e.endpoint.length === 0) throw Error("You must pass a subscription with a valid endpoint URL.");
|
|
266
|
+
let a = n?.TTL ?? 2419200;
|
|
267
|
+
if (!Number.isFinite(a) || a < 0) throw Error("TTL must be a number >= 0.");
|
|
268
|
+
let o = n?.contentEncoding ?? r.AES_128_GCM;
|
|
269
|
+
N(o);
|
|
270
|
+
let s = n?.urgency ?? i.NORMAL;
|
|
271
|
+
P(s);
|
|
272
|
+
let c = n?.topic;
|
|
273
|
+
c && k(c);
|
|
274
|
+
let l = n?.allowMultipleRecords ?? !1, u = n?.rs ?? 4096;
|
|
275
|
+
if (!Number.isInteger(u) || u < 18) throw Error("rs must be an integer >= 18.");
|
|
276
|
+
let d = n?.finalRecordPadding ?? 0, f = n?.headers ?? {}, p = n?.gcmAPIKey ?? this.config.gcm?.apiKey ?? null, m = n?.vapidDetails === void 0 ? this.config.vapid : n.vapidDetails, h = {
|
|
277
|
+
...f,
|
|
278
|
+
TTL: String(a),
|
|
279
|
+
Urgency: s
|
|
280
|
+
};
|
|
281
|
+
c && (h.Topic = c);
|
|
282
|
+
let g;
|
|
283
|
+
if (t != null && t !== "") {
|
|
284
|
+
if (!e.keys?.p256dh || !e.keys?.auth) throw Error("To send a payload, the subscription must include 'keys.p256dh' and 'keys.auth'.");
|
|
285
|
+
let n = Buffer.isBuffer(t) ? t : t instanceof Uint8Array ? Buffer.from(t) : Buffer.from(t, "utf8");
|
|
286
|
+
h["Content-Type"] = "application/octet-stream";
|
|
287
|
+
let i = D({
|
|
288
|
+
p256dh: e.keys.p256dh,
|
|
289
|
+
auth: e.keys.auth,
|
|
290
|
+
payload: n,
|
|
291
|
+
rs: u,
|
|
292
|
+
allowMultipleRecords: l,
|
|
293
|
+
finalRecordPadding: d
|
|
294
|
+
});
|
|
295
|
+
g = i, h["Content-Encoding"] = r.AES_128_GCM, h["Content-Length"] = String(i.length);
|
|
296
|
+
} else h["Content-Length"] = "0";
|
|
297
|
+
let _ = e.endpoint, v = A(_), b = j(_);
|
|
298
|
+
if (v) p ? h.Authorization = `key=${p}` : console.warn("GCM endpoint detected but no GCM API key provided.");
|
|
299
|
+
else if (m) {
|
|
300
|
+
let e = y({
|
|
301
|
+
audience: M(_),
|
|
302
|
+
subject: m.subject,
|
|
303
|
+
publicKey: m.publicKey,
|
|
304
|
+
privateKey: m.privateKey,
|
|
305
|
+
contentEncoding: o
|
|
306
|
+
}).get("Authorization");
|
|
307
|
+
e && (h.Authorization = e);
|
|
308
|
+
} else b && p && (h.Authorization = `key=${p}`);
|
|
309
|
+
return {
|
|
310
|
+
endpoint: _,
|
|
311
|
+
init: {
|
|
312
|
+
method: "POST",
|
|
313
|
+
headers: h,
|
|
314
|
+
body: g
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
async notify(e, t, n) {
|
|
319
|
+
let { endpoint: r, init: i } = this.generateRequest(e, t, n), a = await (n?.fetch ?? this.config.fetch ?? fetch)(r, i);
|
|
320
|
+
if (!a.ok && n?.throwOnInvalidResponse) throw new O("Received unexpected response code", a);
|
|
321
|
+
return a;
|
|
322
|
+
}
|
|
330
323
|
};
|
|
331
|
-
//#
|
|
324
|
+
//#endregion
|
|
325
|
+
export { o as DEFAULT_RS, a as DEFAULT_TTL, s as MIN_RS, r as SupportedContentEncoding, i as SupportedUrgency, l as VAPID, F as WebPush, O as WebPushError };
|
|
326
|
+
|
|
327
|
+
//# sourceMappingURL=index.es.js.map
|