Skip to content

Commit 7b0943f

Browse files
authored
Merge branch 'master' into dependabot/npm_and_yarn/ava-6.1.1
2 parents 20675d2 + 9a3ba05 commit 7b0943f

File tree

16 files changed

+321
-273
lines changed

16 files changed

+321
-273
lines changed

.github/codeql/codeql-config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ paths-ignore:
22
- node_modules
33
- extension/lib
44
- extension/types
5-
- test/source/**/*.ts
5+
- test/source/

.semaphore/semaphore.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
version: v1.0
2-
name: Flowcrypt Node Core Tests
2+
name: FlowCrypt Extension Tests
33
agent:
44
machine:
55
type: e2-standard-4

extension/js/common/api/account-servers/external-service.ts

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { Buf } from '../../core/buf.js';
1313
import { ClientConfigurationError, ClientConfigurationJson } from '../../client-configuration.js';
1414
import { InMemoryStore } from '../../platform/store/in-memory-store.js';
1515
import { Serializable } from '../../platform/store/abstract-store.js';
16-
import { GoogleOAuth } from '../authentication/google/google-oauth.js';
1716
import { AuthenticationConfiguration } from '../../authentication-configuration.js';
1817
import { Xss } from '../../platform/xss.js';
1918

@@ -193,27 +192,6 @@ export class ExternalService extends Api {
193192
method: 'POST',
194193
}
195194
: undefined;
196-
try {
197-
return await ExternalService.apiCall(this.url, path, values, progress, await this.authHdr(), 'json');
198-
} catch (firstAttemptErr) {
199-
const idToken = await InMemoryStore.get(this.acctEmail, InMemoryStoreKeys.ID_TOKEN);
200-
if (ApiErr.isAuthErr(firstAttemptErr) && idToken) {
201-
// force refresh token
202-
const { email } = GoogleOAuth.parseIdToken(idToken);
203-
if (email) {
204-
return await ExternalService.apiCall(
205-
this.url,
206-
path,
207-
values,
208-
progress,
209-
{
210-
authorization: await GoogleOAuth.googleApiAuthHeader(email, true),
211-
},
212-
'json'
213-
);
214-
}
215-
}
216-
throw firstAttemptErr;
217-
}
195+
return await ExternalService.apiCall(this.url, path, values, progress, await this.authHdr(), 'json');
218196
};
219197
}

extension/js/common/api/shared/api.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,24 @@ export class Api {
412412
const result = await Api.ajaxWithJquery(req, resFmt, formattedData);
413413
return result as FetchResult<T, RT>;
414414
} else {
415-
return await Api.ajax(req, resFmt);
415+
try {
416+
return await Api.ajax(req, resFmt);
417+
} catch (firstAttemptErr) {
418+
const idToken = headers?.authorization?.split('Bearer ')?.[1];
419+
if (ApiErr.isAuthErr(firstAttemptErr) && idToken) {
420+
// Needed authorization from the service worker side to avoid circular dependency injection errors
421+
// that occur when importing GoogleAuth directly.
422+
const authorization = await BrowserMsg.send.bg.await.getGoogleApiAuthorization({ idToken });
423+
if (authorization) {
424+
const updatedReq = {
425+
...req,
426+
headers: { authorization },
427+
};
428+
return await Api.ajax(updatedReq, resFmt);
429+
}
430+
}
431+
throw firstAttemptErr;
432+
}
416433
}
417434
}
418435

