Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#1509 Migrate to Manifest V3 #5607

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2ab84bd
#1509 start manifest v3 migration
sosnovsky Feb 19, 2024
5042b9e
fix
sosnovsky Feb 19, 2024
1377e4a
fix: permissions
ioanmo226 Feb 26, 2024
3abf9f9
feat: use alarms intead of inMemoryStoreGetUntilAvailable
ioanmo226 Feb 26, 2024
e3b3e63
Revert "feat: use alarms intead of inMemoryStoreGetUntilAvailable"
ioanmo226 Feb 26, 2024
03cd8eb
fix: splash screen login issue
ioanmo226 Feb 26, 2024
56b3bdc
feat: (WIP) added custom settimeout and setinterval
ioanmo226 Feb 27, 2024
1049bc3
(WIP) move to settimeout and setinterval to background script
ioanmo226 Feb 28, 2024
05ca615
(WIP) setHandledTimeout and interval
ioanmo226 Feb 28, 2024
02c21fa
fix: CSP issue
ioanmo226 Feb 28, 2024
cb1423a
fix: revert timeout and interval
ioanmo226 Feb 28, 2024
75da801
feat: setinterval
ioanmo226 Feb 29, 2024
cfb4753
temp: disable tap
ioanmo226 Feb 29, 2024
b62bba8
fix: *.google.com manifest issue
ioanmo226 Feb 29, 2024
056d979
fix: oauth2.htm permission issue
ioanmo226 Mar 1, 2024
295c430
fix: trustedTypes error handling
ioanmo226 Mar 4, 2024
52b1004
fix: use openpgp ES module
ioanmo226 Mar 6, 2024
50bd21c
revert: openpgp removal from content script
ioanmo226 Mar 6, 2024
8b2e645
fix: conflicts
ioanmo226 Mar 11, 2024
02b10f5
wip: use webpack to bundle forge
ioanmo226 Mar 12, 2024
2c9bf6f
Merge branch 'master' into 1509-manifest-v3
ioanmo226 Mar 12, 2024
4623cc4
fix: unit test
ioanmo226 Mar 12, 2024
0959d7f
fix: unit test
ioanmo226 Mar 13, 2024
48c31d4
fix: ui test compose - test compose after reconnect account
ioanmo226 Mar 13, 2024
53367ee
feat: added forge mjs
ioanmo226 Mar 15, 2024
3934489
fix: forge
ioanmo226 Mar 18, 2024
df0dc31
fix: network available checK
ioanmo226 Mar 19, 2024
a852f87
fix: google reauth
ioanmo226 Mar 19, 2024
f7cb1cc
fix: replace xmlhttprequest with fetch and wip objurls
ioanmo226 Mar 26, 2024
4262aee
fix: ui test
ioanmo226 Mar 26, 2024
d431fc7
Revert "temp: disable tap"
ioanmo226 Mar 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions extension/js/common/browser/browser-msg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,14 +251,14 @@ export class BrowserMsg {
div.style.backgroundColor = '#a44';
div.style.padding = '4px 6px';
const a = document.createElement('a');
a.href = window.location.href.split('#')[0];
a.href = location.href.split('#')[0];
a.textContent = 'RELOAD';
a.style.color = 'white';
a.style.fontWeight = 'bold';
a.style.marginLeft = '12px';
div.appendChild(a);
}
window.document.body.appendChild(div);
document.body.appendChild(div);
};

