Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
5520fe0
use chrome.runtime broadcast sends with custom generated tabId
Jul 26, 2023
2a252a6
Merge remote-tracking branch 'origin/master' into issue-5329-broadcas…
Jul 28, 2023
4c1ab6a
postMessage as backup (wip)
Jul 28, 2023
f76d041
simplify, removed background relay (wip)
Jul 29, 2023
df8cf42
fix broadcast propagation
Jul 30, 2023
3cfce61
fix parent propagation
Jul 31, 2023
640c60b
Merge remote-tracking branch 'origin/master' into issue-5329-broadcas…
Jul 31, 2023
3c57a7a
fetch() instead of BrowserMsg.ajax (wip)
Aug 7, 2023
eb7fed8
Merge remote-tracking branch 'origin/master' into issue-5329-broadcas…
Aug 7, 2023
7405ae3
Merge remote-tracking branch 'origin/master' into issue-5329-broadcas…
Aug 8, 2023
ada3139
better custom reponseText handling for 400 Bad request and test robus…
Aug 8, 2023
1f73b14
proper network error handling
Aug 10, 2023
cc22d8b
Update mock for token substitution
Aug 10, 2023
b4d4fbc
Merge remote-tracking branch 'origin/master' into issue-5329-broadcas…
Aug 16, 2023
5a55e51
Merge remote-tracking branch 'origin/master' into issue-5329-broadcas…
Aug 16, 2023
bab1c82
download progress and timeout implementation with fetch()
Aug 17, 2023
7a75e96
convert 'Failed to fetch' error to AjaxErr
Aug 17, 2023
9a613c5
Merge remote-tracking branch 'origin/master' into issue-5329-broadcas…
Aug 18, 2023
6769139
HTTP/2 support in mock
Aug 18, 2023
cc3e0b0
Status texts from dictionary for HTTP/2
Aug 18, 2023
058849c
fix HTTP/2 Authority check in mock
Aug 19, 2023
196643e
upload progress implementation for fetch()
Aug 20, 2023
ab5aaab
Merge remote-tracking branch 'origin/master' into issue-1509-fetch
Aug 20, 2023
8adbe64
fix
Aug 21, 2023
3f2d4d2
Merge remote-tracking branch 'origin/master' into issue-1509-fetch
Aug 21, 2023
427479d
Merge remote-tracking branch 'origin/master' into issue-1509-fetch
Aug 22, 2023
3314abb
dummy url for feature-detection, required in Firefox
Aug 22, 2023
62c7356
Merge remote-tracking branch 'origin/master' into issue-1509-fetch
Aug 22, 2023
e0294c6
removed unused Bm.Res.Ajax type
Aug 23, 2023
205cbc6
Merge branch 'master' into issue-1509-fetch
sosnovsky Aug 30, 2023
4d227af
Merge branch 'master' into issue-1509-fetch
sosnovsky Sep 5, 2023
f68b2f5
Merge branch 'master' into issue-1509-fetch
sosnovsky Sep 15, 2023
81c505d
Merge branch 'master' into issue-1509-fetch
sosnovsky Oct 3, 2023
4f249e3
Merge branch 'master' into issue-1509-fetch
sosnovsky Oct 9, 2023
824c9b0
use background page for ajax calls
sosnovsky Oct 9, 2023
7157840
add fetchWithRetry
sosnovsky Oct 12, 2023
c9a60f8
Merge branch 'master' into issue-1509-fetch
sosnovsky Oct 17, 2023
3aece70
use jquery for requests with upload progress
sosnovsky Oct 17, 2023
d847436
fixes for download progress
sosnovsky Oct 18, 2023
da3f5eb
Merge branch 'master' into issue-1509-fetch
sosnovsky Oct 19, 2023
8696318
fix refresh token oauth request in firefox
sosnovsky Oct 19, 2023
e195434
run tests with firefox
sosnovsky Oct 19, 2023
aeedc14
revert semaphore.yml changes
sosnovsky Oct 19, 2023
0b99697
use chrome for puppeteer
sosnovsky Oct 19, 2023
22902a8
run live tests
sosnovsky Oct 20, 2023
ad8a8cb
Merge branch 'master' into issue-1509-fetch
sosnovsky Oct 23, 2023
fdf9b7e
use background script for cors requests
sosnovsky Oct 23, 2023
229939f
Merge branch 'master' into issue-1509-fetch
sosnovsky Oct 25, 2023
a0d9e81
wip
sosnovsky Oct 25, 2023
3900595
use background page only for firefox
sosnovsky Oct 27, 2023
50ec672
Merge branch 'master' into issue-1509-fetch
sosnovsky Oct 30, 2023
4f2dd28
Merge branch 'master' into issue-1509-fetch
sosnovsky Oct 30, 2023
5127723
send ajax_progress from background page
sosnovsky Nov 1, 2023
150a572
debug live tests
sosnovsky Nov 2, 2023
a3d3a1e
live test debug
sosnovsky Nov 2, 2023
e3a756a
wip
sosnovsky Nov 2, 2023
e8fa39c
test fix
sosnovsky Nov 3, 2023
63f1f1c
live tests fix
sosnovsky Nov 3, 2023
37d9563
Merge branch 'master' into issue-1509-fetch
sosnovsky Nov 6, 2023
92e567d
live test fix
sosnovsky Nov 6, 2023
294b293
run live tests only in master
sosnovsky Nov 7, 2023
8b4fc26
re-enable tap
sosnovsky Nov 7, 2023
ebbdcd0
pr review
sosnovsky Nov 7, 2023
7c74b16
fix google url
sosnovsky Nov 8, 2023
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
2 changes: 1 addition & 1 deletion extension/chrome/elements/attachment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ export class AttachmentDownloadView extends View {
this.size = fileSize || this.size;
const progressEl = $('.download_progress');
if (!percent && this.size) {
percent = Math.floor(((received * 0.75) / this.size) * 100);
percent = Math.min(Math.floor((received / this.size) * 100), 100);
}
if (percent) {
progressEl.text(`${Math.min(100, percent)}%`);
Expand Down
23 changes: 4 additions & 19 deletions extension/chrome/elements/pgp_block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

'use strict';

import { Url, Value } from '../../js/common/core/common.js';
import { Url } from '../../js/common/core/common.js';
import { Assert } from '../../js/common/assert.js';
import { RenderMessage } from '../../js/common/render-message.js';
import { Attachment } from '../../js/common/core/attachment.js';
Expand All @@ -14,7 +14,7 @@ import { PgpBlockViewQuoteModule } from './pgp_block_modules/pgp-block-quote-mod
import { PgpBlockViewRenderModule } from './pgp_block_modules/pgp-block-render-module.js';
import { CommonHandlers, Ui } from '../../js/common/browser/ui.js';
import { View } from '../../js/common/view.js';
import { Bm, BrowserMsg } from '../../js/common/browser/browser-msg.js';
import { BrowserMsg } from '../../js/common/browser/browser-msg.js';

export class PgpBlockView extends View {
public readonly acctEmail: string; // needed for attachment decryption, probably should be refactored out
Expand All @@ -28,7 +28,6 @@ export class PgpBlockView extends View {
public readonly renderModule: PgpBlockViewRenderModule;
public readonly printModule = new PgpBlockViewPrintModule();
private readonly tabId = BrowserMsg.generateTabId();

private progressOperation?: {
text: string;
operationId: string; // to ignore possible stray notifications, we generate an id for each operation
Expand Down Expand Up @@ -65,32 +64,18 @@ export class PgpBlockView extends View {
BrowserMsg.addListener('pgp_block_render', async (msg: RenderMessage) => {
this.processMessage(msg);
});
BrowserMsg.addListener('ajax_progress', async (progress: Bm.AjaxProgress) => {
this.handleAjaxProgress(progress);
});
BrowserMsg.addListener('confirmation_result', CommonHandlers.createAsyncResultHandler());
BrowserMsg.listen([this.getDest(), this.frameId]); // we receive non-critical ajax_progress calls via frameId address
BrowserMsg.listen(this.getDest());
BrowserMsg.send.pgpBlockReady(this, { frameId: this.frameId, messageSender: this.getDest() });
};

private handleAjaxProgress = ({ operationId, percent, loaded, total, expectedTransferSize }: Bm.AjaxProgress) => {
if (this.progressOperation && this.progressOperation.operationId === operationId) {
const perc = Value.getPercentage(percent, loaded, total, expectedTransferSize);
if (typeof perc !== 'undefined') {
this.renderProgress({ ...this.progressOperation, perc });
}
return true;
}
return false;
};

private renderProgress = ({ operationId, text, perc, init }: { operationId: string; text: string; perc?: number; init?: boolean }) => {
if (init) {
this.progressOperation = { operationId, text };
} else if (this.progressOperation?.operationId !== operationId) {
return;
}
const renderText = perc ? `${text} ${perc}%` : text;
const renderText = perc ? `${text} ${Math.min(perc, 100)}%` : text;
this.renderModule.renderText(renderText);
};

Expand Down
2 changes: 1 addition & 1 deletion extension/chrome/settings/modules/change_passphrase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ View.run(
const prv = await KeyUtil.parse(this.mostUsefulPrv.keyInfo.private);
if ((await KeyUtil.decrypt(prv, String($('#current_pass_phrase').val()))) === true) {
await this.bruteForceProtection.passphraseCheckSucceed();
this.mostUsefulPrv!.key = prv; // eslint-disable-line @typescript-eslint/no-non-null-assertion
this.mostUsefulPrv.key = prv;
this.displayBlock('step_1_enter_new');
$('#new_pass_phrase').trigger('focus');
} else {
Expand Down
13 changes: 10 additions & 3 deletions extension/chrome/settings/setup/setup-key-manager-autogen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { SetupOptions, SetupView } from '../setup.js';
import { Ui } from '../../../js/common/browser/ui.js';
import { Url } from '../../../js/common/core/common.js';
import { AcctStore } from '../../../js/common/platform/store/acct-store.js';
import { ApiErr } from '../../../js/common/api/shared/api-error.js';
import { AjaxErr, ApiErr } from '../../../js/common/api/shared/api-error.js';
import { Api } from '../../../js/common/api/shared/api.js';
import { Settings } from '../../../js/common/settings.js';
import { KeyUtil } from '../../../js/common/core/crypto/key.js';
Expand Down Expand Up @@ -81,9 +81,16 @@ export class SetupWithEmailKeyManagerModule {
} catch (e) {
if (ApiErr.isNetErr(e) && (await Api.isInternetAccessible())) {
// frendly message when key manager is down, helpful during initial infrastructure setup
e.message =
`FlowCrypt Email Key Manager at ${this.view.clientConfiguration.getKeyManagerUrlForPrivateKeys()} cannot be reached. ` +
const url = this.view.clientConfiguration.getKeyManagerUrlForPrivateKeys();
const message =
`FlowCrypt Email Key Manager at ${url} cannot be reached. ` +
'If your organization requires a VPN, please connect to it. Else, please inform your network admin.';
if (e instanceof AjaxErr) {
e.message = message; // we assume isNetErr wasn't identified by the message property
} else {
// convert to AjaxErr, identifiable as a net error, with a custom message
throw AjaxErr.fromNetErr(message, e.stack, url);
}
}
throw e;
}
Expand Down
17 changes: 4 additions & 13 deletions extension/js/background_page/bg-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

'use strict';

import { Api } from '../common/api/shared/api.js';
import { BgUtils } from './bgutils.js';
import { Bm, BrowserMsg } from '../common/browser/browser-msg.js';
import { Bm } from '../common/browser/browser-msg.js';
import { Gmail } from '../common/api/email-provider/gmail/gmail.js';
import { GlobalStore } from '../common/platform/store/global-store.js';
import { ContactStore } from '../common/platform/store/contact-store.js';
import { Api } from '../common/api/shared/api.js';

export class BgHandlers {
public static openSettingsPageHandler: Bm.AsyncResponselessHandler = async ({ page, path, pageUrlParams, addNewAcct, acctEmail }: Bm.Settings) => {
Expand All @@ -30,16 +30,7 @@ export class BgHandlers {
};

public static ajaxHandler = async (r: Bm.Ajax): Promise<Bm.Res.Ajax> => {
if (r.req.context?.operationId) {
// progress updates were requested via messages
const frameId = r.req.context.frameId;
const operationId = r.req.context.operationId;
const expectedTransferSize = r.req.context.expectedTransferSize;
r.req.xhr = Api.getAjaxProgressXhrFactory({
download: (percent, loaded, total) => BrowserMsg.send.ajaxProgress(frameId, { percent, loaded, total, expectedTransferSize, operationId }),
});
}
return await Api.ajax(r.req, r.stack);
return await Api.ajax(r.req, r.resFmt);
};

public static ajaxGmailAttachmentGetChunkHandler = async (r: Bm.AjaxGmailAttachmentGetChunk): Promise<Bm.Res.AjaxGmailAttachmentGetChunk> => {
Expand All @@ -61,7 +52,7 @@ export class BgHandlers {
if (activeTabs[0].id !== undefined) {
type ScriptRes = { acctEmail: string | undefined; sameWorld: boolean | undefined }[];
chrome.tabs.executeScript(
activeTabs[0].id!, // eslint-disable-line @typescript-eslint/no-non-null-assertion
activeTabs[0].id,
{ code: 'var r = {acctEmail: window.account_email_global, sameWorld: window.same_world_global}; r' },
(result: ScriptRes) => {
resolve({
Expand Down
99 changes: 48 additions & 51 deletions extension/js/common/api/account-servers/external-service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */
'use strict';

import { Api, ProgressCb, ProgressCbs, ReqFmt, ReqMethod } from '../shared/api.js';
import { Api, ProgressCb, ProgressCbs } from '../shared/api.js';
import { AcctStore } from '../../platform/store/acct-store.js';
import { Dict, Str } from '../../core/common.js';
import { ErrorReport } from '../../platform/catch.js';
Expand All @@ -12,6 +12,7 @@ import { ParsedRecipients } from '../email-provider/email-provider-api.js';
import { Buf } from '../../core/buf.js';
import { ClientConfigurationError, ClientConfigurationJson } from '../../client-configuration.js';
import { InMemoryStore } from '../../platform/store/in-memory-store.js';
import { Serializable } from '../../platform/store/abstract-store.js';
import { GoogleOAuth } from '../authentication/google/google-oauth.js';
import { AuthenticationConfiguration } from '../../authentication-configuration.js';
import { Xss } from '../../platform/xss.js';
Expand Down Expand Up @@ -86,13 +87,13 @@ export class ExternalService extends Api {
};

public getServiceInfo = async (): Promise<FesRes.ServiceInfo> => {
return await this.request<FesRes.ServiceInfo>('GET', `/api/`);
return await this.request<FesRes.ServiceInfo>(`/api/`);
};

public fetchAndSaveClientConfiguration = async (): Promise<ClientConfigurationJson> => {
const auth = await this.request<AuthenticationConfiguration>('GET', `/api/${this.apiVersion}/client-configuration/authentication?domain=${this.domain}`);
const auth = await this.request<AuthenticationConfiguration>(`/api/${this.apiVersion}/client-configuration/authentication?domain=${this.domain}`);
await AcctStore.set(this.acctEmail, { authentication: auth });
const r = await this.request<FesRes.ClientConfiguration>('GET', `/api/${this.apiVersion}/client-configuration?domain=${this.domain}`);
const r = await this.request<FesRes.ClientConfiguration>(`/api/${this.apiVersion}/client-configuration?domain=${this.domain}`);
if (r.clientConfiguration && !r.clientConfiguration.flags) {
throw new ClientConfigurationError('missing_flags');
}
Expand All @@ -101,28 +102,26 @@ export class ExternalService extends Api {
};

public reportException = async (errorReport: ErrorReport): Promise<void> => {
await this.request('POST', `/api/${this.apiVersion}/log-collector/exception`, {}, errorReport);
await this.request(`/api/${this.apiVersion}/log-collector/exception`, { fmt: 'JSON', data: errorReport });
};

public helpFeedback = async (email: string, message: string): Promise<FesRes.HelpFeedback> => {
return await this.request<FesRes.HelpFeedback>('POST', `/api/${this.apiVersion}/account/feedback`, {}, { email, message });
return await this.request<FesRes.HelpFeedback>(`/api/${this.apiVersion}/account/feedback`, { fmt: 'JSON', data: { email, message } });
};

public reportEvent = async (tags: EventTag[], message: string, details?: string): Promise<void> => {
await this.request(
'POST',
`/api/${this.apiVersion}/log-collector/exception`,
{},
{
await this.request(`/api/${this.apiVersion}/log-collector/exception`, {
fmt: 'JSON',
data: {
tags,
message,
details,
}
);
},
});
};

public webPortalMessageNewReplyToken = async (): Promise<FesRes.ReplyToken> => {
return await this.request<FesRes.ReplyToken>('POST', `/api/${this.apiVersion}/message/new-reply-token`, {}, {});
return await this.request<FesRes.ReplyToken>(`/api/${this.apiVersion}/message/new-reply-token`, { fmt: 'JSON', data: {} });
};

public webPortalMessageUpload = async (
Expand Down Expand Up @@ -151,50 +150,52 @@ export class ExternalService extends Api {
),
});
const multipartBody = { content, details };
return await this.request<FesRes.MessageUpload>('POST', `/api/${this.apiVersion}/message`, {}, multipartBody, { upload: progressCb });
return await this.request<FesRes.MessageUpload>(`/api/${this.apiVersion}/message`, { fmt: 'FORM', data: multipartBody }, { upload: progressCb });
Copy link
Contributor Author

@rrrooommmaaa rrrooommmaaa Aug 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If upload progress needs to be supported for this call, should convert the body to 'TEXT' the same way as in Gmail.msgSend and make sure FES supports HTTP/2

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like currently FES supports only HTTP/1.1:

Screenshot 2023-08-23 at 22 39 39

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't upload progress achievable regardless of http version? We know how much data we intend to send, and we surely know how much data we've sent so far.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetch upload streaming currently has only limited support, only in Chrome > 105 and HTTP/2

};

public messageGatewayUpdate = async (externalId: string, emailGatewayMessageId: string) => {
await this.request(
'POST',
`/api/${this.apiVersion}/message/${externalId}/gateway`,
{},
{
await this.request(`/api/${this.apiVersion}/message/${externalId}/gateway`, {
fmt: 'JSON',
data: {
emailGatewayMessageId,
}
);
},
});
};

private authHdr = async (): Promise<Dict<string>> => {
private authHdr = async (): Promise<{ authorization: string }> => {
const idToken = await InMemoryStore.getUntilAvailable(this.acctEmail, InMemoryStoreKeys.ID_TOKEN);
if (idToken) {
return { Authorization: `Bearer ${idToken}` }; // eslint-disable-line @typescript-eslint/naming-convention
return { authorization: `Bearer ${idToken}` };
}
// user will not actually see this message, they'll see a generic login prompt
throw new BackendAuthErr('Missing id token, please re-authenticate');
};

private request = async <RT>(method: ReqMethod, path: string, headers: Dict<string> = {}, vals?: Dict<unknown>, progress?: ProgressCbs): Promise<RT> => {
let reqFmt: ReqFmt | undefined;
if (progress) {
reqFmt = 'FORM';
} else if (method !== 'GET') {
reqFmt = 'JSON';
}
private request = async <RT>(
path: string,
vals?:
| {
data: Dict<string | Attachment>;
fmt: 'FORM';
}
| { data: Dict<Serializable>; fmt: 'JSON' },
progress?: ProgressCbs
): Promise<RT> => {
const values:
| {
data: Dict<string | Attachment>;
fmt: 'FORM';
method: 'POST';
}
| { data: Dict<Serializable>; fmt: 'JSON'; method: 'POST' }
| undefined = vals
? {
...vals,
method: 'POST',
}
: undefined;
try {
return await ExternalService.apiCall(
this.url,
path,
vals,
reqFmt,
progress,
{
...headers,
...(await this.authHdr()),
},
'json',
method
);
return await ExternalService.apiCall(this.url, path, values, progress, await this.authHdr(), 'json');
} catch (firstAttemptErr) {
const idToken = await InMemoryStore.get(this.acctEmail, InMemoryStoreKeys.ID_TOKEN);
if (ApiErr.isAuthErr(firstAttemptErr) && idToken) {
Expand All @@ -204,16 +205,12 @@ export class ExternalService extends Api {
return await ExternalService.apiCall(
this.url,
path,
vals,
reqFmt,
values,
progress,
{
...headers,
// eslint-disable-next-line @typescript-eslint/naming-convention
Authorization: await GoogleOAuth.googleApiAuthHeader(email, true),
authorization: await GoogleOAuth.googleApiAuthHeader(email, true),
},
'json',
method
'json'
);
}
}
Expand Down
Loading