extension/js/common/browser/browser-msg.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export namespace Bm {
7777
expiration: number | undefined;
7878
};
7979
export type InMemoryStoreGet = { acctEmail: string; key: string };
80+
export type GetGoogleApiAuthorization = { idToken: string };
8081
export type ReconnectAcctAuthPopup = { acctEmail: string; scopes?: string[]; screenDimensions: ScreenDimensions };
8182
export type Ajax = { req: ApiAjax; resFmt: ResFmt };
8283
export type AjaxProgress = { operationId: string; percent?: number; loaded: number; total: number; expectedTransferSize: number };
@@ -99,6 +100,7 @@ export namespace Bm {
99100
export type InMemoryStoreGet = string | undefined;
100101
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
101102
export type InMemoryStoreSet = void;
103+
export type GetGoogleApiAuthorization = string | undefined;
102104
export type ReconnectAcctAuthPopup = AuthRes;
103105
export type AjaxGmailAttachmentGetChunk = { chunk: Buf };
104106
export type ExpirationCacheGet<V> = Promise<V | undefined>;
@@ -113,6 +115,7 @@ export namespace Bm {
113115
| GetActiveTabInfo
114116
| ReconnectAcctAuthPopup
115117
| InMemoryStoreGet
118+
| GetGoogleApiAuthorization
116119
| InMemoryStoreSet
117120
| ExpirationCacheGet<unknown>
118121
| ExpirationCacheSet
@@ -124,6 +127,7 @@ export namespace Bm {
124127
export type AnyRequest =
125128
| PassphraseEntry
126129
| OpenPage
130+
| GetGoogleApiAuthorization
127131
| ExpirationCacheGet
128132
| ExpirationCacheSet<unknown>
129133
| ExpirationCacheDeleteExpired
@@ -202,6 +206,8 @@ export class BrowserMsg {
202206
getActiveTabInfo: () => BrowserMsg.sendAwait(undefined, 'get_active_tab_info', undefined, true) as Promise<Bm.Res.GetActiveTabInfo>,
203207
inMemoryStoreGet: (bm: Bm.InMemoryStoreGet) => BrowserMsg.sendAwait(undefined, 'inMemoryStoreGet', bm, true) as Promise<Bm.Res.InMemoryStoreGet>,
204208
inMemoryStoreSet: (bm: Bm.InMemoryStoreSet) => BrowserMsg.sendAwait(undefined, 'inMemoryStoreSet', bm, true) as Promise<Bm.Res.InMemoryStoreSet>,
209+
getGoogleApiAuthorization: (bm: Bm.GetGoogleApiAuthorization) =>
210+
BrowserMsg.sendAwait(undefined, 'getGoogleApiAuthorization', bm, true) as Promise<Bm.Res.GetGoogleApiAuthorization>,
205211
db: (bm: Bm.Db): Promise<Bm.Res.Db> => BrowserMsg.sendAwait(undefined, 'db', bm, true) as Promise<Bm.Res.Db>,
206212
ajax: (bm: Bm.Ajax): Promise<Bm.Res.Ajax> => BrowserMsg.sendAwait(undefined, 'ajax', bm, true) as Promise<Bm.Res.Ajax>,
207213
ajaxGmailAttachmentGetChunk: (bm: Bm.AjaxGmailAttachmentGetChunk) =>

extension/js/common/platform/catch.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,14 @@ export class Catch {
281281
}
282282
}
283283

284+
private static groupSimilarReports(value: string): string {
285+
return value
286+
.replace(/chrome-extension:\/\/[^\/]+\//, 'chrome-extension://EXTENSION_ID/')
287+
.replace(/https:\/\/www\.googleapis\.com\/gmail\/v1\/users\/me\/threads\/[^\/]+/, 'https://www.googleapis.com/gmail/v1/users/me/threads/THREAD_ID')
288+
.replace(/https:\/\/www\.googleapis\.com\/gmail\/v1\/users\/me\/messages\/[^\/]+/, 'https://www.googleapis.com/gmail/v1/users/me/messages/MESSAGE_ID')
289+
.replace(/https:\/\/www\.googleapis\.com\/gmail\/v1\/users\/me\/drafts\/[^\/]+/, 'https://www.googleapis.com/gmail/v1/users/me/drafts/DRAFT_ID');
290+
}
291+
284292
private static formatExceptionForReport(thrown: unknown, line?: number, col?: number): ErrorReport {
285293
if (!line || !col) {
286294
const { line: parsedLine, col: parsedCol } = Catch.getErrorLineAndCol(thrown);
@@ -298,11 +306,11 @@ export class Catch {
298306
const exception = Catch.formExceptionFromThrown(thrown);
299307
return {
300308
name: exception.name.substring(0, 50),
301-
message: exception.message.substring(0, 200),
302-
url: location.href.split('?')[0],
309+
message: Catch.groupSimilarReports(exception.message.substring(0, 200)),
310+
url: Catch.groupSimilarReports(location.href.split('?')[0]),
303311
line: line || 1,
304312
col: col || 1,
305-
trace: exception.stack || '',
313+
trace: Catch.groupSimilarReports(exception.stack || ''),
306314
version: VERSION,
307315
environment: Catch.RUNTIME_ENVIRONMENT,
308316
product: 'web-ext',

extension/js/content_scripts/webmail/setup-webmail-content-script.ts renamed to extension/js/content_scripts/webmail/generic/setup-webmail-content-script.ts

Lines changed: 50 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,37 @@
33
'use strict';
44

55
import Swal from 'sweetalert2';
6-
import { AccountServer } from '../../common/api/account-server.js';
7-
import { KeyManager } from '../../common/api/key-server/key-manager.js';
8-
import { ApiErr, BackendAuthErr } from '../../common/api/shared/api-error.js';
9-
import { BrowserMsgCommonHandlers } from '../../common/browser/browser-msg-common-handlers.js';
10-
import { Bm, BrowserMsg, TabIdRequiredError } from '../../common/browser/browser-msg.js';
11-
import { ContentScriptWindow } from '../../common/browser/browser-window.js';
12-
import { Env, WebMailName } from '../../common/browser/env.js';
13-
import { Time } from '../../common/browser/time.js';
14-
import { CommonHandlers, Ui } from '../../common/browser/ui.js';
15-
import { ClientConfiguration, ClientConfigurationError } from '../../common/client-configuration.js';
16-
import { Str, Url } from '../../common/core/common.js';
17-
import { InMemoryStoreKeys, VERSION } from '../../common/core/const.js';
18-
import { getLocalKeyExpiration, processAndStoreKeysFromEkmLocally } from '../../common/helpers.js';
19-
import { Injector } from '../../common/inject.js';
20-
import { Lang } from '../../common/lang.js';
21-
import { Notifications } from '../../common/notifications.js';
22-
import { Catch } from '../../common/platform/catch.js';
23-
import { AcctStore } from '../../common/platform/store/acct-store.js';
24-
import { GlobalStore } from '../../common/platform/store/global-store.js';
25-
import { InMemoryStore } from '../../common/platform/store/in-memory-store.js';
26-
import { WebmailVariantString, XssSafeFactory } from '../../common/xss-safe-factory.js';
27-
import { RelayManager } from '../../common/relay-manager.js';
6+
import { AccountServer } from '../../../common/api/account-server.js';
7+
import { KeyManager } from '../../../common/api/key-server/key-manager.js';
8+
import { ApiErr, BackendAuthErr } from '../../../common/api/shared/api-error.js';
9+
import { BrowserMsgCommonHandlers } from '../../../common/browser/browser-msg-common-handlers.js';
10+
import { Bm, BrowserMsg, TabIdRequiredError } from '../../../common/browser/browser-msg.js';
11+
import { ContentScriptWindow } from '../../../common/browser/browser-window.js';
12+
import { Env, WebMailName } from '../../../common/browser/env.js';
13+
import { Time } from '../../../common/browser/time.js';
14+
import { CommonHandlers, Ui } from '../../../common/browser/ui.js';
15+
import { ClientConfiguration, ClientConfigurationError } from '../../../common/client-configuration.js';
16+
import { Str, Url } from '../../../common/core/common.js';
17+
import { InMemoryStoreKeys, VERSION } from '../../../common/core/const.js';
18+
import { getLocalKeyExpiration, processAndStoreKeysFromEkmLocally } from '../../../common/helpers.js';
19+
import { Injector } from '../../../common/inject.js';
20+
import { Lang } from '../../../common/lang.js';
21+
import { Notifications } from '../../../common/notifications.js';
22+
import { Catch } from '../../../common/platform/catch.js';
23+
import { AcctStore } from '../../../common/platform/store/acct-store.js';
24+
import { GlobalStore } from '../../../common/platform/store/global-store.js';
25+
import { InMemoryStore } from '../../../common/platform/store/in-memory-store.js';
26+
import { WebmailVariantString, XssSafeFactory } from '../../../common/xss-safe-factory.js';
27+
import { RelayManager } from '../../../common/relay-manager.js';
28+
import { WebmailElementReplacer } from './webmail-element-replacer.js';
2829

2930
export type WebmailVariantObject = {
3031
newDataLayer: undefined | boolean;
3132
newUi: undefined | boolean;
3233
email: undefined | string;
3334
gmailVariant: WebmailVariantString;
3435
};
35-
export type IntervalFunction = { interval: number; handler: () => void };
36+
3637
type WebmailSpecificInfo = {
3738
name: WebMailName;
3839
variant: WebmailVariantString;
@@ -45,17 +46,9 @@ type WebmailSpecificInfo = {
4546
inject: Injector,
4647
notifications: Notifications,
4748
factory: XssSafeFactory,
48-
notifyMurdered: () => void,
4949
relayManager: RelayManager
5050
) => Promise<void>;
5151
};
52-
export interface WebmailElementReplacer {
53-
getIntervalFunctions: () => IntervalFunction[];
54-
setReplyBoxEditable: () => Promise<void>;
55-
reinsertReplyBox: (replyMsgId: string) => void;
56-
scrollToReplyBox: (replyMsgId: string) => void;
57-
scrollToCursorInReplyBox: (replyMsgId: string, cursorOffsetTop: number) => void;
58-
}
5952

6053
const win = window as unknown as ContentScriptWindow;
6154

@@ -275,23 +268,6 @@ export const contentScriptSetupIfVacant = async (webmailSpecific: WebmailSpecifi
275268
}
276269
};
277270

278-
const notifyMurdered = () => {
279-
const notifEl = document.getElementsByClassName('webmail_notifications')[0];
280-
const div = document.createElement('div');
281-
div.innerText = 'FlowCrypt has updated, please reload the tab. ';
282-
div.classList.add('webmail_notification');
283-
const a = document.createElement('a');
284-
a.href = '#';
285-
a.onclick = function () {
286-
const parent = (this as HTMLAnchorElement).parentNode as HTMLElement | undefined;
287-
parent?.remove();
288-
};
289-
a.textContent = 'close';
290-
div.appendChild(a);
291-
notifEl.textContent = '';
292-
notifEl.appendChild(div);
293-
};
294-
295271
const showPassphraseDialog = async (factory: XssSafeFactory, { longids, type, initiatorFrameId }: Bm.PassphraseDialog) => {
296272
await factory.showPassphraseDialog(longids, type, initiatorFrameId);
297273
};
@@ -451,7 +427,7 @@ export const contentScriptSetupIfVacant = async (webmailSpecific: WebmailSpecifi
451427
ppEvent,
452428
Catch.try(() => notifyExpiringKeys(acctEmail, clientConfiguration, notifications))
453429
);
454-
await webmailSpecific.start(acctEmail, clientConfiguration, inject, notifications, factory, notifyMurdered, relayManager);
430+
await webmailSpecific.start(acctEmail, clientConfiguration, inject, notifications, factory, relayManager);
455431
} catch (e) {
456432
if (e instanceof TabIdRequiredError) {
457433
console.error(`FlowCrypt cannot start: ${String(e)}`);
@@ -527,3 +503,28 @@ export const contentScriptSetupIfVacant = async (webmailSpecific: WebmailSpecifi
527503
}
528504
}
529505
};
506+
507+
/**
508+
* This happens when Firefox (or possibly Thunderbird) just updated FlowCrypt.
509+
*
510+
* Previous (meaning this currently running) instance of FlowCrypt will no longer
511+
* have access to its various classes or global variables, and is left in a
512+
* semi-functioning state. The best we can do is to ask the user to reload
513+
* the tab, which will load the newly updated version of the extension cleanly.
514+
*/
515+
export const notifyMurdered = () => {
516+
const notifEl = document.getElementsByClassName('webmail_notifications')[0];
517+
const div = document.createElement('div');
518+
div.innerText = 'FlowCrypt has updated, please reload the tab. ';
519+
div.classList.add('webmail_notification');
520+
const a = document.createElement('a');
521+
a.href = '#';
522+
a.onclick = function () {
523+
const parent = (this as HTMLAnchorElement).parentNode as HTMLElement | undefined;
524+
parent?.remove();
525+
};
526+
a.textContent = 'close';
527+
div.appendChild(a);
528+
notifEl.textContent = '';
529+
notifEl.appendChild(div);
530+
};
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact [email protected] */
2+
3+
import { ContentScriptWindow } from '../../../common/browser/browser-window';
4+
import { notifyMurdered } from './setup-webmail-content-script';
5+
6+
export type IntervalFunction = { interval: number; handler: () => void };
7+
8+
export abstract class WebmailElementReplacer {
9+
private replacePgpElsInterval: number;
10+
11+
public abstract getIntervalFunctions: () => IntervalFunction[];
12+
public abstract setReplyBoxEditable: () => Promise<void>;
13+
public abstract reinsertReplyBox: (replyMsgId: string) => void;
14+
public abstract scrollToReplyBox: (replyMsgId: string) => void;
15+
public abstract scrollToCursorInReplyBox: (replyMsgId: string, cursorOffsetTop: number) => void;
16+
17+
public runIntervalFunctionsPeriodically = () => {
18+
const intervalFunctions = this.getIntervalFunctions();
19+
for (const intervalFunction of intervalFunctions) {
20+
intervalFunction.handler();
21+
this.replacePgpElsInterval = (window as unknown as ContentScriptWindow).TrySetDestroyableInterval(() => {
22+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
23+
if (typeof (window as any).$ === 'function') {
24+
intervalFunction.handler();
25+
} else {
26+
// firefox will unload jquery when extension is restarted or updated
27+
clearInterval(this.replacePgpElsInterval);
28+
notifyMurdered();
29+
}
30+
}, intervalFunction.interval);
31+
}
32+
};
33+
}

extension/js/content_scripts/webmail/gmail-element-replacer.ts renamed to extension/js/content_scripts/webmail/gmail/gmail-element-replacer.ts

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,34 @@
22

33
'use strict';
44

5-
import { Dict, Str } from '../../common/core/common.js';
6-
import { FactoryReplyParams, XssSafeFactory } from '../../common/xss-safe-factory.js';
7-
import { IntervalFunction, WebmailElementReplacer } from './setup-webmail-content-script.js';
8-
import { ApiErr } from '../../common/api/shared/api-error.js';
9-
import { Attachment } from '../../common/core/attachment.js';
10-
import { BrowserMsg } from '../../common/browser/browser-msg.js';
11-
import { Catch } from '../../common/platform/catch.js';
12-
import { GlobalStore, LocalDraft } from '../../common/platform/store/global-store.js';
13-
import { Injector } from '../../common/inject.js';
14-
import { PubLookup } from '../../common/api/pub-lookup.js';
15-
import { Notifications } from '../../common/notifications.js';
16-
import { PgpArmor } from '../../common/core/crypto/pgp/pgp-armor.js';
17-
import { Ui } from '../../common/browser/ui.js';
18-
import { WebmailCommon } from '../../common/webmail.js';
19-
import { Xss } from '../../common/platform/xss.js';
20-
import { ClientConfiguration } from '../../common/client-configuration.js';
5+
import { Dict, Str } from '../../../common/core/common.js';
6+
import { FactoryReplyParams, XssSafeFactory } from '../../../common/xss-safe-factory.js';
7+
import { ApiErr } from '../../../common/api/shared/api-error.js';
8+
import { Attachment } from '../../../common/core/attachment.js';
9+
import { BrowserMsg } from '../../../common/browser/browser-msg.js';
10+
import { Catch } from '../../../common/platform/catch.js';
11+
import { GlobalStore, LocalDraft } from '../../../common/platform/store/global-store.js';
12+
import { Injector } from '../../../common/inject.js';
13+
import { PubLookup } from '../../../common/api/pub-lookup.js';
14+
import { Notifications } from '../../../common/notifications.js';
15+
import { PgpArmor } from '../../../common/core/crypto/pgp/pgp-armor.js';
16+
import { Ui } from '../../../common/browser/ui.js';
17+
import { WebmailCommon } from '../../../common/webmail.js';
18+
import { Xss } from '../../../common/platform/xss.js';
19+
import { ClientConfiguration } from '../../../common/client-configuration.js';
2120
// 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?
22-
import { ContactStore } from '../../common/platform/store/contact-store.js';
23-
import { MessageRenderer } from '../../common/message-renderer.js';
24-
import { RelayManager } from '../../common/relay-manager.js';
25-
import { MessageInfo } from '../../common/render-message.js';
21+
import { ContactStore } from '../../../common/platform/store/contact-store.js';
22+
import { MessageRenderer } from '../../../common/message-renderer.js';
23+
import { RelayManager } from '../../../common/relay-manager.js';
24+
import { MessageInfo } from '../../../common/render-message.js';
2625
import { GmailLoaderContext } from './gmail-loader-context.js';
27-
import { JQueryEl } from '../../common/loader-context-interface.js';
28-
import { MessageBody, Mime } from '../../common/core/mime.js';
29-
import { MsgBlock } from '../../common/core/msg-block.js';
30-
import { ReplyOption } from '../../../chrome/elements/compose-modules/compose-reply-btn-popover-module.js';
26+
import { JQueryEl } from '../../../common/loader-context-interface.js';
27+
import { MessageBody, Mime } from '../../../common/core/mime.js';
28+
import { MsgBlock } from '../../../common/core/msg-block.js';
29+
import { ReplyOption } from '../../../../chrome/elements/compose-modules/compose-reply-btn-popover-module.js';
30+
import { WebmailElementReplacer, IntervalFunction } from '../generic/webmail-element-replacer.js';
3131

32-
export class GmailElementReplacer implements WebmailElementReplacer {
32+
export class GmailElementReplacer extends WebmailElementReplacer {
3333
private debug = false;
3434

3535
private recipientHasPgpCache: Dict<boolean> = {};
@@ -74,6 +74,7 @@ export class GmailElementReplacer implements WebmailElementReplacer {
7474
private readonly notifications: Notifications,
7575
private readonly relayManager: RelayManager
7676
) {
77+
super();
7778
this.webmailCommon = new WebmailCommon(acctEmail, injector);
7879
this.pubLookup = new PubLookup(clientConfiguration);
7980
}

0 commit comments

Comments
 (0)