diff --git a/extension/chrome/settings/initial.htm b/extension/chrome/settings/initial.htm
index e59cf22de70..a5c9158bbd6 100644
--- a/extension/chrome/settings/initial.htm
+++ b/extension/chrome/settings/initial.htm
@@ -36,9 +36,13 @@
Click the
button in the
corner
+
+ Go to Thunderbird Mail and click the

button to get
+ started
+
-
+ |
|
diff --git a/extension/chrome/settings/initial.ts b/extension/chrome/settings/initial.ts
index 67e53d17512..4895ad61d28 100644
--- a/extension/chrome/settings/initial.ts
+++ b/extension/chrome/settings/initial.ts
@@ -8,7 +8,10 @@ import { View } from '../../js/common/view.js';
View.run(
class InitialView extends View {
public render = async () => {
- const browserName = Catch.browser().name === 'chrome' && Number(Catch.browser().v) >= 76 ? 'chrome' : 'firefox';
+ const browserName = Catch.browser().name === 'chrome' && Number(Catch.browser().v) >= 76 ? 'chrome' : Catch.browser().name;
+ if (browserName === 'thunderbird') {
+ $('#img-setup-arrow').hide();
+ }
const stepsEl = document.getElementById(`${browserName}-steps`);
if (stepsEl) {
stepsEl.style.display = 'block';
diff --git a/extension/js/common/browser/browser.ts b/extension/js/common/browser/browser.ts
index deab0720340..cd22fa31225 100644
--- a/extension/js/common/browser/browser.ts
+++ b/extension/js/common/browser/browser.ts
@@ -9,6 +9,7 @@ import { Attachment } from '../core/attachment.js';
import { Catch } from '../platform/catch.js';
import { Dict, Url, UrlParam } from '../core/common.js';
import { GlobalStore } from '../platform/store/global-store.js';
+import { BgUtils } from '../../service_worker/bgutils.js';
export class Browser {
public static objUrlCreate = (content: Uint8Array | string) => {
@@ -56,19 +57,23 @@ export class Browser {
const basePath = chrome.runtime.getURL(`chrome/settings/${path}`);
const pageUrlParams = rawPageUrlParams ? JSON.stringify(rawPageUrlParams) : undefined;
if (acctEmail || path === 'fatal.htm') {
- Browser.openExtensionTab(Url.create(basePath, { acctEmail, page, pageUrlParams }));
+ await Browser.openExtensionTab(Url.create(basePath, { acctEmail, page, pageUrlParams }));
} else if (addNewAcct) {
- Browser.openExtensionTab(Url.create(basePath, { addNewAcct }));
+ await Browser.openExtensionTab(Url.create(basePath, { addNewAcct }));
} else {
const acctEmails = await GlobalStore.acctEmailsGet();
- Browser.openExtensionTab(Url.create(basePath, { acctEmail: acctEmails[0], page, pageUrlParams }));
+ await Browser.openExtensionTab(Url.create(basePath, { acctEmail: acctEmails[0], page, pageUrlParams }));
}
};
- public static openExtensionTab = (url: string) => {
- const tab = window.open(url, 'flowcrypt');
- if (tab) {
- tab.focus();
+ public static openExtensionTab = async (url: string) => {
+ if (Catch.browser().name === 'thunderbird') {
+ await BgUtils.openExtensionTab(url);
+ } else {
+ const tab = window.open(url, 'flowcrypt');
+ if (tab) {
+ tab.focus();
+ }
}
};
}
diff --git a/extension/js/common/browser/env.ts b/extension/js/common/browser/env.ts
index 89512c0e290..1a81c2c075c 100644
--- a/extension/js/common/browser/env.ts
+++ b/extension/js/common/browser/env.ts
@@ -7,7 +7,7 @@
import { Url } from '../core/common.js';
-export type WebMailName = 'gmail' | 'outlook' | 'settings';
+export type WebMailName = 'gmail' | 'thunderbird' | 'outlook' | 'settings';
export type WebMailVersion = 'generic' | 'gmail2020' | 'gmail2022';
export class Env {
diff --git a/extension/js/common/inject.ts b/extension/js/common/inject.ts
index ad166535a7c..c2aa2ec1a66 100644
--- a/extension/js/common/inject.ts
+++ b/extension/js/common/inject.ts
@@ -14,6 +14,7 @@ import { PassphraseStore } from './platform/store/passphrase-store.js';
type Host = {
gmail: string;
+ thunderbird: string;
outlook: string;
settings: string;
};
@@ -26,11 +27,13 @@ export class Injector {
private container: { [key: string]: Host } = {
composeBtnSel: {
gmail: 'div.aeN, div.aBO', // .aeN for normal look, .aBO for new look https://github.com/FlowCrypt/flowcrypt-browser/issues/4099
+ thunderbird: '', // todo in another issue
outlook: 'div._fce_b',
settings: '#does_not_have',
},
finishSesionBtnSel: {
gmail: 'body',
+ thunderbird: '', // todo in another issue
outlook: '#does_not_have',
settings: '#settings > div.header',
},
diff --git a/extension/js/common/platform/catch.ts b/extension/js/common/platform/catch.ts
index cfd00b01565..f0eeb053b15 100644
--- a/extension/js/common/platform/catch.ts
+++ b/extension/js/common/platform/catch.ts
@@ -149,12 +149,14 @@ export class Catch {
}
public static browser(): {
- name: 'firefox' | 'ie' | 'chrome' | 'opera' | 'safari' | 'unknown';
+ name: 'firefox' | 'thunderbird' | 'ie' | 'chrome' | 'opera' | 'safari' | 'unknown';
v: number | undefined;
} {
// http://stackoverflow.com/questions/4825498/how-can-i-find-out-which-browser-a-user-is-using
if (/Firefox[\/\s](\d+\.\d+)/.test(navigator.userAgent)) {
return { name: 'firefox', v: Number(RegExp.$1) };
+ } else if (/Thunderbird[\/\s](\d+\.\d+)/.test(navigator.userAgent)) {
+ return { name: 'thunderbird', v: Number(RegExp.$1) };
} else if (/MSIE (\d+\.\d+);/.test(navigator.userAgent)) {
return { name: 'ie', v: Number(RegExp.$1) };
} else if (/Chrome[\/\s](\d+\.\d+)/.test(navigator.userAgent)) {
diff --git a/extension/js/common/settings.ts b/extension/js/common/settings.ts
index 176c9586ccd..74e5432fc9e 100644
--- a/extension/js/common/settings.ts
+++ b/extension/js/common/settings.ts
@@ -431,7 +431,7 @@ export class Settings {
public static async loginWithPopupShowModalOnErr(acctEmail: string, then: () => void = () => undefined) {
if (window !== window.top && !chrome.windows) {
// Firefox, chrome.windows isn't available in iframes
- Browser.openExtensionTab(Url.create(chrome.runtime.getURL(`chrome/settings/index.htm`), { acctEmail }));
+ await Browser.openExtensionTab(Url.create(chrome.runtime.getURL(`chrome/settings/index.htm`), { acctEmail }));
await Ui.modal.info(`Reload after logging in.`);
return window.location.reload();
}
diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts
new file mode 100644
index 00000000000..fc286d6e341
--- /dev/null
+++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts
@@ -0,0 +1,13 @@
+/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */
+
+'use strict';
+
+import { IntervalFunction, WebmailElementReplacer } from '../generic/webmail-element-replacer';
+
+export class ThunderbirdElementReplacer extends WebmailElementReplacer {
+ public getIntervalFunctions: () => IntervalFunction[];
+ public setReplyBoxEditable: () => Promise;
+ public reinsertReplyBox: (replyMsgId: string) => void;
+ public scrollToReplyBox: (replyMsgId: string) => void;
+ public scrollToCursorInReplyBox: (replyMsgId: string, cursorOffsetTop: number) => void;
+}
diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-webmail-startup.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-webmail-startup.ts
new file mode 100644
index 00000000000..2e0b16a4d84
--- /dev/null
+++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-webmail-startup.ts
@@ -0,0 +1,38 @@
+/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */
+import { ClientConfiguration } from '../../../common/client-configuration';
+import { Injector } from '../../../common/inject';
+import { Notifications } from '../../../common/notifications';
+import { contentScriptSetupIfVacant } from '../generic/setup-webmail-content-script';
+import { GmailElementReplacer } from '../gmail/gmail-element-replacer';
+import { ThunderbirdElementReplacer } from './thunderbird-element-replacer';
+
+export class ThunderbirdWebmailStartup {
+ private replacer: GmailElementReplacer;
+
+ public asyncConstructor = async () => {
+ await contentScriptSetupIfVacant({
+ name: 'thunderbird',
+ variant: undefined,
+ getUserAccountEmail: () => undefined, // todo, but can start with undefined
+ getUserFullName: () => undefined, // todo, but can start with undefined
+ getReplacer: () => new ThunderbirdElementReplacer(), // todo - add this class empty, methods do nothing
+ start: this.start,
+ });
+ };
+
+ private start = async (
+ acctEmail: string,
+ clientConfiguration: ClientConfiguration,
+ injector: Injector,
+ notifications: Notifications
+ // factory: XssSafeFactory, // todo in another issue
+ // relayManager: RelayManager // todo in another issue
+ ) => {
+ // injector.btns(); // todo in another issue - add compose button
+ this.replacer.runIntervalFunctionsPeriodically();
+ await notifications.showInitial(acctEmail);
+ notifications.show(
+ 'FlowCrypt Thunderbird support is still in early development, and not expected to function properly yet. Support will be gradually added in upcoming versions.'
+ );
+ };
+}
diff --git a/extension/js/content_scripts/webmail/webmail.ts b/extension/js/content_scripts/webmail/webmail.ts
index 5e5d6adf937..beadcc1047f 100644
--- a/extension/js/content_scripts/webmail/webmail.ts
+++ b/extension/js/content_scripts/webmail/webmail.ts
@@ -6,6 +6,7 @@
import { Catch } from '../../common/platform/catch.js';
import { GmailWebmailStartup } from './gmail/gmail-webmail-startup.js';
+import { ThunderbirdWebmailStartup } from './thunderbird/thunderbird-webmail-startup.js';
declare global {
interface Window {
@@ -16,5 +17,10 @@ declare global {
Catch.try(async () => {
// when we support more webmails, there will be if/else here to figure out which one to run
- await new GmailWebmailStartup().asyncConstructor();
+ const browserName = Catch.browser().name;
+ if (browserName === 'thunderbird') {
+ await new ThunderbirdWebmailStartup().asyncConstructor();
+ } else {
+ await new GmailWebmailStartup().asyncConstructor();
+ }
})();
diff --git a/scripts/build.sh b/scripts/build.sh
index 8415f1d7fff..3e34cbd493b 100755
--- a/scripts/build.sh
+++ b/scripts/build.sh
@@ -81,12 +81,13 @@ main() {
synchronize_files "build/chrome-enterprise"
synchronize_files "build/chrome-consumer"
synchronize_files "build/firefox-consumer"
+ synchronize_files "build/thunderbird-consumer"
}
exit 0
fi
if [[ "$#" == 1 ]] && [[ "$1" == "--incremental" ]]; then
- delete_directories $BUILD_DIRECTORY/firefox-consumer $BUILD_DIRECTORY/chrome-consumer $BUILD_DIRECTORY/chrome-consumer-mock $BUILD_DIRECTORY/chrome-enterprise $BUILD_DIRECTORY/chrome-enterprise-mock $BUILD_DIRECTORY/generic-extension-wip/js/content_scripts
+ delete_directories $BUILD_DIRECTORY/firefox-consumer $BUILD_DIRECTORY/thunderbird-consumer $BUILD_DIRECTORY/chrome-consumer $BUILD_DIRECTORY/chrome-consumer-mock $BUILD_DIRECTORY/chrome-enterprise $BUILD_DIRECTORY/chrome-enterprise-mock $BUILD_DIRECTORY/generic-extension-wip/js/content_scripts
# build concurrently - using standard typescript compiler with --incremental flag
mkdir -p $BUILD_DIRECTORY
build_typescript_project false "./tsconfig.json" true "$BUILD_DIRECTORY/tsconfig.tsbuildinfo"
@@ -150,6 +151,7 @@ main() {
cp -r $OUTPUT_DIRECTORY ./build/chrome-enterprise
cp -r $OUTPUT_DIRECTORY ./build/chrome-consumer
cp -r $OUTPUT_DIRECTORY ./build/firefox-consumer
+ cp -r $OUTPUT_DIRECTORY ./build/thunderbird-consumer
node ./build/tooling/build-types-and-manifests
}
diff --git a/test/source/patterns.ts b/test/source/patterns.ts
index 48de01d0a31..cb8a4ed9459 100644
--- a/test/source/patterns.ts
+++ b/test/source/patterns.ts
@@ -94,7 +94,7 @@ for (const buildType of ['chrome-consumer', 'chrome-enterprise']) {
}
for (const expectedPermission of expectedPermissions) {
if (!manifest.permissions.includes(expectedPermission)) {
- if (!(expectedPermission === 'unlimitedStorage' && buildType === 'firefox-consumer')) {
+ if (!(expectedPermission === 'unlimitedStorage' && (buildType === 'firefox-consumer' || buildType === 'thunderbird-consumer'))) {
console.error(`Missing permission '${expectedPermission}' in ${buildType}/manifest.json`);
errsFound++;
}
diff --git a/tooling/build-types-and-manifests.ts b/tooling/build-types-and-manifests.ts
index 807bcd6965a..d613e94efe1 100644
--- a/tooling/build-types-and-manifests.ts
+++ b/tooling/build-types-and-manifests.ts
@@ -15,8 +15,8 @@ const DIR = './build';
const version: string = JSON.parse(readFileSync('./package.json').toString()).version;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
-const addManifest = (toBuildType: string, transform: (manifest: { [k: string]: any }) => void) => {
- const manifest = JSON.parse(readFileSync(`${DIR}/generic-extension-wip/manifest.json`).toString());
+const addManifest = (toBuildType: string, transform: (manifest: { [k: string]: any }) => void, fromBuildType = 'generic-extension-wip') => {
+ const manifest = JSON.parse(readFileSync(`${DIR}/${fromBuildType}/manifest.json`).toString());
transform(manifest);
writeFileSync(`${DIR}/${toBuildType}/manifest.json`, JSON.stringify(manifest, undefined, 2));
};
@@ -49,6 +49,29 @@ addManifest('firefox-consumer', manifest => {
delete manifest.minimum_chrome_version;
});
+addManifest(
+ 'thunderbird-consumer',
+ manifest => {
+ manifest.browser_specific_settings.strict_min_version = '102.0';
+ manifest.browser_action.default_title = 'FlowCrypt Encryption for Thunderbird';
+ manifest.name = 'FlowCrypt Encryption for Thunderbird';
+ manifest.description = 'Secure end-to-end encryption with FlowCrypt'; // needs to updated later
+ manifest.compose_action = {
+ default_title: 'FlowCrypt', // eslint-disable-line @typescript-eslint/naming-convention
+ default_icon: '/img/logo/flowcrypt-logo-64-64.png', // eslint-disable-line @typescript-eslint/naming-convention
+ // default_popup will be updated later
+ default_popup: '/chrome/popups/default.htm', // eslint-disable-line @typescript-eslint/naming-convention
+ };
+ manifest.message_display_action = {
+ default_title: 'FlowCrypt', // eslint-disable-line @typescript-eslint/naming-convention
+ default_icon: '/img/logo/flowcrypt-logo-64-64.png', // eslint-disable-line @typescript-eslint/naming-convention
+ // default_popup will be updated later
+ default_popup: '/chrome/popups/default.htm', // eslint-disable-line @typescript-eslint/naming-convention
+ };
+ },
+ 'firefox-consumer'
+);
+
addManifest('chrome-enterprise', manifest => {
manifest.version = version;
manifest.name = 'FlowCrypt for Enterprise';