fix: keep sender favicons off and style sheets out of notification previews (bulwarkmail/webmail#1136)

This commit is contained in:
Linus Rath
2026-10-31 05:08:11 +01:00
parent 90e4e230af
commit 6da985a225
2 changed files with 43 additions and 6 deletions
+36 -2
View File
@@ -325,6 +325,37 @@ describe('pushBackgroundTask notifications', () => {
}));
});
it('drops a leading style sheet from the notification preview (webmail #1136)', async () => {
(secureFetch as ReturnType<typeof vi.fn>).mockImplementation(async (url: string) => ({
ok: true,
json: async () => (url.endsWith('/.well-known/jmap')
? {
apiUrl: 'https://mail.example.com/jmap/',
primaryAccounts: { 'urn:ietf:params:jmap:mail': 'jmap-primary' },
accounts: { 'jmap-primary': {} },
}
: {
methodResponses: [[
'Email/get',
{ list: [{ id: 'm1', threadId: 't1', keywords: {}, subject: 'Sale', preview: '@media screen and (min-width:600px){.hide{display:none!important}} Our autumn sale\nstarts today', from: [{ name: 'Shop', email: 'news@shop.example' }] }] },
'0',
]],
}),
}));
await pushBackgroundTask({
kind: 'jmap-email-push',
accountLabel: 'alice',
accountId: 'jmap-primary',
emailIds: JSON.stringify(['m1']),
});
expect(showNotification).toHaveBeenCalledWith(expect.objectContaining({
emailId: 'm1',
preview: 'Our autumn sale starts today',
}));
});
it('handles markRead action via detached JMAP', async () => {
const postCalls: any[] = [];
(secureFetch as ReturnType<typeof vi.fn>).mockImplementation(async (url: string, opts?: any) => {
@@ -759,9 +790,12 @@ describe('senderFaviconsAllowed', () => {
expect(await senderFaviconsAllowed()).toBe(true);
});
it('defaults to on like the store, but skips icons when the settings are unreadable', async () => {
it('defaults to off like the store, and skips icons when the settings are unreadable', async () => {
await AsyncStorage.removeItem(KEY);
expect(await senderFaviconsAllowed()).toBe(true);
expect(await senderFaviconsAllowed()).toBe(false);
// Settings saved before the key existed: the store fills in its default (off).
await AsyncStorage.setItem(KEY, JSON.stringify({ emailNotificationsEnabled: true }));
expect(await senderFaviconsAllowed()).toBe(false);
await AsyncStorage.setItem(KEY, '{not json');
expect(await senderFaviconsAllowed()).toBe(false);
await AsyncStorage.removeItem(KEY);
+7 -4
View File
@@ -13,6 +13,7 @@ import {
getFaviconUrl,
} from './avatar-utils';
import { singleLine } from './single-line';
import { cleanPreview } from './preview-text';
import {
lastNotifiedKey,
migrateLegacyPushKeys,
@@ -67,13 +68,14 @@ async function emailNotificationsAllowed(): Promise<boolean> {
// The "Sender Favicons" setting covers notification icons as well as the
// in-app avatars: with it off the tray shows initials, and the sender's domain
// and the device's IP never reach the icon service. Off when unreadable.
// and the device's IP never reach the icon service. Off by default, like the
// store, and off when unreadable.
export async function senderFaviconsAllowed(): Promise<boolean> {
try {
const raw = await AsyncStorage.getItem(SETTINGS_STORAGE_KEY);
if (!raw) return true; // first launch: the store's default is on
if (!raw) return false; // first launch: the store's default is off
const parsed = JSON.parse(raw) as PushPersistedSettings;
return parsed.senderFavicons !== false;
return parsed.senderFavicons === true;
} catch {
return false;
}
@@ -583,7 +585,8 @@ async function notifyForPush(accountId: string, session: DetachedSession, payloa
const faviconDomain = favicons ? getFaviconDomain(address) : null;
const iconUrl = faviconDomain ? getFaviconUrl(faviconDomain) : undefined;
const preview = email.preview ? singleLine(email.preview) : undefined;
// No leading style sheet or preheader padding (webmail #1136).
const preview = singleLine(cleanPreview(email.preview)) || undefined;
const markReadLabel = translate(locale, 'email_viewer.mark_read', 'Mark as read');
const deleteLabel = translate(locale, 'email_viewer.delete', 'Delete');
const replyLabel = translate(locale, 'email_viewer.reply', 'Reply');