// WebKit drops a push subscription after three pushes that show no // notification, so on an Apple endpoint every path through the worker's push // handler must end in one. Run: bun test tools/sw-push-quota.test.js import { test, expect, mock } from 'bun:test'; let respond = async () => true; mock.module('../src/nwc-respond.js', () => ({ respondFromBg: (...a) => respond(...a), tendRenewals: async () => {} })); mock.module('../src/nwc-lock.js', () => ({ withRequestLock: async (_id, fn) => fn() })); mock.module('../src/bg-autowithdraw.js', () => ({ bgAutoWithdraw: async () => {} })); mock.module('../src/dm-inbox.js', () => ({ allInboxes: async () => [{ muted: ['m'.repeat(64)] }], classifyDm: async () => ({ own: true }), shouldNotifyDm: () => false, classifyCall: async () => null, })); const listeners = {}; let endpoint = 'https://web.push.apple.com/abc'; let windows = []; const shown = [], closed = []; globalThis.self = { location: { hostname: 'v3.coinos.io', origin: 'https://v3.coinos.io' }, addEventListener: (t, f) => { listeners[t] = f; }, clients: { matchAll: async () => windows }, registration: { pushManager: { getSubscription: async () => ({ endpoint }) }, showNotification: async (title, o) => { shown.push({ title, ...o }); }, getNotifications: async ({ tag }) => shown.filter((n) => n.tag === tag).map((n) => ({ close: () => closed.push(n) })), }, }; await import('../src/sw-nwc.js'); async function push(data) { shown.length = 0; closed.length = 0; let done; listeners.push({ data: { json: () => data }, waitUntil: (p) => { done = p; } }); await done; return shown.map((n) => n.title); } const nwc = { type: 'nwc', servicePubkey: 'a'.repeat(64), event: { id: 'e'.repeat(64), kind: 23194 } }; const hidden = { visibilityState: 'hidden', postMessage: () => {} }; const visible = { visibilityState: 'visible', postMessage: () => {} }; test('apple: every quiet path shows the stand-in', async () => { endpoint = 'https://web.push.apple.com/abc'; windows = []; respond = async () => true; // a lookup answered, or another device took it expect(await push(nwc)).toEqual(['Coinos']); expect(await push({ type: 'notify', reason: 'dm', wrap: {} })).toEqual(['Coinos']); expect(await push({ type: 'notify', reason: 'reaction', from: 'm'.repeat(64) })).toEqual(['Coinos']); expect(await push({ type: 'notify', reason: 'call', wrapId: 'x' })).toEqual(['Coinos']); expect(await push({ type: 'weird' })).toEqual(['Coinos']); expect(closed.length).toBe(0); }); test('apple: an app on screen gets the stand-in closed at once', async () => { windows = [visible]; expect(await push({ type: 'notify', reason: 'payment', amountSat: 5 })).toEqual(['Coinos']); expect(closed.length).toBe(1); }); test('apple: a suspended background app does not swallow the request', async () => { windows = [hidden]; let asked = 0; respond = async () => { asked++; return { notify: { title: 'Payment sent', body: '' } }; }; expect(await push(nwc)).toEqual(['Payment sent']); expect(asked).toBe(1); }); test('elsewhere: quiet stays quiet', async () => { endpoint = 'https://fcm.googleapis.com/fcm/send/abc'; windows = []; respond = async () => true; expect(await push(nwc)).toEqual([]); windows = [hidden]; let asked = 0; respond = async () => { asked++; return true; }; expect(await push(nwc)).toEqual([]); expect(asked).toBe(0); // the open (if hidden) window answers on Android windows = []; respond = async () => false; expect(await push(nwc)).toEqual(['Payment request']); });