mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-05 08:16:22 +00:00
fix: keep push notifications off screen with email notifications off or do not disturb on
This commit is contained in:
@@ -17,18 +17,18 @@ describe('writePushPreferences', () => {
|
||||
|
||||
it('stores the preference under the key the worker reads', async () => {
|
||||
const { writePushPreferences, pushPreferencesKey } = await import('../push-preferences');
|
||||
await writePushPreferences({ sound: false });
|
||||
await writePushPreferences({ sound: false, show: true });
|
||||
expect(put).toHaveBeenCalledTimes(1);
|
||||
const [key, response] = put.mock.calls[0] as [string, Response];
|
||||
expect(key).toBe(pushPreferencesKey(window.location.origin));
|
||||
expect(key.endsWith('/__push-state/prefs')).toBe(true);
|
||||
expect(await response.json()).toEqual({ sound: false });
|
||||
expect(await response.json()).toEqual({ sound: false, show: true });
|
||||
});
|
||||
|
||||
it('does nothing without a Cache store', async () => {
|
||||
vi.stubGlobal('caches', undefined);
|
||||
const { writePushPreferences } = await import('../push-preferences');
|
||||
await expect(writePushPreferences({ sound: true })).resolves.toBeUndefined();
|
||||
await expect(writePushPreferences({ sound: true, show: true })).resolves.toBeUndefined();
|
||||
expect(put).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -42,7 +42,7 @@ describe('writePushPreferences', () => {
|
||||
},
|
||||
});
|
||||
const { writePushPreferences } = await import('../push-preferences');
|
||||
await expect(writePushPreferences({ sound: true })).resolves.toBeUndefined();
|
||||
await expect(writePushPreferences({ sound: true, show: true })).resolves.toBeUndefined();
|
||||
expect(put).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -54,21 +54,21 @@ describe('settings store push preference mirror', () => {
|
||||
vi.doMock('@/lib/push-preferences', () => ({ writePushPreferences }));
|
||||
const { useSettingsStore } = await import('@/stores/settings-store');
|
||||
|
||||
expect(writePushPreferences).toHaveBeenCalledWith({ sound: true });
|
||||
expect(writePushPreferences).toHaveBeenCalledWith({ sound: true, show: true });
|
||||
writePushPreferences.mockClear();
|
||||
|
||||
useSettingsStore.getState().updateSetting('emailNotificationSound', false);
|
||||
expect(writePushPreferences).toHaveBeenCalledWith({ sound: false });
|
||||
expect(writePushPreferences).toHaveBeenCalledWith({ sound: false, show: true });
|
||||
writePushPreferences.mockClear();
|
||||
|
||||
// Unrelated settings do not rewrite it.
|
||||
useSettingsStore.getState().updateSetting('emailNotificationsEnabled', true);
|
||||
expect(writePushPreferences).not.toHaveBeenCalled();
|
||||
|
||||
// Email notifications off means no sound for push either.
|
||||
// Email notifications off means no notification, so no sound either.
|
||||
useSettingsStore.getState().updateSetting('emailNotificationSound', true);
|
||||
useSettingsStore.getState().updateSetting('emailNotificationsEnabled', false);
|
||||
expect(writePushPreferences).toHaveBeenLastCalledWith({ sound: false });
|
||||
expect(writePushPreferences).toHaveBeenLastCalledWith({ sound: false, show: false });
|
||||
vi.doUnmock('@/lib/push-preferences');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -143,4 +143,34 @@ describe('what a notification counts', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('with notifications off or do not disturb on', () => {
|
||||
it('announces nothing: shows and removes at once, without asking for the message', async () => {
|
||||
const { push, shade, previews } = loadWorker(() => ({ unreadTotal: 1, slot: 0, email: mail('e1', 'Secret subject') }), { prefs: { sound: false, show: false } });
|
||||
await push(delivery('a', 'e1'));
|
||||
expect(shade.size).toBe(0);
|
||||
expect(previews).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('does not bring the message up later when turned on again', async () => {
|
||||
const off = loadWorker(() => ({ unreadTotal: 1, slot: 0, email: mail('e1') }), { prefs: { sound: false, show: false } });
|
||||
await off.push(delivery('a', 'e1'));
|
||||
const { push, shade } = off;
|
||||
// Same store, now switched on: a redelivery stays quiet.
|
||||
await push(delivery('a', 'e1'));
|
||||
expect(shade.size).toBe(0);
|
||||
});
|
||||
|
||||
it('keeps showing with only the sound off', async () => {
|
||||
const { push, shade } = loadWorker(() => ({ unreadTotal: 1, slot: 0, email: mail('e1') }), { prefs: { sound: false, show: true } });
|
||||
await push(delivery('a', 'e1'));
|
||||
expect(shade.get('bulwark-mail:a')!.options).toMatchObject({ silent: true, renotify: false });
|
||||
});
|
||||
|
||||
it('shows as before for prefs written by an older page', async () => {
|
||||
const { push, shade } = loadWorker(() => ({ unreadTotal: 1, slot: 0, email: mail('e1') }), { prefs: { sound: true } });
|
||||
await push(delivery('a', 'e1'));
|
||||
expect(shade.size).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
// -- end of cases --
|
||||
|
||||
@@ -13,6 +13,8 @@ export const PUSH_STATE_CACHE = 'bulwark-push-state-v1';
|
||||
export interface PushPreferences {
|
||||
/** False: show push notifications for new mail without a sound. */
|
||||
sound: boolean;
|
||||
/** False: "Email notifications" is off or do not disturb is on. */
|
||||
show: boolean;
|
||||
}
|
||||
|
||||
/** The cache key sw.js reads the preferences from. */
|
||||
|
||||
@@ -622,6 +622,14 @@ async function notifyAccount(accountId, emailIds, accountLabel) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Email notifications off, or do not disturb on: a browser still wants a
|
||||
// notification for the push, so one comes and goes with nothing on it.
|
||||
if (!(await showPreference())) {
|
||||
if (freshIds.length > 0) await writeNotifiedIds(accountId, notified.concat(freshIds));
|
||||
await showWithoutAlert(groupTag);
|
||||
return;
|
||||
}
|
||||
|
||||
// Best effort: ask the webmail to look up the email so we can build a useful
|
||||
// notification. With ids from the server we ask for exactly the newest of
|
||||
// them; otherwise the API falls back to the newest unread Inbox message. If
|
||||
@@ -880,6 +888,18 @@ async function soundPreference() {
|
||||
}
|
||||
}
|
||||
|
||||
/** The "Email notifications" switch and do not disturb, mirrored the same way. Nothing stored: show. */
|
||||
async function showPreference() {
|
||||
try {
|
||||
const cache = await caches.open(PUSH_STATE_CACHE);
|
||||
const res = await cache.match(pushPreferencesKey());
|
||||
const prefs = res ? await res.json() : null;
|
||||
return !prefs || prefs.show !== false;
|
||||
} catch (_) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/** True when another account alerted audibly less than QUIET_WINDOW_MS ago; otherwise records this one. */
|
||||
async function withinQuietWindow(accountId) {
|
||||
const now = Date.now();
|
||||
|
||||
@@ -6,12 +6,22 @@ vi.mock('@/lib/push-preferences', () => ({ writePushPreferences }));
|
||||
import { useSettingsStore } from '../settings-store';
|
||||
|
||||
describe('do not disturb and push notifications (CO-13)', () => {
|
||||
it('keeps pushed notifications silent while do not disturb is on', () => {
|
||||
it('keeps pushed notifications off screen while do not disturb is on', () => {
|
||||
useSettingsStore.setState({ emailNotificationsEnabled: true, emailNotificationSound: true, doNotDisturb: false });
|
||||
writePushPreferences.mockClear();
|
||||
useSettingsStore.getState().updateSetting('doNotDisturb', true);
|
||||
expect(writePushPreferences).toHaveBeenLastCalledWith({ sound: false });
|
||||
expect(writePushPreferences).toHaveBeenLastCalledWith({ sound: false, show: false });
|
||||
useSettingsStore.getState().updateSetting('doNotDisturb', false);
|
||||
expect(writePushPreferences).toHaveBeenLastCalledWith({ sound: true });
|
||||
expect(writePushPreferences).toHaveBeenLastCalledWith({ sound: true, show: true });
|
||||
});
|
||||
|
||||
it('keeps them off screen with email notifications off, and only silent with the sound off', () => {
|
||||
useSettingsStore.setState({ emailNotificationsEnabled: true, emailNotificationSound: true, doNotDisturb: false });
|
||||
writePushPreferences.mockClear();
|
||||
useSettingsStore.getState().updateSetting('emailNotificationsEnabled', false);
|
||||
expect(writePushPreferences).toHaveBeenLastCalledWith({ sound: false, show: false });
|
||||
useSettingsStore.getState().updateSetting('emailNotificationsEnabled', true);
|
||||
useSettingsStore.getState().updateSetting('emailNotificationSound', false);
|
||||
expect(writePushPreferences).toHaveBeenLastCalledWith({ sound: false, show: true });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2276,17 +2276,19 @@ if (typeof window !== 'undefined') {
|
||||
applyInterfaceLayout(store.interfaceLayout, store.density);
|
||||
|
||||
// The service worker shows push notifications without this store, so it
|
||||
// reads the sound preference from its own Cache store (#1289).
|
||||
// Do not disturb can't keep a push from showing (browsers require one per
|
||||
// push), but it keeps it silent.
|
||||
const pushSound = (s: SettingsState) => s.emailNotificationsEnabled && s.emailNotificationSound && !s.doNotDisturb;
|
||||
// reads these from its own Cache store (#1289).
|
||||
const pushShow = (s: SettingsState) => s.emailNotificationsEnabled && !s.doNotDisturb;
|
||||
const pushSound = (s: SettingsState) => pushShow(s) && s.emailNotificationSound;
|
||||
let lastPushSound = pushSound(store);
|
||||
void writePushPreferences({ sound: lastPushSound });
|
||||
let lastPushShow = pushShow(store);
|
||||
void writePushPreferences({ sound: lastPushSound, show: lastPushShow });
|
||||
useSettingsStore.subscribe((state) => {
|
||||
const sound = pushSound(state);
|
||||
if (sound === lastPushSound) return;
|
||||
const show = pushShow(state);
|
||||
if (sound === lastPushSound && show === lastPushShow) return;
|
||||
lastPushSound = sound;
|
||||
void writePushPreferences({ sound });
|
||||
lastPushShow = show;
|
||||
void writePushPreferences({ sound, show });
|
||||
});
|
||||
|
||||
// The policy arrives after this store has rehydrated (and again with the
|
||||
|
||||
Reference in New Issue
Block a user