diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index 82319f39a17..0502a4d4b89 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -1,5 +1,5 @@ version: v1.0 -name: Flowcrypt Node Core Tests +name: FlowCrypt Extension Tests agent: machine: type: e2-standard-4 diff --git a/extension/js/content_scripts/webmail/setup-webmail-content-script.ts b/extension/js/content_scripts/webmail/generic/setup-webmail-content-script.ts similarity index 88% rename from extension/js/content_scripts/webmail/setup-webmail-content-script.ts rename to extension/js/content_scripts/webmail/generic/setup-webmail-content-script.ts index 81e5e6868cc..2a8c130b277 100644 --- a/extension/js/content_scripts/webmail/setup-webmail-content-script.ts +++ b/extension/js/content_scripts/webmail/generic/setup-webmail-content-script.ts @@ -3,28 +3,29 @@ 'use strict'; import Swal from 'sweetalert2'; -import { AccountServer } from '../../common/api/account-server.js'; -import { KeyManager } from '../../common/api/key-server/key-manager.js'; -import { ApiErr, BackendAuthErr } from '../../common/api/shared/api-error.js'; -import { BrowserMsgCommonHandlers } from '../../common/browser/browser-msg-common-handlers.js'; -import { Bm, BrowserMsg, TabIdRequiredError } from '../../common/browser/browser-msg.js'; -import { ContentScriptWindow } from '../../common/browser/browser-window.js'; -import { Env, WebMailName } from '../../common/browser/env.js'; -import { Time } from '../../common/browser/time.js'; -import { CommonHandlers, Ui } from '../../common/browser/ui.js'; -import { ClientConfiguration, ClientConfigurationError } from '../../common/client-configuration.js'; -import { Str, Url } from '../../common/core/common.js'; -import { InMemoryStoreKeys, VERSION } from '../../common/core/const.js'; -import { getLocalKeyExpiration, processAndStoreKeysFromEkmLocally } from '../../common/helpers.js'; -import { Injector } from '../../common/inject.js'; -import { Lang } from '../../common/lang.js'; -import { Notifications } from '../../common/notifications.js'; -import { Catch } from '../../common/platform/catch.js'; -import { AcctStore } from '../../common/platform/store/acct-store.js'; -import { GlobalStore } from '../../common/platform/store/global-store.js'; -import { InMemoryStore } from '../../common/platform/store/in-memory-store.js'; -import { WebmailVariantString, XssSafeFactory } from '../../common/xss-safe-factory.js'; -import { RelayManager } from '../../common/relay-manager.js'; +import { AccountServer } from '../../../common/api/account-server.js'; +import { KeyManager } from '../../../common/api/key-server/key-manager.js'; +import { ApiErr, BackendAuthErr } from '../../../common/api/shared/api-error.js'; +import { BrowserMsgCommonHandlers } from '../../../common/browser/browser-msg-common-handlers.js'; +import { Bm, BrowserMsg, TabIdRequiredError } from '../../../common/browser/browser-msg.js'; +import { ContentScriptWindow } from '../../../common/browser/browser-window.js'; +import { Env, WebMailName } from '../../../common/browser/env.js'; +import { Time } from '../../../common/browser/time.js'; +import { CommonHandlers, Ui } from '../../../common/browser/ui.js'; +import { ClientConfiguration, ClientConfigurationError } from '../../../common/client-configuration.js'; +import { Str, Url } from '../../../common/core/common.js'; +import { InMemoryStoreKeys, VERSION } from '../../../common/core/const.js'; +import { getLocalKeyExpiration, processAndStoreKeysFromEkmLocally } from '../../../common/helpers.js'; +import { Injector } from '../../../common/inject.js'; +import { Lang } from '../../../common/lang.js'; +import { Notifications } from '../../../common/notifications.js'; +import { Catch } from '../../../common/platform/catch.js'; +import { AcctStore } from '../../../common/platform/store/acct-store.js'; +import { GlobalStore } from '../../../common/platform/store/global-store.js'; +import { InMemoryStore } from '../../../common/platform/store/in-memory-store.js'; +import { WebmailVariantString, XssSafeFactory } from '../../../common/xss-safe-factory.js'; +import { RelayManager } from '../../../common/relay-manager.js'; +import { WebmailElementReplacer } from './webmail-element-replacer.js'; export type WebmailVariantObject = { newDataLayer: undefined | boolean; @@ -32,7 +33,7 @@ export type WebmailVariantObject = { email: undefined | string; gmailVariant: WebmailVariantString; }; -export type IntervalFunction = { interval: number; handler: () => void }; + type WebmailSpecificInfo = { name: WebMailName; variant: WebmailVariantString; @@ -45,17 +46,9 @@ type WebmailSpecificInfo = { inject: Injector, notifications: Notifications, factory: XssSafeFactory, - notifyMurdered: () => void, relayManager: RelayManager ) => Promise; }; -export interface WebmailElementReplacer { - getIntervalFunctions: () => IntervalFunction[]; - setReplyBoxEditable: () => Promise; - reinsertReplyBox: (replyMsgId: string) => void; - scrollToReplyBox: (replyMsgId: string) => void; - scrollToCursorInReplyBox: (replyMsgId: string, cursorOffsetTop: number) => void; -} const win = window as unknown as ContentScriptWindow; @@ -275,23 +268,6 @@ export const contentScriptSetupIfVacant = async (webmailSpecific: WebmailSpecifi } }; - const notifyMurdered = () => { - const notifEl = document.getElementsByClassName('webmail_notifications')[0]; - const div = document.createElement('div'); - div.innerText = 'FlowCrypt has updated, please reload the tab. '; - div.classList.add('webmail_notification'); - const a = document.createElement('a'); - a.href = '#'; - a.onclick = function () { - const parent = (this as HTMLAnchorElement).parentNode as HTMLElement | undefined; - parent?.remove(); - }; - a.textContent = 'close'; - div.appendChild(a); - notifEl.textContent = ''; - notifEl.appendChild(div); - }; - const showPassphraseDialog = async (factory: XssSafeFactory, { longids, type, initiatorFrameId }: Bm.PassphraseDialog) => { await factory.showPassphraseDialog(longids, type, initiatorFrameId); }; @@ -451,7 +427,7 @@ export const contentScriptSetupIfVacant = async (webmailSpecific: WebmailSpecifi ppEvent, Catch.try(() => notifyExpiringKeys(acctEmail, clientConfiguration, notifications)) ); - await webmailSpecific.start(acctEmail, clientConfiguration, inject, notifications, factory, notifyMurdered, relayManager); + await webmailSpecific.start(acctEmail, clientConfiguration, inject, notifications, factory, relayManager); } catch (e) { if (e instanceof TabIdRequiredError) { console.error(`FlowCrypt cannot start: ${String(e)}`); @@ -527,3 +503,28 @@ export const contentScriptSetupIfVacant = async (webmailSpecific: WebmailSpecifi } } }; + +/** + * This happens when Firefox (or possibly Thunderbird) just updated FlowCrypt. + * + * Previous (meaning this currently running) instance of FlowCrypt will no longer + * have access to its various classes or global variables, and is left in a + * semi-functioning state. The best we can do is to ask the user to reload + * the tab, which will load the newly updated version of the extension cleanly. + */ +export const notifyMurdered = () => { + const notifEl = document.getElementsByClassName('webmail_notifications')[0]; + const div = document.createElement('div'); + div.innerText = 'FlowCrypt has updated, please reload the tab. '; + div.classList.add('webmail_notification'); + const a = document.createElement('a'); + a.href = '#'; + a.onclick = function () { + const parent = (this as HTMLAnchorElement).parentNode as HTMLElement | undefined; + parent?.remove(); + }; + a.textContent = 'close'; + div.appendChild(a); + notifEl.textContent = ''; + notifEl.appendChild(div); +}; diff --git a/extension/js/content_scripts/webmail/generic/webmail-element-replacer.ts b/extension/js/content_scripts/webmail/generic/webmail-element-replacer.ts new file mode 100644 index 00000000000..d997200423d --- /dev/null +++ b/extension/js/content_scripts/webmail/generic/webmail-element-replacer.ts @@ -0,0 +1,33 @@ +/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ + +import { ContentScriptWindow } from '../../../common/browser/browser-window'; +import { notifyMurdered } from './setup-webmail-content-script'; + +export type IntervalFunction = { interval: number; handler: () => void }; + +export abstract class WebmailElementReplacer { + private replacePgpElsInterval: number; + + public abstract getIntervalFunctions: () => IntervalFunction[]; + public abstract setReplyBoxEditable: () => Promise; + public abstract reinsertReplyBox: (replyMsgId: string) => void; + public abstract scrollToReplyBox: (replyMsgId: string) => void; + public abstract scrollToCursorInReplyBox: (replyMsgId: string, cursorOffsetTop: number) => void; + + public runIntervalFunctionsPeriodically = () => { + const intervalFunctions = this.getIntervalFunctions(); + for (const intervalFunction of intervalFunctions) { + intervalFunction.handler(); + this.replacePgpElsInterval = (window as unknown as ContentScriptWindow).TrySetDestroyableInterval(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (typeof (window as any).$ === 'function') { + intervalFunction.handler(); + } else { + // firefox will unload jquery when extension is restarted or updated + clearInterval(this.replacePgpElsInterval); + notifyMurdered(); + } + }, intervalFunction.interval); + } + }; +} diff --git a/extension/js/content_scripts/webmail/gmail-element-replacer.ts b/extension/js/content_scripts/webmail/gmail/gmail-element-replacer.ts similarity index 95% rename from extension/js/content_scripts/webmail/gmail-element-replacer.ts rename to extension/js/content_scripts/webmail/gmail/gmail-element-replacer.ts index 4bfc6655d7d..7b5a4a9f238 100644 --- a/extension/js/content_scripts/webmail/gmail-element-replacer.ts +++ b/extension/js/content_scripts/webmail/gmail/gmail-element-replacer.ts @@ -2,34 +2,34 @@ 'use strict'; -import { Dict, Str } from '../../common/core/common.js'; -import { FactoryReplyParams, XssSafeFactory } from '../../common/xss-safe-factory.js'; -import { IntervalFunction, WebmailElementReplacer } from './setup-webmail-content-script.js'; -import { ApiErr } from '../../common/api/shared/api-error.js'; -import { Attachment } from '../../common/core/attachment.js'; -import { BrowserMsg } from '../../common/browser/browser-msg.js'; -import { Catch } from '../../common/platform/catch.js'; -import { GlobalStore, LocalDraft } from '../../common/platform/store/global-store.js'; -import { Injector } from '../../common/inject.js'; -import { PubLookup } from '../../common/api/pub-lookup.js'; -import { Notifications } from '../../common/notifications.js'; -import { PgpArmor } from '../../common/core/crypto/pgp/pgp-armor.js'; -import { Ui } from '../../common/browser/ui.js'; -import { WebmailCommon } from '../../common/webmail.js'; -import { Xss } from '../../common/platform/xss.js'; -import { ClientConfiguration } from '../../common/client-configuration.js'; +import { Dict, Str } from '../../../common/core/common.js'; +import { FactoryReplyParams, XssSafeFactory } from '../../../common/xss-safe-factory.js'; +import { ApiErr } from '../../../common/api/shared/api-error.js'; +import { Attachment } from '../../../common/core/attachment.js'; +import { BrowserMsg } from '../../../common/browser/browser-msg.js'; +import { Catch } from '../../../common/platform/catch.js'; +import { GlobalStore, LocalDraft } from '../../../common/platform/store/global-store.js'; +import { Injector } from '../../../common/inject.js'; +import { PubLookup } from '../../../common/api/pub-lookup.js'; +import { Notifications } from '../../../common/notifications.js'; +import { PgpArmor } from '../../../common/core/crypto/pgp/pgp-armor.js'; +import { Ui } from '../../../common/browser/ui.js'; +import { WebmailCommon } from '../../../common/webmail.js'; +import { Xss } from '../../../common/platform/xss.js'; +import { ClientConfiguration } from '../../../common/client-configuration.js'; // todo: can we somehow define a purely relay class for ContactStore to clearly show that crypto-libraries are not loaded and can't be used? -import { ContactStore } from '../../common/platform/store/contact-store.js'; -import { MessageRenderer } from '../../common/message-renderer.js'; -import { RelayManager } from '../../common/relay-manager.js'; -import { MessageInfo } from '../../common/render-message.js'; +import { ContactStore } from '../../../common/platform/store/contact-store.js'; +import { MessageRenderer } from '../../../common/message-renderer.js'; +import { RelayManager } from '../../../common/relay-manager.js'; +import { MessageInfo } from '../../../common/render-message.js'; import { GmailLoaderContext } from './gmail-loader-context.js'; -import { JQueryEl } from '../../common/loader-context-interface.js'; -import { MessageBody, Mime } from '../../common/core/mime.js'; -import { MsgBlock } from '../../common/core/msg-block.js'; -import { ReplyOption } from '../../../chrome/elements/compose-modules/compose-reply-btn-popover-module.js'; +import { JQueryEl } from '../../../common/loader-context-interface.js'; +import { MessageBody, Mime } from '../../../common/core/mime.js'; +import { MsgBlock } from '../../../common/core/msg-block.js'; +import { ReplyOption } from '../../../../chrome/elements/compose-modules/compose-reply-btn-popover-module.js'; +import { WebmailElementReplacer, IntervalFunction } from '../generic/webmail-element-replacer.js'; -export class GmailElementReplacer implements WebmailElementReplacer { +export class GmailElementReplacer extends WebmailElementReplacer { private debug = false; private recipientHasPgpCache: Dict = {}; @@ -74,6 +74,7 @@ export class GmailElementReplacer implements WebmailElementReplacer { private readonly notifications: Notifications, private readonly relayManager: RelayManager ) { + super(); this.webmailCommon = new WebmailCommon(acctEmail, injector); this.pubLookup = new PubLookup(clientConfiguration); } diff --git a/extension/js/content_scripts/webmail/gmail-loader-context.ts b/extension/js/content_scripts/webmail/gmail/gmail-loader-context.ts similarity index 95% rename from extension/js/content_scripts/webmail/gmail-loader-context.ts rename to extension/js/content_scripts/webmail/gmail/gmail-loader-context.ts index 203762b6442..e44afe8eeed 100644 --- a/extension/js/content_scripts/webmail/gmail-loader-context.ts +++ b/extension/js/content_scripts/webmail/gmail/gmail-loader-context.ts @@ -2,9 +2,9 @@ 'use strict'; -import { Attachment } from '../../common/core/attachment.js'; -import { JQueryEl, LoaderContextInterface } from '../../common/loader-context-interface.js'; -import { XssSafeFactory } from '../../common/xss-safe-factory.js'; +import { Attachment } from '../../../common/core/attachment.js'; +import { JQueryEl, LoaderContextInterface } from '../../../common/loader-context-interface.js'; +import { XssSafeFactory } from '../../../common/xss-safe-factory.js'; export class GmailLoaderContext implements LoaderContextInterface { public constructor( diff --git a/extension/js/content_scripts/webmail/gmail/gmail-webmail-startup.ts b/extension/js/content_scripts/webmail/gmail/gmail-webmail-startup.ts new file mode 100644 index 00000000000..851e02f0f35 --- /dev/null +++ b/extension/js/content_scripts/webmail/gmail/gmail-webmail-startup.ts @@ -0,0 +1,147 @@ +/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ + +import { Gmail } from '../../../common/api/email-provider/gmail/gmail'; +import { Env } from '../../../common/browser/env'; +import { Time } from '../../../common/browser/time'; +import { ClientConfiguration } from '../../../common/client-configuration'; +import { Str } from '../../../common/core/common'; +import { Injector } from '../../../common/inject'; +import { MessageRenderer } from '../../../common/message-renderer'; +import { Notifications } from '../../../common/notifications'; +import { Catch } from '../../../common/platform/catch'; +import { RelayManager } from '../../../common/relay-manager'; +import { XssSafeFactory } from '../../../common/xss-safe-factory'; +import { WebmailVariantObject, contentScriptSetupIfVacant } from '../generic/setup-webmail-content-script'; +import { WebmailElementReplacer } from '../generic/webmail-element-replacer'; +import { GmailElementReplacer } from './gmail-element-replacer'; + +export class GmailWebmailStartup { + private replacer: WebmailElementReplacer; + + public asyncConstructor = async () => { + this.injectFCVarScript(); + await Time.sleep(100); // Wait until injected dom is added + const webmailVariant = this.determineWebmailVariant(); + await contentScriptSetupIfVacant({ + name: 'gmail', + variant: webmailVariant.gmailVariant, + getUserAccountEmail: () => this.getUserAccountEmail(webmailVariant), + getUserFullName: () => $('div.gb_hb div.gb_lb').text() || $('div.gb_Fb.gb_Hb').text(), + getReplacer: () => this.replacer, + start: this.start, + }); + }; + + private start = async ( + acctEmail: string, + clientConfiguration: ClientConfiguration, + injector: Injector, + notifications: Notifications, + factory: XssSafeFactory, + relayManager: RelayManager + ) => { + this.hijackGmailHotkeys(); + injector.btns(); + const messageRenderer = await MessageRenderer.newInstance(acctEmail, new Gmail(acctEmail), relayManager, factory); + this.replacer = new GmailElementReplacer(factory, clientConfiguration, acctEmail, messageRenderer, injector, notifications, relayManager); + await notifications.showInitial(acctEmail); + this.replacer.runIntervalFunctionsPeriodically(); + }; + + private getUserAccountEmail = (hostPageInfo: WebmailVariantObject): undefined | string => { + if (window.location.search.indexOf('&view=btop&') === -1) { + // when view=btop present, FlowCrypt should not be activated + if (hostPageInfo.email) { + return hostPageInfo.email; + } + const emailRegex = /[a-z0-9._\-]+@[^…< ]+/gi; + const acctEmailLoadingMatch = $('#loading div.msg').text().match(emailRegex); + if (acctEmailLoadingMatch) { + // try parse from loading div + return acctEmailLoadingMatch[0].trim().toLowerCase(); + } + const emailFromAccountDropdown = $('div.gb_Cb > div.gb_Ib').text()?.trim()?.toLowerCase(); + if (Str.isEmailValid(emailFromAccountDropdown)) { + return emailFromAccountDropdown; + } + const titleMatch = document.title.match(emailRegex); + if (titleMatch) { + return titleMatch[0].trim().toLowerCase(); + } + const emailFromAccountModal = $('div.gb_Dc > div').last()?.text()?.trim()?.toLowerCase(); + if (Str.isEmailValid(emailFromAccountModal)) { + return emailFromAccountModal; + } + // eslint-disable-next-line no-underscore-dangle + const emailFromConfigVariable = window.gbar_?.CONFIG?.[0]?.[4]?.ka?.[5]; + if (Str.isEmailValid(emailFromConfigVariable)) { + return String(emailFromConfigVariable); + } + const emailFromUserNameAndEmail = $('.gb_2e .gb_Fc :last-child').text(); + if (Str.isEmailValid(emailFromUserNameAndEmail)) { + return emailFromUserNameAndEmail; + } + } + return undefined; + }; + + private injectFCVarScript = () => { + const scriptElement = document.createElement('script'); + scriptElement.src = chrome.runtime.getURL('/js/common/core/feature-config-injector.js'); + (document.head || document.documentElement).appendChild(scriptElement); + }; + + private determineWebmailVariant = (): WebmailVariantObject => { + const insights: WebmailVariantObject = { + newDataLayer: undefined, + newUi: undefined, + email: undefined, + gmailVariant: undefined, + }; + + try { + const extracted = (JSON.parse($('body > div#FC_VAR_PASS').text()) as unknown[]).map(String); + if (extracted[0] === 'true') { + insights.newDataLayer = true; + } else if (extracted[0] === 'false') { + insights.newDataLayer = false; + } + if (extracted[1] === 'true') { + insights.newUi = true; + } else if (extracted[1] === 'false') { + insights.newUi = false; + } + if (Str.isEmailValid(extracted[2])) { + insights.email = extracted[2].trim().toLowerCase(); + } + if (typeof insights.newDataLayer === 'undefined' && typeof insights.newUi === 'undefined' && typeof insights.email === 'undefined') { + insights.gmailVariant = 'html'; + } else if (insights.newUi === false) { + insights.gmailVariant = 'standard'; + } else if (insights.newUi === true) { + insights.gmailVariant = 'new'; + } + } catch (e) { + // no need to handle + } + return insights; + }; + + private hijackGmailHotkeys = () => { + const keys = Env.keyCodes(); + const unsecureReplyKeyShortcuts = [keys.a, keys.r, keys.A, keys.R, keys.f, keys.F]; + $(document).keypress(e => { + Catch.try(() => { + const causesUnsecureReply = unsecureReplyKeyShortcuts.includes(e.which); + if ( + causesUnsecureReply && + !$(document.activeElement!).is('input, select, textarea, div[contenteditable="true"]') && // eslint-disable-line @typescript-eslint/no-non-null-assertion + $('iframe.reply_message').length + ) { + e.stopImmediatePropagation(); + this.replacer.setReplyBoxEditable().catch(Catch.reportErr); + } + })(); + }); + }; +} diff --git a/extension/js/content_scripts/webmail/webmail.ts b/extension/js/content_scripts/webmail/webmail.ts index 8b8255be086..5e5d6adf937 100644 --- a/extension/js/content_scripts/webmail/webmail.ts +++ b/extension/js/content_scripts/webmail/webmail.ts @@ -4,20 +4,9 @@ // todo - a few things are duplicated here, refactor -import { WebmailVariantObject, contentScriptSetupIfVacant } from './setup-webmail-content-script.js'; import { Catch } from '../../common/platform/catch.js'; -import { ContentScriptWindow } from '../../common/browser/browser-window.js'; -import { Env } from '../../common/browser/env.js'; -import { GmailElementReplacer } from './gmail-element-replacer.js'; -import { Injector } from '../../common/inject.js'; -import { Notifications } from '../../common/notifications.js'; -import { Str } from '../../common/core/common.js'; -import { XssSafeFactory } from '../../common/xss-safe-factory.js'; -import { ClientConfiguration } from '../../common/client-configuration.js'; -import { RelayManager } from '../../common/relay-manager.js'; -import { MessageRenderer } from '../../common/message-renderer.js'; -import { Gmail } from '../../common/api/email-provider/gmail/gmail.js'; -import { Time } from '../../common/browser/time.js'; +import { GmailWebmailStartup } from './gmail/gmail-webmail-startup.js'; + declare global { interface Window { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -26,151 +15,6 @@ declare global { } Catch.try(async () => { - const gmailWebmailStartup = async () => { - let replacePgpElsInterval: number; - let replacer: GmailElementReplacer; - - const getUserAccountEmail = (): undefined | string => { - if (window.location.search.indexOf('&view=btop&') === -1) { - // when view=btop present, FlowCrypt should not be activated - if (hostPageInfo.email) { - return hostPageInfo.email; - } - const emailRegex = /[a-z0-9._\-]+@[^…< ]+/gi; - const acctEmailLoadingMatch = $('#loading div.msg').text().match(emailRegex); - if (acctEmailLoadingMatch) { - // try parse from loading div - return acctEmailLoadingMatch[0].trim().toLowerCase(); - } - const emailFromAccountDropdown = $('div.gb_Cb > div.gb_Ib').text()?.trim()?.toLowerCase(); - if (Str.isEmailValid(emailFromAccountDropdown)) { - return emailFromAccountDropdown; - } - const titleMatch = document.title.match(emailRegex); - if (titleMatch) { - return titleMatch[0].trim().toLowerCase(); - } - const emailFromAccountModal = $('div.gb_Dc > div').last()?.text()?.trim()?.toLowerCase(); - if (Str.isEmailValid(emailFromAccountModal)) { - return emailFromAccountModal; - } - // eslint-disable-next-line no-underscore-dangle - const emailFromConfigVariable = window.gbar_?.CONFIG?.[0]?.[4]?.ka?.[5]; - if (Str.isEmailValid(emailFromConfigVariable)) { - return String(emailFromConfigVariable); - } - const emailFromUserNameAndEmail = $('.gb_2e .gb_Fc :last-child').text(); - if (Str.isEmailValid(emailFromUserNameAndEmail)) { - return emailFromUserNameAndEmail; - } - } - return undefined; - }; - - const injectFCVarScript = () => { - const scriptElement = document.createElement('script'); - scriptElement.src = chrome.runtime.getURL('/js/common/core/feature-config-injector.js'); - (document.head || document.documentElement).appendChild(scriptElement); - }; - - const getInsightsFromHostVariables = () => { - const insights: WebmailVariantObject = { - newDataLayer: undefined, - newUi: undefined, - email: undefined, - gmailVariant: undefined, - }; - - try { - const extracted = (JSON.parse($('body > div#FC_VAR_PASS').text()) as unknown[]).map(String); - if (extracted[0] === 'true') { - insights.newDataLayer = true; - } else if (extracted[0] === 'false') { - insights.newDataLayer = false; - } - if (extracted[1] === 'true') { - insights.newUi = true; - } else if (extracted[1] === 'false') { - insights.newUi = false; - } - if (Str.isEmailValid(extracted[2])) { - insights.email = extracted[2].trim().toLowerCase(); - } - if (typeof insights.newDataLayer === 'undefined' && typeof insights.newUi === 'undefined' && typeof insights.email === 'undefined') { - insights.gmailVariant = 'html'; - } else if (insights.newUi === false) { - insights.gmailVariant = 'standard'; - } else if (insights.newUi === true) { - insights.gmailVariant = 'new'; - } - } catch (e) { - // no need to handle - } - return insights; - }; - - const start = async ( - acctEmail: string, - clientConfiguration: ClientConfiguration, - injector: Injector, - notifications: Notifications, - factory: XssSafeFactory, - notifyMurdered: () => void, - relayManager: RelayManager - ) => { - hijackGmailHotkeys(); - injector.btns(); - const messageRenderer = await MessageRenderer.newInstance(acctEmail, new Gmail(acctEmail), relayManager, factory); - replacer = new GmailElementReplacer(factory, clientConfiguration, acctEmail, messageRenderer, injector, notifications, relayManager); - await notifications.showInitial(acctEmail); - const intervaliFunctions = replacer.getIntervalFunctions(); - for (const intervalFunction of intervaliFunctions) { - intervalFunction.handler(); - replacePgpElsInterval = (window as unknown as ContentScriptWindow).TrySetDestroyableInterval(() => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (typeof (window as any).$ === 'function') { - intervalFunction.handler(); - } else { - // firefox will unload jquery when extension is restarted or updated - clearInterval(replacePgpElsInterval); - notifyMurdered(); - } - }, intervalFunction.interval); - } - }; - - const hijackGmailHotkeys = () => { - const keys = Env.keyCodes(); - const unsecureReplyKeyShortcuts = [keys.a, keys.r, keys.A, keys.R, keys.f, keys.F]; - $(document).keypress(e => { - Catch.try(() => { - const causesUnsecureReply = unsecureReplyKeyShortcuts.includes(e.which); - if ( - causesUnsecureReply && - !$(document.activeElement!).is('input, select, textarea, div[contenteditable="true"]') && // eslint-disable-line @typescript-eslint/no-non-null-assertion - $('iframe.reply_message').length - ) { - e.stopImmediatePropagation(); - replacer.setReplyBoxEditable().catch(Catch.reportErr); - } - })(); - }); - }; - - injectFCVarScript(); - await Time.sleep(100); // Wait until injected dom is added - const hostPageInfo = getInsightsFromHostVariables(); - await contentScriptSetupIfVacant({ - name: 'gmail', - variant: hostPageInfo.gmailVariant, - getUserAccountEmail, - getUserFullName: () => $('div.gb_hb div.gb_lb').text() || $('div.gb_Fb.gb_Hb').text(), - getReplacer: () => replacer, - start, - }); - }; - // when we support more webmails, there will be if/else here to figure out which one to run - // in which case each *WebmailStartup function should go into its own file - await gmailWebmailStartup(); + await new GmailWebmailStartup().asyncConstructor(); })(); diff --git a/test/source/mock/lib/api.ts b/test/source/mock/lib/api.ts index 6fdf8ba249d..cd3b505f4aa 100644 --- a/test/source/mock/lib/api.ts +++ b/test/source/mock/lib/api.ts @@ -152,7 +152,7 @@ export class Api { const address = this.server.address(); const port = typeof address === 'object' && address ? address.port : undefined; const msg = `${this.apiName} listening on ${port}`; - console.log(msg); + console.debug(msg); resolve(); }); this.server.on('error', e => { diff --git a/test/source/test.ts b/test/source/test.ts index d29254f556c..fd22729962b 100644 --- a/test/source/test.ts +++ b/test/source/test.ts @@ -42,6 +42,7 @@ const consts = { PROMISE_TIMEOUT_OVERALL: undefined as unknown as Promise, // will be set right below IS_LOCAL_DEBUG: process.argv.includes('--debug') ? true : false, // run locally by developer, not in ci }; + /* eslint-enable @typescript-eslint/naming-convention */ console.info('consts: ', JSON.stringify(consts), '\n'); consts.PROMISE_TIMEOUT_OVERALL = new Promise((resolve, reject) => setTimeout(() => reject(new Error(`TIMEOUT_OVERALL`)), consts.TIMEOUT_OVERALL)); @@ -200,17 +201,14 @@ test.after.always('evaluate Catch.reportErr errors', async t => { } }); -test.afterEach.always('send debug info if any', async t => { +test.afterEach.always('finalize', async t => { console.info(`${t.passed ? 'passed' : 'FAILED'} test, ${t.title}`); const failRnd = Util.lousyRandom(); - const testId = `FlowCrypt Browser Extension ${testVariant} ${failRnd}`; - const debugHtmlAttachments = getDebugHtmlAtts(testId, t.context as TestContext); + const debugHtmlAttachments = getDebugHtmlAtts(t.title, t.context as TestContext); if (debugHtmlAttachments.length) { - console.info(`FAIL ID ${testId}`); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error standaloneTestTimeout(t, consts.TIMEOUT_SHORT, t.title); - console.info(`There are ${debugHtmlAttachments.length} debug files.`); const debugArtifactDir = realpathSync(`${__dirname}/..`) + '/debugArtifacts'; try { mkdirSync(debugArtifactDir); @@ -223,10 +221,14 @@ test.afterEach.always('send debug info if any', async t => { const filePath = `${debugArtifactDir}/${fileName}`; console.info(`Writing debug file ${fileName}`); writeFileSync(filePath, debugHtmlAttachments[i]); + try { + await asyncExec(`artifact push job ${filePath}`); + } catch (e) { + // probably local environment without semaphore CLI tooling + } } - console.info('All debug files written.'); } else if (!t.passed) { - console.info(`no fails to debug`); + console.info(`No debug artifacts created for this failure`); } t.pass(); });