mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 16:14:07 +00:00
fix: call no relay from the worker in no-third-party mode, and handle several accounts in one push side by side
This commit is contained in:
@@ -295,4 +295,26 @@ describe('nothing new to say', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('a push naming several accounts, one server slow', () => {
|
||||
it('does not make the others wait out its deadline', async () => {
|
||||
const started: string[] = [];
|
||||
let release = () => {};
|
||||
const slow = new Promise<void>((resolve) => { release = resolve; });
|
||||
const fetchSlow = vi.fn(async (input: string) => {
|
||||
const url = new URL(input, 'https://mail.example.com');
|
||||
const account = url.searchParams.get('accountId') ?? '';
|
||||
started.push(account);
|
||||
if (account === 'slow') await slow;
|
||||
return { ok: true, json: async () => ({ unreadTotal: 1, slot: 0, email: mail(`m-${account}`) }) };
|
||||
});
|
||||
const w = loadWorker(() => null, { globals: { fetch: fetchSlow } });
|
||||
const done = w.push({ kind: 'jmap-state-change', accountId: 'slow', changed: { slow: { EmailDelivery: 's' }, fast: { EmailDelivery: 's' } } });
|
||||
await vi.waitFor(() => expect(started).toEqual(['slow', 'fast']));
|
||||
await vi.waitFor(() => expect(w.shade.has('bulwark-mail:fast')).toBe(true));
|
||||
release();
|
||||
await done;
|
||||
expect(w.shade.size).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
// -- end of cases --
|
||||
|
||||
@@ -7,7 +7,7 @@ import { describe, expect, it, vi } from 'vitest';
|
||||
// in a VM with a minimal ServiceWorkerGlobalScope and fire the event.
|
||||
const SW_SOURCE = readFileSync(join(process.cwd(), 'public', 'sw.js'), 'utf8');
|
||||
|
||||
function loadWorker(tabs: { postMessage: (msg: unknown) => void }[], registrations?: unknown) {
|
||||
function loadWorker(tabs: { postMessage: (msg: unknown) => void }[], registrations?: unknown, config: { noThirdPartyTraffic?: boolean } | null = {}) {
|
||||
const listeners = new Map<string, (event: unknown) => void>();
|
||||
const newSubscription = { toJSON: () => ({ endpoint: 'https://push.example/new', keys: { p256dh: 'p', auth: 'a' } }) };
|
||||
const subscribe = vi.fn(async (_options: { userVisibleOnly?: boolean; applicationServerKey?: Uint8Array }) => newSubscription);
|
||||
@@ -23,8 +23,12 @@ function loadWorker(tabs: { postMessage: (msg: unknown) => void }[], registratio
|
||||
self,
|
||||
atob,
|
||||
caches: { open: async () => ({ match: async () => (registrations ? { json: async () => registrations } : undefined) }) },
|
||||
fetch: vi.fn(async (url: string, init: { body: string }) => {
|
||||
relayCalls.push({ url, body: JSON.parse(init.body) });
|
||||
fetch: vi.fn(async (url: string, init?: { body: string }) => {
|
||||
if (url.endsWith('/api/config')) {
|
||||
if (!config) throw new TypeError('offline');
|
||||
return { ok: true, json: async () => config };
|
||||
}
|
||||
relayCalls.push({ url, body: JSON.parse(init!.body) });
|
||||
return { ok: true };
|
||||
}),
|
||||
URL, URLSearchParams, Response, Date, console, Uint8Array,
|
||||
@@ -69,6 +73,18 @@ describe('service worker pushsubscriptionchange', () => {
|
||||
expect(relayCalls).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('calls no relay while third-party traffic is off: the page moves it', async () => {
|
||||
const { fire, relayCalls, newSubscription } = loadWorker([], REGISTRATIONS, { noThirdPartyTraffic: true });
|
||||
await fire('pushsubscriptionchange', { oldSubscription: null, newSubscription });
|
||||
expect(relayCalls).toEqual([]);
|
||||
});
|
||||
|
||||
it('calls no relay when it cannot find out', async () => {
|
||||
const { fire, relayCalls, newSubscription } = loadWorker([], REGISTRATIONS, null);
|
||||
await fire('pushsubscriptionchange', { oldSubscription: null, newSubscription });
|
||||
expect(relayCalls).toEqual([]);
|
||||
});
|
||||
|
||||
it('calls no relay for a browser where push was never on', async () => {
|
||||
const { fire, relayCalls, newSubscription } = loadWorker([]);
|
||||
await fire('pushsubscriptionchange', { oldSubscription: null, newSubscription });
|
||||
|
||||
+17
-3
@@ -603,7 +603,9 @@ async function handlePush(event) {
|
||||
const withNewMail = emailIds.length > 0 || !changed
|
||||
? []
|
||||
: Object.keys(changed).filter((id) => changed[id] && typeof changed[id] === "object" && "EmailDelivery" in changed[id]);
|
||||
for (const accountId of withNewMail.length > 0 ? withNewMail : [named]) {
|
||||
// Side by side: each has its own preview deadline, and Safari wants the
|
||||
// notifications within 30 s of the push.
|
||||
await Promise.all((withNewMail.length > 0 ? withNewMail : [named]).map(async (accountId) => {
|
||||
try {
|
||||
await notifyAccount(accountId, emailIds, accountLabel);
|
||||
} catch (_) {
|
||||
@@ -617,7 +619,7 @@ async function handlePush(event) {
|
||||
data: { kind: "mail-list", accountId },
|
||||
});
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
async function notifyAccount(accountId, emailIds, accountLabel) {
|
||||
@@ -1000,6 +1002,18 @@ async function readRelayRegistrations() {
|
||||
}
|
||||
}
|
||||
|
||||
/** No-third-party mode may have come on since the page left a relay here. Unknown counts as no. */
|
||||
async function mayCallRelays() {
|
||||
try {
|
||||
const res = await fetch(`${BASE_PATH}/api/config`, { cache: "no-store" });
|
||||
if (!res.ok) return false;
|
||||
const config = await res.json();
|
||||
return config.noThirdPartyTraffic !== true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function applicationServerKeyOf(base64Url) {
|
||||
const padded = base64Url + "=".repeat((4 - (base64Url.length % 4)) % 4);
|
||||
const raw = atob(padded.replace(/-/g, "+").replace(/_/g, "/"));
|
||||
@@ -1031,7 +1045,7 @@ async function handlePushSubscriptionChange(event) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (subscription && registrations && typeof subscription.toJSON === "function") {
|
||||
if (subscription && registrations && typeof subscription.toJSON === "function" && (await mayCallRelays())) {
|
||||
const json = subscription.toJSON();
|
||||
for (const entry of Object.values(registrations.entries)) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user