Initial independent version of CrAPP
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
/** Invio di notifiche web push (senza payload) firmate con VAPID, compatibile con il runtime edge. */
|
||||
|
||||
function base64UrlDecode(value: string): Uint8Array {
|
||||
const padding = "=".repeat((4 - (value.length % 4)) % 4);
|
||||
const base64 = (value + padding).replace(/-/g, "+").replace(/_/g, "/");
|
||||
const raw = atob(base64);
|
||||
const out = new Uint8Array(raw.length);
|
||||
for (let i = 0; i < raw.length; i += 1) out[i] = raw.charCodeAt(i);
|
||||
return out;
|
||||
}
|
||||
|
||||
function base64UrlEncode(bytes: Uint8Array): string {
|
||||
let binary = "";
|
||||
bytes.forEach((b) => {
|
||||
binary += String.fromCharCode(b);
|
||||
});
|
||||
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
||||
}
|
||||
|
||||
function encodeJson(value: unknown): string {
|
||||
return base64UrlEncode(new TextEncoder().encode(JSON.stringify(value)));
|
||||
}
|
||||
|
||||
async function importaChiave(publicKey: string, privateKey: string) {
|
||||
const pub = base64UrlDecode(publicKey);
|
||||
return crypto.subtle.importKey(
|
||||
"jwk",
|
||||
{
|
||||
kty: "EC",
|
||||
crv: "P-256",
|
||||
d: privateKey,
|
||||
x: base64UrlEncode(pub.slice(1, 33)),
|
||||
y: base64UrlEncode(pub.slice(33, 65)),
|
||||
ext: true,
|
||||
},
|
||||
{ name: "ECDSA", namedCurve: "P-256" },
|
||||
false,
|
||||
["sign"],
|
||||
);
|
||||
}
|
||||
|
||||
async function creaVapidJwt(audience: string, subject: string, publicKey: string, privateKey: string) {
|
||||
const header = encodeJson({ typ: "JWT", alg: "ES256" });
|
||||
const payload = encodeJson({
|
||||
aud: audience,
|
||||
exp: Math.floor(Date.now() / 1000) + 12 * 60 * 60,
|
||||
sub: subject,
|
||||
});
|
||||
const chiave = await importaChiave(publicKey, privateKey);
|
||||
const firma = await crypto.subtle.sign(
|
||||
{ name: "ECDSA", hash: "SHA-256" },
|
||||
chiave,
|
||||
new TextEncoder().encode(`${header}.${payload}`),
|
||||
);
|
||||
return `${header}.${payload}.${base64UrlEncode(new Uint8Array(firma))}`;
|
||||
}
|
||||
|
||||
/** Invia una notifica "vuota": il service worker recupera poi il testo aggiornato. */
|
||||
export async function inviaPush(endpoint: string): Promise<number> {
|
||||
const publicKey = process.env["VAPID_PUBLIC_KEY"];
|
||||
const privateKey = process.env["VAPID_PRIVATE_KEY"];
|
||||
const subject = process.env["VAPID_SUBJECT"] ?? "mailto:crapp@crapvolley.it";
|
||||
if (!publicKey || !privateKey) throw new Error("Chiavi VAPID non configurate");
|
||||
|
||||
const audience = new URL(endpoint).origin;
|
||||
const jwt = await creaVapidJwt(audience, subject, publicKey, privateKey);
|
||||
|
||||
const res = await fetch(endpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
TTL: "86400",
|
||||
Authorization: `vapid t=${jwt}, k=${publicKey}`,
|
||||
"Content-Length": "0",
|
||||
},
|
||||
});
|
||||
return res.status;
|
||||
}
|
||||
Reference in New Issue
Block a user