Skip to content

Commit e76f2cd

Browse files
author
hclsys
committed
fix(push-web): default VAPID subject to https://openclaw.ai instead of mailto:openclaw@localhost (#83134)
Apple Web Push (Safari, iOS PWA) rejects VAPID JWTs whose `sub` is `mailto:openclaw@localhost` with `BadJwtToken`. The upstream `web-push` README documents this restriction: VAPID subjects must be either `https:` URLs or `mailto:` with a real domain — not `localhost`. The result on first install with no `OPENCLAW_VAPID_SUBJECT` override was that iOS PWA subscribers silently never received pushes. Switch the default to `https://openclaw.ai` (the project's public URL) so Apple's push endpoint accepts the JWT out of the box. Operators who want a real contact mailbox can still override with `OPENCLAW_VAPID_SUBJECT=mailto:ops@example.com`. Test updates: - `setVapidDetails` direct assertion now expects the new default. - The "generates and persists VAPID keys" regression assertion is relaxed to "valid VAPID subject (https: or mailto:) AND not localhost", which is the actual contract the issue describes.
1 parent 2c9f68f commit e76f2cd

2 files changed

Lines changed: 14 additions & 3 deletions

File tree

src/infra/push-web.test.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,12 @@ describe("resolveVapidKeys", () => {
5858
const keys = await resolveVapidKeys(tmpDir);
5959
expect(keys.publicKey).toBe("test-public-key-base64url");
6060
expect(keys.privateKey).toBe("test-private-key-base64url");
61-
expect(keys.subject).toMatch(/^mailto:/);
61+
// #83134: default must be a valid VAPID `sub` Apple Web Push accepts.
62+
// `mailto:openclaw@localhost` was rejected with BadJwtToken; the
63+
// upstream web-push README documents `https:` or `mailto:` with a
64+
// real domain.
65+
expect(keys.subject).toMatch(/^(https:\/\/|mailto:)/);
66+
expect(keys.subject).not.toMatch(/localhost/);
6267

6368
// Second call returns same keys.
6469
const keys2 = await resolveVapidKeys(tmpDir);
@@ -211,7 +216,7 @@ describe("sending", () => {
211216
expect(result.ok).toBe(true);
212217
expect(vi.mocked(webPush.setVapidDetails)).toHaveBeenCalledTimes(1);
213218
expect(vi.mocked(webPush.setVapidDetails)).toHaveBeenCalledWith(
214-
"mailto:openclaw@localhost",
219+
"https://openclaw.ai",
215220
"test-public-key-base64url",
216221
"test-private-key-base64url",
217222
);

src/infra/push-web.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,13 @@ const WEB_PUSH_STATE_FILENAME = "push/web-push-subscriptions.json";
3636
const VAPID_KEYS_FILENAME = "push/vapid-keys.json";
3737
const MAX_ENDPOINT_LENGTH = 2048;
3838
const MAX_KEY_LENGTH = 512;
39-
const DEFAULT_VAPID_SUBJECT = "mailto:openclaw@localhost";
39+
// #83134: Apple Web Push (Safari + iOS PWA) rejects VAPID `aud` JWTs whose
40+
// `sub` is `mailto:openclaw@localhost` with `BadJwtToken`. The upstream
41+
// `web-push` README documents the same restriction. Use the project's public
42+
// `https://openclaw.ai` as the contact URL so Apple's push endpoint accepts
43+
// the JWT out of the box on first install; operators who want a real contact
44+
// can still override with `OPENCLAW_VAPID_SUBJECT=mailto:...`.
45+
const DEFAULT_VAPID_SUBJECT = "https://openclaw.ai";
4046

4147
const withLock = createAsyncLock();
4248

0 commit comments

Comments
 (0)