diff --git a/src/lib/__tests__/push-background-task.test.ts b/src/lib/__tests__/push-background-task.test.ts index a61ffdc4..c90a53a6 100644 --- a/src/lib/__tests__/push-background-task.test.ts +++ b/src/lib/__tests__/push-background-task.test.ts @@ -325,6 +325,37 @@ describe('pushBackgroundTask notifications', () => { })); }); + it('drops a leading style sheet from the notification preview (webmail #1136)', async () => { + (secureFetch as ReturnType).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).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); diff --git a/src/lib/push-background-task.ts b/src/lib/push-background-task.ts index cecf9d2f..53c09bf7 100644 --- a/src/lib/push-background-task.ts +++ b/src/lib/push-background-task.ts @@ -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 { // 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 { 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');