public static generateTabId = (contentScript?: boolean) => {
Expand Down Expand Up @@ -303,6 +303,7 @@ export class BrowserMsg {
}
};
try {
console.log('GOT BG MESSAGE ' + msg.name);
if (Object.keys(BrowserMsg.HANDLERS_REGISTERED_BACKGROUND).includes(msg.name)) {
// standard or broadcast message
const handler: Bm.AsyncRespondingHandler = BrowserMsg.HANDLERS_REGISTERED_BACKGROUND[msg.name];
Expand All @@ -323,7 +324,7 @@ export class BrowserMsg {

protected static listenForWindowMessages = (dest: Bm.Dest) => {
const extensionOrigin = Env.getExtensionOrigin();
window.addEventListener('message', async e => {
addEventListener('message', async e => {
if (e.origin !== 'https://mail.google.com' && e.origin !== extensionOrigin) return;
const encryptedMsg = e.data as SymEncryptedMessage;
if (BrowserMsg.processed.has(encryptedMsg.uid)) return;
Expand Down Expand Up @@ -438,7 +439,7 @@ export class BrowserMsg {
// todo: can objUrls be deleted by another recipient?
const encryptedMsg = await SymmetricMessageEncryption.encrypt(validMsg);
BrowserMsg.sendToChildren(encryptedMsg);
window.postMessage(encryptedMsg, '*');
postMessage(encryptedMsg, '*');
BrowserMsg.sendUpParentLine(encryptedMsg);
})();
}
Expand Down Expand Up @@ -505,7 +506,7 @@ export class BrowserMsg {
let w: Window = window;
while (w.parent && w.parent !== w) {
w = w.parent;
window.parent.postMessage(encryptedWithPropagationFlag, '*');
parent.postMessage(encryptedWithPropagationFlag, '*');
}
};
/**
Expand Down
59 changes: 56 additions & 3 deletions extension/js/common/core/expiration-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,65 @@
export class ExpirationCache<K, V> {
private cache = new Map<K, { value: V; expiration: number }>();

// eslint-disable-next-line @typescript-eslint/naming-convention
public constructor(public EXPIRATION_TICKS: number) {}
public constructor(public expirationTicks: number) {}

public set = async (key: K, value?: V, expiration?: number) => {
console.log('STORE SET ' + key);
if (value) {
await chrome.storage.session.set({ [`${key}`]: { value, expiration: expiration || Date.now() + this.expirationTicks } });
// this.cache.set(key, { value, expiration: expiration || Date.now() + this.expirationTicks });
} else {
await chrome.storage.session.remove(`${key}`); // set({ [`${key}`]: value });
// this.cache.delete(key);
}
};

public get = async (key: K): Promise<V | undefined> => {
// const found = this.cache.get(key);
const found = ((await chrome.storage.session.get(`${key}`)) as { value: V; expiration: number }) ?? undefined;
if (found) {
if (found.expiration > Date.now()) {
return found.value;
} else {
// expired, so delete it and return as if not found
this.cache.delete(key);
}
}
return undefined;
};

public deleteExpired = (additionalPredicate: (key: K, value: V) => boolean = () => false): void => {
const keysToDelete: K[] = [];
for (const [key, value] of this.cache.entries()) {
if (value.expiration <= Date.now() || additionalPredicate(key, value.value)) {
keysToDelete.push(key);
}
}
for (const key of keysToDelete) {
this.cache.delete(key);
}
};

// await the value if it's a promise and remove from cache in case of exception
// the value is provided along with the key as parameter to eliminate possibility of a missing (expired) record
public await = async (key: K, value: V): Promise<V> => {
try {
return await value;
} catch (e) {
if (this.get(key) === value) await this.set(key); // remove faulty record
Fixed Show fixed Hide fixed
return Promise.reject(e);
}
};
}

export class SimpleExpirationCache<K, V> {
private cache = new Map<K, { value: V; expiration: number }>();

public constructor(public expirationTicks: number) {}

public set = (key: K, value?: V, expiration?: number) => {
if (value) {
this.cache.set(key, { value, expiration: expiration || Date.now() + this.EXPIRATION_TICKS });
this.cache.set(key, { value, expiration: expiration || Date.now() + this.expirationTicks });
} else {
this.cache.delete(key);
}
Expand Down
8 changes: 4 additions & 4 deletions extension/js/common/downloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import { GmailRes } from './api/email-provider/gmail/gmail-parser.js';
import { Gmail } from './api/email-provider/gmail/gmail.js';
import { Attachment, Attachment$treatAs } from './core/attachment.js';
import { Buf } from './core/buf.js';
import { ExpirationCache } from './core/expiration-cache.js';
import { SimpleExpirationCache } from './core/expiration-cache.js';

export class Downloader {
private readonly chunkDownloads = new ExpirationCache<Attachment, Promise<Buf>>(2 * 60 * 60 * 1000); // 2 hours
private readonly fullMessages = new ExpirationCache<string, Promise<GmailRes.GmailMsg>>(24 * 60 * 60 * 1000); // 24 hours
private readonly rawMessages = new ExpirationCache<string, Promise<GmailRes.GmailMsg>>(24 * 60 * 60 * 1000); // 24 hours
private readonly chunkDownloads = new SimpleExpirationCache<Attachment, Promise<Buf>>(2 * 60 * 60 * 1000); // 2 hours
private readonly fullMessages = new SimpleExpirationCache<string, Promise<GmailRes.GmailMsg>>(24 * 60 * 60 * 1000); // 24 hours
private readonly rawMessages = new SimpleExpirationCache<string, Promise<GmailRes.GmailMsg>>(24 * 60 * 60 * 1000); // 24 hours

public constructor(private readonly gmail: Gmail) {}

Expand Down
4 changes: 2 additions & 2 deletions extension/js/common/message-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { JQueryEl, LoaderContextInterface } from './loader-context-interface.js'
import { Gmail } from './api/email-provider/gmail/gmail.js';
import { ApiErr } from './api/shared/api-error.js';
import { isCustomerUrlFesUsed } from './helpers.js';
import { ExpirationCache } from './core/expiration-cache.js';
import { SimpleExpirationCache } from './core/expiration-cache.js';

type ProcessedMessage = {
body: MessageBody;
Expand All @@ -43,7 +43,7 @@ type ProcessedMessage = {

export class MessageRenderer {
public readonly downloader: Downloader;
private readonly processedMessages = new ExpirationCache<string, Promise<ProcessedMessage>>(24 * 60 * 60 * 1000); // 24 hours
private readonly processedMessages = new SimpleExpirationCache<string, Promise<ProcessedMessage>>(24 * 60 * 60 * 1000); // 24 hours

private constructor(
private readonly acctEmail: string,
Expand Down
2 changes: 1 addition & 1 deletion extension/js/common/oauth2/oauth2_inject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ const win: Window = window;

// Redirect back to the extension itself so that we have priveledged access again
// Need to send BrowserMsg event back to GoogleAuth
const redirect = chrome.extension.getURL('/chrome/elements/oauth2.htm');
const redirect = chrome.runtime.getURL('/chrome/elements/oauth2.htm');

win.location = redirect + win.location.search;
27 changes: 16 additions & 11 deletions extension/js/common/platform/catch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export type ErrorReport = {

export class Catch {
public static RUNTIME_ENVIRONMENT = 'undetermined';
private static ORIG_ONERROR = window.onerror;
private static ORIG_ONERROR = onerror;
private static CONSOLE_MSG = ' Please report errors above to human@flowcrypt.com. We fix errors VERY promptly.';
private static IGNORE_ERR_MSG = [
// happens in gmail window when reloaded extension + now reloading gmail
Expand Down Expand Up @@ -119,7 +119,7 @@ export class Catch {
*/
public static reportErr = (e: unknown): boolean => {
const { line, col } = Catch.getErrorLineAndCol(e);
return Catch.onErrorInternalHandler(e instanceof Error ? e.message : String(e), window.location.href, line, col, e, true);
return Catch.onErrorInternalHandler(e instanceof Error ? e.message : String(e), location.href, line, col, e, true);
};

/**
Expand Down Expand Up @@ -167,9 +167,9 @@ export class Catch {
}
};

public static environment = (url = window.location.href): string => {
public static environment = (url = location.href): string => {
const browserName = Catch.browser().name;
const origin = new URL(window.location.href).origin;
const origin = new URL(location.href).origin;
let env = 'unknown';
if (url.indexOf('bnjglocicd') !== -1) {
env = 'ex:prod';
Expand Down Expand Up @@ -205,7 +205,7 @@ export class Catch {
Catch.test();
} catch (e) {
// return stack after removing first 3 lines plus url
return `${((e as Error).stack || '').split('\n').splice(3).join('\n')}\n\nurl: ${Catch.censoredUrl(window.location.href)}\n`;
return `${((e as Error).stack || '').split('\n').splice(3).join('\n')}\n\nurl: ${Catch.censoredUrl(location.href)}\n`;
}
return ''; // make ts happy - this will never happen
};
Expand Down Expand Up @@ -243,16 +243,18 @@ export class Catch {
}
const { line, col } = Catch.getErrorLineAndCol(e);
const msg = e instanceof Error ? e.message : String(e);
Catch.onErrorInternalHandler(`REJECTION: ${msg}`, window.location.href, line, col, e, true);
Catch.onErrorInternalHandler(`REJECTION: ${msg}`, location.href, line, col, e, true);
}
};

public static setHandledInterval = (cb: () => void | Promise<void>, ms: number): number => {
return window.setInterval(Catch.try(cb), ms); // error-handled: else setInterval will silently swallow errors
// TODO: Manifest V3
return ms; // window.setInterval(Catch.try(cb), ms); // error-handled: else setInterval will silently swallow errors
};

public static setHandledTimeout = (cb: () => void | Promise<void>, ms: number): number => {
return window.setTimeout(Catch.try(cb), ms); // error-handled: else setTimeout will silently swallow errors
// TODO: Manifest V3
return ms; // window.setTimeout(Catch.try(cb), ms); // error-handled: else setTimeout will silently swallow errors
};

public static doesReject = async (p: Promise<unknown>, errNeedle?: string[]) => {
Expand Down Expand Up @@ -294,7 +296,7 @@ export class Catch {
return {
name: exception.name.substring(0, 50),
message: exception.message.substring(0, 200),
url: window.location.href.split('?')[0],
url: location.href.split('?')[0],
line: line || 0,
col: col || 0,
trace: exception.stack || '',
Expand Down Expand Up @@ -402,5 +404,8 @@ export class Catch {
}

Catch.RUNTIME_ENVIRONMENT = Catch.environment();
window.onerror = Catch.onErrorInternalHandler as OnErrorEventHandler;
window.onunhandledrejection = Catch.onUnhandledRejectionInternalHandler;
// window is undefined in background service worker
if (typeof window !== 'undefined') {
window.onerror = Catch.onErrorInternalHandler as OnErrorEventHandler;
}
onunhandledrejection = Catch.onUnhandledRejectionInternalHandler;
2 changes: 1 addition & 1 deletion extension/js/common/platform/require.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type Codec = {
};

export const requireOpenpgp = (): typeof OpenPGP => {
if (window !== globalThis && Catch.browser().name === 'firefox') {
if (typeof window !== 'undefined' && window !== globalThis && Catch.browser().name === 'firefox') {
// fix Firefox sandbox permission issues as per convo https://github.com/FlowCrypt/flowcrypt-browser/pull/5013#discussion_r1148343995
window.Uint8Array.prototype.subarray = function (...args) {
return new Uint8Array(this).subarray(...args);
Expand Down
5 changes: 4 additions & 1 deletion extension/js/common/platform/store/in-memory-store.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */

import { AbstractStore } from './abstract-store.js';
import { BrowserMsg } from '../../browser/browser-msg.js';
import { Time } from '../../browser/time.js';
import { BrowserMsg } from '../../browser/browser-msg.js';

/**
* Temporary In-Memory store for sensitive values, expiring after clientConfiguration.in_memory_pass_phrase_session_length (or default 4 hours)
Expand All @@ -11,10 +11,13 @@ import { Time } from '../../browser/time.js';
export class InMemoryStore extends AbstractStore {
public static set = async (acctEmail: string, key: string, value?: string, expiration?: number) => {
return await BrowserMsg.send.bg.await.inMemoryStoreSet({ acctEmail, key, value, expiration });
// console.log(expiration);
// return chrome.storage.session.set({ [`${emailKeyIndex(acctEmail, key)}`]: value });
};

public static get = async (acctEmail: string, key: string): Promise<string | undefined> => {
return (await BrowserMsg.send.bg.await.inMemoryStoreGet({ acctEmail, key })) ?? undefined;
// return ((await chrome.storage.session.get([emailKeyIndex(acctEmail, key)])) as unknown as string) ?? undefined;
};

public static getUntilAvailable = async (acctEmail: string, key: string, retryCount = 20): Promise<string | undefined> => {
Expand Down
5 changes: 5 additions & 0 deletions extension/js/content_scripts/webmail/webmail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ Catch.try(async () => {
if (Str.isEmailValid(emailFromAccountDropdown)) {
return emailFromAccountDropdown;
}

const emailFromAccountModal = $('div.gb_Dc > div').last().text().trim().toLowerCase();
if (Str.isEmailValid(emailFromAccountModal)) {
return emailFromAccountModal;
}
}
return undefined;
};
Expand Down
71 changes: 71 additions & 0 deletions extension/js/service_worker/background.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */

'use strict';

import { GoogleOAuth } from '../common/api/authentication/google/google-oauth.js';
import { Bm, BrowserMsg } from '../common/browser/browser-msg.js';
import { emailKeyIndex } from '../common/core/common.js';
import { ExpirationCache } from '../common/core/expiration-cache.js';
import { BgHandlers } from './bg-handlers.js';
import { Catch } from '../common/platform/catch.js';
import { ContactStore } from '../common/platform/store/contact-store.js';
import { BgUtils } from './bgutils.js';
import { migrateGlobal, moveContactsToEmailsAndPubkeys, updateOpgpRevocations, updateSearchables, updateX509FingerprintsAndLongids } from './migrations.js';
import { GlobalStore, GlobalStoreDict } from '../common/platform/store/global-store.js';
import { VERSION } from '../common/core/const.js';
import { injectFcIntoWebmail } from './inject.js';

console.info('background.js service worker starting');

(async () => {
chrome.storage.onChanged.addListener((changes, namespace) => {
for (const [key, { oldValue, newValue }] of Object.entries(changes)) {
console.log(`Storage key "${key}" in namespace "${namespace}" changed.`, `Old value was "${oldValue}", new value is "${newValue}".`);
}
});

let db: IDBDatabase;
let storage: GlobalStoreDict;
const inMemoryStore = new ExpirationCache<string, string>(4 * 60 * 60 * 1000); // 4 hours

// TODO: Manifest V3
// Catch.setHandledInterval(() => inMemoryStore.deleteExpired(), 60000); // each minute
try {
await migrateGlobal();
await GlobalStore.set({ version: Number(VERSION.replace(/\./g, '')) });
storage = await GlobalStore.get(['settings_seen']);
} catch (e) {
await BgUtils.handleStoreErr(GlobalStore.errCategorize(e));
return;
}
if (!storage.settings_seen) {
await BgUtils.openSettingsPage('initial.htm'); // called after the very first installation of the plugin
// eslint-disable-next-line @typescript-eslint/naming-convention
await GlobalStore.set({ settings_seen: true });
}
try {
db = await ContactStore.dbOpen(); // takes 4-10 ms first time
await updateOpgpRevocations(db);
await updateX509FingerprintsAndLongids(db);
await updateSearchables(db);
await moveContactsToEmailsAndPubkeys(db);
} catch (e) {
await BgUtils.handleStoreErr(e);
return;
}
// storage related handlers
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
BrowserMsg.bgAddListener('db', (r: Bm.Db) => BgHandlers.dbOperationHandler(db, r));
BrowserMsg.bgAddListener('inMemoryStoreSet', async (r: Bm.InMemoryStoreSet) => inMemoryStore.set(emailKeyIndex(r.acctEmail, r.key), r.value, r.expiration));
BrowserMsg.bgAddListener('inMemoryStoreGet', async (r: Bm.InMemoryStoreGet) => inMemoryStore.get(emailKeyIndex(r.acctEmail, r.key)));

BrowserMsg.bgAddListener('ajax', BgHandlers.ajaxHandler);
BrowserMsg.bgAddListener('ajaxGmailAttachmentGetChunk', BgHandlers.ajaxGmailAttachmentGetChunkHandler);
BrowserMsg.bgAddListener('settings', BgHandlers.openSettingsPageHandler);
BrowserMsg.bgAddListener('update_uninstall_url', BgHandlers.updateUninstallUrl);
BrowserMsg.bgAddListener('get_active_tab_info', BgHandlers.getActiveTabInfo);
BrowserMsg.bgAddListener('reconnect_acct_auth_popup', (r: Bm.ReconnectAcctAuthPopup) => GoogleOAuth.newAuthPopup(r));
BrowserMsg.bgListen();
await BgHandlers.updateUninstallUrl({});
injectFcIntoWebmail();
})().catch(Catch.reportErr);
Loading
Loading