After every login (and when notifications are turned on) the app creates or reuses a per-device VAPID key, subscribes with the browser's PushManager and registers the subscription with the server's mod_push2; logging out or turning notifications off unregisters it. sw.js shows each push as a notification (sender, text, calls, attachments) and counts it onto the icon badge; the app resets that count when it runs. Match profile "important": mod_push2's "archived" profiles never match (it looks for "stana-id"). Tested end to end against a local Prosody with mod_push2 and the blunix relay module: decrypted payload, valid VAPID JWT, also with the session hibernated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
41 lines
1.9 KiB
TypeScript
41 lines
1.9 KiB
TypeScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { createVapidKey, enableElement, fromBase64, toBase64 } from "../src/xmpp/push.ts";
|
|
|
|
const bytes = (n: number, v: number) => new Uint8Array(n).fill(v).buffer;
|
|
|
|
test("base64 round trip, also from base64url", () => {
|
|
const data = Uint8Array.from([0, 1, 250, 251, 252, 253, 254, 255]);
|
|
assert.deepEqual(fromBase64(toBase64(data)), data);
|
|
assert.deepEqual(fromBase64("-_8"), Uint8Array.from([251, 255]));
|
|
});
|
|
|
|
test("VAPID key: P-256 public key and PKCS#8 private key", async () => {
|
|
const key = await createVapidKey();
|
|
const pub = fromBase64(key.publicRaw);
|
|
assert.equal(pub.length, 65);
|
|
assert.equal(pub[0], 4); // uncompressed point
|
|
assert.ok(fromBase64(key.privatePkcs8).length > 100);
|
|
});
|
|
|
|
test("enable request for mod_push2", async () => {
|
|
const vapid = await createVapidKey();
|
|
const el = enableElement(
|
|
"push.0ff.space",
|
|
{ endpoint: "https://web.push.apple.com/QGuXmPmr5a", p256dh: bytes(65, 7), auth: bytes(16, 9) },
|
|
vapid,
|
|
);
|
|
assert.equal(el.attrs.xmlns, "urn:xmpp:push2:0");
|
|
assert.equal(el.getChildText("service"), "push.0ff.space");
|
|
assert.equal(el.getChildText("client"), "https://web.push.apple.com/QGuXmPmr5a");
|
|
const match = el.getChild("match")!;
|
|
assert.equal(match.attrs.profile, "urn:xmpp:push2:match:important");
|
|
const send = match.getChild("send", "urn:xmpp:push2:send:sce+rfc8291+rfc8292:0")!;
|
|
// mod_push2 appends "==" itself, so no padding here.
|
|
assert.equal(send.getChildText("ua-public"), toBase64(bytes(65, 7)).replace(/=+$/, ""));
|
|
assert.equal(send.getChildText("auth-secret"), toBase64(bytes(16, 9)).replace(/=+$/, ""));
|
|
assert.equal(send.getChildText("jwt-alg"), "ES256");
|
|
assert.equal(send.getChildText("jwt-key"), vapid.privatePkcs8);
|
|
const aud = send.getChildren("jwt-claim").find((c) => c.attrs.name === "aud");
|
|
assert.equal(aud?.text(), "https://web.push.apple.com");
|
|
});
|