diff --git a/extension/js/common/browser/browser-msg.ts b/extension/js/common/browser/browser-msg.ts index 668990f22bd..3e2b90ddfa4 100644 --- a/extension/js/common/browser/browser-msg.ts +++ b/extension/js/common/browser/browser-msg.ts @@ -81,9 +81,9 @@ export namespace Bm { export type Ajax = { req: ApiAjax; resFmt: ResFmt }; export type AjaxProgress = { operationId: string; percent?: number; loaded: number; total: number; expectedTransferSize: number }; export type AjaxGmailAttachmentGetChunk = { acctEmail: string; msgId: string; attachmentId: string; treatAs: string }; - export type ExpirationCacheGet = { key: string; prefix: string; expirationTicks: number }; - export type ExpirationCacheSet = { key: string; prefix: string; value: V | undefined; expirationTicks: number; expiration?: number }; - export type ExpirationCacheDeleteExpired = { prefix: string; expirationTicks: number }; + export type ExpirationCacheGet = { key: string; expirationTicks: number }; + export type ExpirationCacheSet = { key: string; value: V | undefined; expirationTicks: number; expiration?: number }; + export type ExpirationCacheDeleteExpired = { expirationTicks: number }; export type ShowAttachmentPreview = { iframeUrl: string }; export type ShowConfirmation = { text: string; isHTML: boolean; messageSender: Dest; requestUid: string; footer?: string }; export type ReRenderRecipient = { email: string }; diff --git a/extension/js/common/core/expiration-cache.ts b/extension/js/common/core/expiration-cache.ts index fae4cb63d49..8f61bfbe065 100644 --- a/extension/js/common/core/expiration-cache.ts +++ b/extension/js/common/core/expiration-cache.ts @@ -9,10 +9,7 @@ import { Env } from '../browser/env.js'; */ type ExpirationCacheType = { value: V; expiration: number }; export class ExpirationCache { - public constructor( - public prefix: string, - public expirationTicks: number - ) {} + public constructor(public expirationTicks: number) {} public set = async (key: string, value?: V, expiration?: number) => { if (Env.isContentScript()) { @@ -20,7 +17,6 @@ export class ExpirationCache { // Need to get data from service worker await BrowserMsg.send.bg.await.expirationCacheSet({ key, - prefix: this.prefix, value, expirationTicks: this.expirationTicks, expiration, @@ -29,9 +25,9 @@ export class ExpirationCache { } if (value) { const expirationVal = { value, expiration: expiration || Date.now() + this.expirationTicks }; - await storageSet('session', { [this.getPrefixedKey(key)]: expirationVal }); + await storageSet('session', { [`${key}`]: expirationVal }); } else { - await storageRemove('session', [this.getPrefixedKey(key)]); + await storageRemove('session', [key]); } }; @@ -44,19 +40,17 @@ export class ExpirationCache { // eslint-disable-next-line @typescript-eslint/no-unsafe-return return await BrowserMsg.send.bg.await.expirationCacheGet({ key, - prefix: this.prefix, expirationTicks: this.expirationTicks, }); } - const prefixedKey = this.getPrefixedKey(key); - const result = await storageGet('session', [prefixedKey]); - const found = result[prefixedKey] as ExpirationCacheType; + const result = await storageGet('session', [key]); + const found = result[key] as ExpirationCacheType; if (found) { if (found.expiration > Date.now()) { return found.value; } else { // expired, so delete it and return as if not found - await storageRemove('session', [prefixedKey]); + await storageRemove('session', [key]); } } return undefined; @@ -66,10 +60,8 @@ export class ExpirationCache { if (Env.isContentScript()) { // Get chrome storage data from content script not allowed // Need to get data from service worker - await BrowserMsg.retryOnBgNotReadyErr(() => - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - BrowserMsg.send.bg.await.expirationCacheDeleteExpired({ prefix: this.prefix, expirationTicks: this.expirationTicks }) - ); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + await BrowserMsg.retryOnBgNotReadyErr(() => BrowserMsg.send.bg.await.expirationCacheDeleteExpired({ expirationTicks: this.expirationTicks })); return; } @@ -96,8 +88,4 @@ export class ExpirationCache { return Promise.reject(e); } }; - - private getPrefixedKey = (key: string) => { - return `${this.prefix}_${key}`; - }; } diff --git a/extension/js/common/downloader.ts b/extension/js/common/downloader.ts index 78583070b84..bd94912784f 100644 --- a/extension/js/common/downloader.ts +++ b/extension/js/common/downloader.ts @@ -10,9 +10,9 @@ import { ExpirationCache } from './core/expiration-cache.js'; import { Catch } from './platform/catch.js'; export class Downloader { - private readonly chunkDownloads = new ExpirationCache>('chunk', 2 * 60 * 60 * 1000); // 2 hours - private readonly fullMessages = new ExpirationCache>('full_message', 24 * 60 * 60 * 1000); // 24 hours - private readonly rawMessages = new ExpirationCache>('raw_message', 24 * 60 * 60 * 1000); // 24 hours + private readonly chunkDownloads = new ExpirationCache>(2 * 60 * 60 * 1000); // 2 hours + private readonly fullMessages = new ExpirationCache>(24 * 60 * 60 * 1000); // 24 hours + private readonly rawMessages = new ExpirationCache>(24 * 60 * 60 * 1000); // 24 hours public constructor(private readonly gmail: Gmail) {} diff --git a/extension/js/common/message-renderer.ts b/extension/js/common/message-renderer.ts index 74e0e54d8e0..feb01cd590c 100644 --- a/extension/js/common/message-renderer.ts +++ b/extension/js/common/message-renderer.ts @@ -43,7 +43,7 @@ type ProcessedMessage = { export class MessageRenderer { public readonly downloader: Downloader; - private readonly processedMessages = new ExpirationCache>('processed_message', 24 * 60 * 60 * 1000); // 24 hours + private readonly processedMessages = new ExpirationCache>(24 * 60 * 60 * 1000); // 24 hours private constructor( private readonly acctEmail: string, diff --git a/extension/js/service_worker/background.ts b/extension/js/service_worker/background.ts index 699ca0b9609..dfe2bfbd455 100644 --- a/extension/js/service_worker/background.ts +++ b/extension/js/service_worker/background.ts @@ -20,7 +20,7 @@ console.info('background.js service worker starting'); (async () => { let db: IDBDatabase; let storage: GlobalStoreDict; - const inMemoryStore = new ExpirationCache('in_memory_store', 4 * 60 * 60 * 1000); // 4 hours + const inMemoryStore = new ExpirationCache(4 * 60 * 60 * 1000); // 4 hours BrowserMsg.createIntervalAlarm('delete_expired', 1); // each minute try { diff --git a/extension/js/service_worker/bg-handlers.ts b/extension/js/service_worker/bg-handlers.ts index f7d590a8cae..6aec6b91ccd 100644 --- a/extension/js/service_worker/bg-handlers.ts +++ b/extension/js/service_worker/bg-handlers.ts @@ -39,17 +39,17 @@ export class BgHandlers { }; public static expirationCacheGetHandler = async (r: Bm.ExpirationCacheGet): Promise> => { - const expirationCache = new ExpirationCache(r.prefix, r.expirationTicks); + const expirationCache = new ExpirationCache(r.expirationTicks); return await expirationCache.get(r.key); }; public static expirationCacheSetHandler = async (r: Bm.ExpirationCacheSet): Promise => { - const expirationCache = new ExpirationCache(r.prefix, r.expirationTicks); + const expirationCache = new ExpirationCache(r.expirationTicks); await expirationCache.set(r.key, r.value, r.expiration); }; public static expirationCacheDeleteExpiredHandler = async (r: Bm.ExpirationCacheDeleteExpired): Promise => { - const expirationCache = new ExpirationCache(r.prefix, r.expirationTicks); + const expirationCache = new ExpirationCache(r.expirationTicks); await expirationCache.deleteExpired(); }; diff --git a/test/source/tests/browser-unit-tests/unit-ExpirationCache.js b/test/source/tests/browser-unit-tests/unit-ExpirationCache.js index 8c35ae13be0..0991706d856 100644 --- a/test/source/tests/browser-unit-tests/unit-ExpirationCache.js +++ b/test/source/tests/browser-unit-tests/unit-ExpirationCache.js @@ -6,7 +6,7 @@ BROWSER_UNIT_TEST_NAME(`[unit][ExpirationCache] entry expires after configured i const sleep = async seconds => { return await new Promise(resolve => setTimeout(resolve, seconds * 1000)); }; - const cache = new ExpirationCache('test-cache', 2000); // 2 seconds + const cache = new ExpirationCache(2000); // 2 seconds await cache.set('test-key', 'test-value'); let cacheValue = await cache.get('test-key'); if (cacheValue !== 'test-value') { @@ -22,7 +22,7 @@ BROWSER_UNIT_TEST_NAME(`[unit][ExpirationCache] entry expires after configured i BROWSER_UNIT_TEST_NAME(`[unit][ExpirationCache.await] removes rejected promises from cache`); (async () => { - const cache = new ExpirationCache('test-cache-promise', 24 * 60 * 60 * 1000); // 24 hours + const cache = new ExpirationCache(24 * 60 * 60 * 1000); // 24 hours const rejectionPromise = Promise.reject(Error('test-error')); cache.set('test-key', rejectionPromise); let cacheValue = cache.get('test-key'); diff --git a/test/source/tests/gmail.ts b/test/source/tests/gmail.ts index f68829fa34d..37f99934d0b 100644 --- a/test/source/tests/gmail.ts +++ b/test/source/tests/gmail.ts @@ -120,28 +120,24 @@ export const defineGmailTests = (testVariant: TestVariant, testWithBrowser: Test test( 'mail.google.com - send rich-text encrypted message', - testWithBrowser( - async (t, browser) => { - const acctEmail = 'ci.tests.gmail@flowcrypt.dev'; - await BrowserRecipe.setUpCommonAcct(t, browser, 'ci.tests.gmail'); - const gmailPage = await BrowserRecipe.openGmailPageAndVerifyComposeBtnPresent(t, browser); - const composePage = await GmailPageRecipe.openSecureComposeWithRichTextWorkaround(t, gmailPage, browser); - const subject = `New Rich Text Message ${Util.lousyRandom()}`; - await ComposePageRecipe.fillMsg(composePage, { to: acctEmail }, subject, undefined, { - richtext: true, - }); - await ComposePageRecipe.sendAndClose(composePage); - await GmailPageRecipe.expandMainMenuIfNeeded(gmailPage); - await gmailPage.waitAndClick('[aria-label^="Inbox"]'); - await gmailPage.waitAndClick('[role="row"]:first-of-type'); // click the first message - await gmailPage.waitForContent('.nH h2.hP', `Automated puppeteer test: ${subject}`); - const urls = await gmailPage.getFramesUrls(['/chrome/elements/pgp_block.htm'], { sleep: 1 }); - await GmailPageRecipe.deleteThread(gmailPage); - expect(urls.length).to.eq(1); - }, - undefined, - minutes(7) - ) + testWithBrowser(async (t, browser) => { + const acctEmail = 'ci.tests.gmail@flowcrypt.dev'; + await BrowserRecipe.setUpCommonAcct(t, browser, 'ci.tests.gmail'); + const gmailPage = await BrowserRecipe.openGmailPageAndVerifyComposeBtnPresent(t, browser); + const composePage = await GmailPageRecipe.openSecureComposeWithRichTextWorkaround(t, gmailPage, browser); + const subject = `New Rich Text Message ${Util.lousyRandom()}`; + await ComposePageRecipe.fillMsg(composePage, { to: acctEmail }, subject, undefined, { + richtext: true, + }); + await ComposePageRecipe.sendAndClose(composePage); + await GmailPageRecipe.expandMainMenuIfNeeded(gmailPage); + await gmailPage.waitAndClick('[aria-label^="Inbox"]'); + await gmailPage.waitAndClick('[role="row"]:first-of-type'); // click the first message + await gmailPage.waitForContent('.nH h2.hP', `Automated puppeteer test: ${subject}`); + const urls = await gmailPage.getFramesUrls(['/chrome/elements/pgp_block.htm'], { sleep: 1 }); + await GmailPageRecipe.deleteThread(gmailPage); + expect(urls.length).to.eq(1); + }) ); test( @@ -378,65 +374,57 @@ export const defineGmailTests = (testVariant: TestVariant, testWithBrowser: Test test( 'mail.google.com - plain reply to encrypted and signed messages', - testWithBrowser( - async (t, browser) => { - await BrowserRecipe.setUpCommonAcct(t, browser, 'ci.tests.gmail'); - const gmailPage = await openGmailPage(t, browser); - await gotoGmailPage(gmailPage, '/FMfcgzGkbDRNgcQxLmkhBCKVSFwkfdvV'); // plain convo - await gmailPage.waitAndClick('[data-tooltip="Reply"]', { delay: 1 }); - await gotoGmailPage(gmailPage, '/181d226b4e69f172'); // go to encrypted convo - await gmailPage.waitAndClick('[data-tooltip="Reply"]', { delay: 1 }); - await gmailPage.waitTillGone('.reply_message'); - await gmailPage.waitAll('[data-tooltip^="Send"]'); // The Send button from the Standard reply box - await gmailPage.waitForContent( - '.reply_message_evaluated .error_notification', - 'The last message was encrypted, but you are composing a reply without encryption.' - ); - await gmailPage.waitAndClick('[data-tooltip="Secure Reply"]'); // Switch to encrypted reply - await gmailPage.waitAll('.reply_message'); - await pageHasSecureReplyContainer(t, browser, gmailPage, { isReplyPromptAccepted: true }); - await gotoGmailPage(gmailPage, '/FMfcgzGkbDRNpjDdNvCrwzqvXspZZxvh'); // go to signed convo - await gmailPage.waitAndClick('[data-tooltip="Reply"]', { delay: 1 }); - await gmailPage.waitTillGone('.reply_message'); - await gmailPage.waitAll('[data-tooltip^="Send"]'); // The Send button from the Standard reply box - await gmailPage.notPresent('.reply_message_evaluated .error_notification'); // should not show the warning about switching to encrypted reply - }, - undefined, - minutes(6) - ) + testWithBrowser(async (t, browser) => { + await BrowserRecipe.setUpCommonAcct(t, browser, 'ci.tests.gmail'); + const gmailPage = await openGmailPage(t, browser); + await gotoGmailPage(gmailPage, '/FMfcgzGkbDRNgcQxLmkhBCKVSFwkfdvV'); // plain convo + await gmailPage.waitAndClick('[data-tooltip="Reply"]', { delay: 1 }); + await gotoGmailPage(gmailPage, '/181d226b4e69f172'); // go to encrypted convo + await gmailPage.waitAndClick('[data-tooltip="Reply"]', { delay: 1 }); + await gmailPage.waitTillGone('.reply_message'); + await gmailPage.waitAll('[data-tooltip^="Send"]'); // The Send button from the Standard reply box + await gmailPage.waitForContent( + '.reply_message_evaluated .error_notification', + 'The last message was encrypted, but you are composing a reply without encryption.' + ); + await gmailPage.waitAndClick('[data-tooltip="Secure Reply"]'); // Switch to encrypted reply + await gmailPage.waitAll('.reply_message'); + await pageHasSecureReplyContainer(t, browser, gmailPage, { isReplyPromptAccepted: true }); + await gotoGmailPage(gmailPage, '/FMfcgzGkbDRNpjDdNvCrwzqvXspZZxvh'); // go to signed convo + await gmailPage.waitAndClick('[data-tooltip="Reply"]', { delay: 1 }); + await gmailPage.waitTillGone('.reply_message'); + await gmailPage.waitAll('[data-tooltip^="Send"]'); // The Send button from the Standard reply box + await gmailPage.notPresent('.reply_message_evaluated .error_notification'); // should not show the warning about switching to encrypted reply + }) ); test( 'mail.google.com - switch to encrypted reply for middle message', - testWithBrowser( - async (t, browser) => { - await BrowserRecipe.setUpCommonAcct(t, browser, 'ci.tests.gmail'); - const gmailPage = await openGmailPage(t, browser); - await gotoGmailPage(gmailPage, '/FMfcgzGqRGfPBbNLWvfPvDbxnHBwkdGf'); // plain convo - await gmailPage.waitAndClick('[role="listitem"] .adf.ads', { delay: 1 }); // click first message of thread - await Util.sleep(3); - const messages = await gmailPage.target.$$('[role="listitem"] .adn.ads'); - expect(messages.length).to.equal(2); - - const plainReplyButton = await messages[0].$('[data-tooltip="Reply"]'); - expect(plainReplyButton).to.be.ok; - await Util.sleep(1); - await plainReplyButton!.click(); - await gmailPage.waitAll('[data-tooltip^="Send"]'); // The Send button from the Standard reply box - await gmailPage.waitForContent( - '.reply_message_evaluated .error_notification', - 'The last message was encrypted, but you are composing a reply without encryption.' - ); - - const secureReplyButton = await messages[0].$('[data-tooltip="Secure Reply"]'); - expect(secureReplyButton).to.be.ok; - await secureReplyButton!.click(); // Switch to encrypted reply - await gmailPage.waitAll('.reply_message'); - await pageHasSecureReplyContainer(t, browser, gmailPage, { isReplyPromptAccepted: true, composeFrameCount: 2 }); - }, - undefined, - minutes(6) - ) + testWithBrowser(async (t, browser) => { + await BrowserRecipe.setUpCommonAcct(t, browser, 'ci.tests.gmail'); + const gmailPage = await openGmailPage(t, browser); + await gotoGmailPage(gmailPage, '/FMfcgzGqRGfPBbNLWvfPvDbxnHBwkdGf'); // plain convo + await gmailPage.waitAndClick('[role="listitem"] .adf.ads', { delay: 1 }); // click first message of thread + await Util.sleep(3); + const messages = await gmailPage.target.$$('[role="listitem"] .adn.ads'); + expect(messages.length).to.equal(2); + + const plainReplyButton = await messages[0].$('[data-tooltip="Reply"]'); + expect(plainReplyButton).to.be.ok; + await Util.sleep(1); + await plainReplyButton!.click(); + await gmailPage.waitAll('[data-tooltip^="Send"]'); // The Send button from the Standard reply box + await gmailPage.waitForContent( + '.reply_message_evaluated .error_notification', + 'The last message was encrypted, but you are composing a reply without encryption.' + ); + + const secureReplyButton = await messages[0].$('[data-tooltip="Secure Reply"]'); + expect(secureReplyButton).to.be.ok; + await secureReplyButton!.click(); // Switch to encrypted reply + await gmailPage.waitAll('.reply_message'); + await pageHasSecureReplyContainer(t, browser, gmailPage, { isReplyPromptAccepted: true, composeFrameCount: 2 }); + }) ); test(