Skip to content

Commit

Permalink
upgrade @electron
Browse files Browse the repository at this point in the history
* TODO (switch back to upstream "node-spellchecker") atom/node-spellchecker#127
* TODO (blocker) electron/electron#20700
* TODO (blocker @electron v8) electron/electron#21154
  • Loading branch information
vladimiry committed Nov 20, 2019
1 parent c87fcca commit 00f9c40
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 143 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@
"rolling-rate-limiter": "0.1.11",
"rxjs": "6.5.3",
"sanitize-html": "1.20.1",
"spellchecker": "3.7.0",
"spellchecker": "https://github.com/vladimiry-playground/node-spellchecker#613ff91dd2d9a5ee0e86be8a3682beecc4e94887",
"ts-deferred": "1.0.4",
"tslib": "1.10.0",
"uuid": "3.3.3",
Expand Down Expand Up @@ -212,7 +212,7 @@
"cross-env": "6.0.3",
"css-loader": "3.2.0",
"cssnano": "4.1.10",
"electron": "6.0.12",
"electron": "7.1.2",
"electron-builder": "21.2.0",
"escape-string-regexp": "2.0.0",
"eslint": "6.6.0",
Expand Down
4 changes: 2 additions & 2 deletions src/electron-main/api/endpoints-builders/tray-icon/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,11 @@ export async function buildEndpoints(

browserWindow.setOverlayIcon(overlay, `Unread messages count: ${unread}`);
tray.setImage(icon);
app.setBadgeCount(unread);
app.badgeCount = unread;
} else {
browserWindow.setOverlayIcon(null, "");
tray.setImage(canvas.native);
app.setBadgeCount(0);
app.badgeCount = 0;
}
},
};
Expand Down
2 changes: 1 addition & 1 deletion src/electron-main/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export async function initApplicationMenu(ctx: Context): Promise<Menu> {
};
const templateItems: MenuItemConstructorOptions[] = PLATFORM === "darwin"
? [{
label: app.getName(),
label: app.name,
submenu: [
aboutItem,
{
Expand Down
13 changes: 10 additions & 3 deletions src/electron-main/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import mimeTypes from "mime-types";
import path from "path";
import pathIsInside from "path-is-inside";
import url from "url";
import {RegisterFileProtocolRequest, Session, app, protocol} from "electron";
import {Session, app, protocol} from "electron";
import {promisify} from "util";

import {Context} from "src/electron-main/model";
Expand All @@ -20,7 +20,11 @@ export function registerStandardSchemes(ctx: Context) {
protocol.registerSchemesAsPrivileged(
ctx.locations.protocolBundles.map(({scheme}) => ({
scheme,
privileges: {standard: true, secure: true},
privileges: {
corsEnabled: true,
secure: true,
standard: true,
},
})),
);
}
Expand Down Expand Up @@ -55,7 +59,10 @@ export async function registerSessionProtocols(ctx: Context, session: Session):
}
}

async function resolveFileSystemResourceLocation(directory: string, request: RegisterFileProtocolRequest): Promise<string> {
async function resolveFileSystemResourceLocation(
directory: string,
request: Arguments<Arguments<(typeof protocol)["registerBufferProtocol"]>[1]>[0],
): Promise<string> {
const resource = path.join(directory, new url.URL(request.url).pathname);

if (!pathIsInside(resource, directory)) {
Expand Down
172 changes: 58 additions & 114 deletions src/electron-main/web-request.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
import {OnBeforeSendHeadersDetails, OnHeadersReceivedDetails, Session} from "electron";
import {
BeforeSendResponse,
HeadersReceivedResponse,
OnBeforeSendHeadersListenerDetails,
OnHeadersReceivedListenerDetails,
Session,
} from "electron";
import {URL} from "url";

import {AccountType} from "src/shared/model/account";
import {Context} from "./model";
import {ElectronContextLocations} from "src/shared/model/electron";
import {ReadonlyDeep} from "type-fest";

// TODO drop these types when "requestHeaders / responseHeaders" get proper types
type RequestDetails = OnBeforeSendHeadersDetails & { requestHeaders: HeadersMap };
type ResponseDetails = OnHeadersReceivedDetails & { responseHeaders: HeadersMap<string[]> };
type RequestDetails = OnBeforeSendHeadersListenerDetails;
type ResponseDetails = OnHeadersReceivedListenerDetails;

interface HeadersMap<V extends string | string[] = string | string[]> {
[k: string]: V;
}

interface RequestProxy {
type RequestProxy = ReadonlyDeep<{
accountType: AccountType;
headers: {
origin: Exclude<ReturnType<typeof getHeader>, null>,
accessControlRequestHeaders: ReturnType<typeof getHeader>,
accessControlRequestMethod: ReturnType<typeof getHeader>,
};
}
}>;

const HEADERS = {
request: {
Expand All @@ -35,19 +38,32 @@ const HEADERS = {
accessControlAllowOrigin: "Access-Control-Allow-Origin",
accessControlExposeHeaders: "Access-Control-Expose-Headers",
},
};
const PROXIES = new Map<number, RequestProxy>();
} as const;

const PROXIES = new Map<RequestDetails["id"] | ResponseDetails["id"], RequestProxy>();

// TODO pass additional "account type" argument and apply only respective listeners
export function initWebRequestListeners(ctx: Context, session: Session) {
const resolveProxy: (details: RequestDetails) => RequestProxy | null = (() => {
const origins: { [k in AccountType]: string[] } = {
const resolveLocalWebClientOrigins = <T extends AccountType>(
accountType: T,
{webClients}: ElectronContextLocations,
): Record<T, string[]> => {
const result: ReturnType<typeof resolveLocalWebClientOrigins> = Object.create(null);

result[accountType] = Object
.values(webClients[accountType])
.map(({entryUrl}) => buildOrigin(new URL(entryUrl)));

return result;
};
const origins: { readonly [k in AccountType]: readonly string[] } = {
...resolveLocalWebClientOrigins("protonmail", ctx.locations),
...resolveLocalWebClientOrigins("tutanota", ctx.locations),
};

return (details: RequestDetails) => {
const proxies: { [k in AccountType]: ReturnType<typeof resolveRequestProxy> } = {
const proxies: Record<AccountType, ReturnType<typeof resolveRequestProxy>> = {
protonmail: resolveRequestProxy("protonmail", details, origins),
tutanota: resolveRequestProxy("tutanota", details, origins),
};
Expand All @@ -64,67 +80,52 @@ export function initWebRequestListeners(ctx: Context, session: Session) {
session.webRequest.onBeforeSendHeaders(
{urls: []},
(
requestDetailsArg,
details,
callback,
) => {
const requestDetails = requestDetailsArg as RequestDetails;
const {requestHeaders} = requestDetails;
const requestProxy = resolveProxy(requestDetails);
const {requestHeaders} = details;
const requestProxy = resolveProxy(details);

if (requestProxy) {
const {name} = getHeader(requestHeaders, HEADERS.request.origin) || {name: HEADERS.request.origin};
requestHeaders[name] = resolveFakeOrigin(requestProxy.accountType, requestDetails);
PROXIES.set(requestDetails.id, requestProxy);
requestHeaders[name] = resolveFakeOrigin(requestProxy.accountType, details);
PROXIES.set(details.id, requestProxy);
}

callback({cancel: false, requestHeaders});
callback({requestHeaders});
},
);

session.webRequest.onHeadersReceived(
(
responseDetailsArg,
details,
callback,
) => {
const responseDetails = responseDetailsArg as ResponseDetails;
const requestProxy = PROXIES.get(responseDetails.id);
const requestProxy = PROXIES.get(details.id);
const responseHeaders = requestProxy
? responseHeadersPatchHandlers[requestProxy.accountType]({responseDetails, requestProxy})
: responseDetails.responseHeaders;
? responseHeadersPatchHandlers[requestProxy.accountType]({details, requestProxy})
: details.responseHeaders;

callback({responseHeaders, cancel: false});
callback({responseHeaders});
},
);
}

function resolveFakeOrigin(accountType: AccountType, requestDetails: RequestDetails): string {
function resolveFakeOrigin(accountType: AccountType, {url}: RequestDetails): string {
if (accountType === "tutanota") {
// WARN: tutanota responds to the specific origins only
// it will not work for example with http://localhost:2015 origin, so they go with a whitelisting
return "http://localhost:9000";
}

// protonmail doesn't care much, so we generate the origin from request
return buildOrigin(new URL(requestDetails.url));
}

function resolveLocalWebClientOrigins<T extends AccountType>(
accountType: T,
{webClients}: ElectronContextLocations,
): Record<T, string[]> {
const result: ReturnType<typeof resolveLocalWebClientOrigins> = Object.create(null);

result[accountType] = Object
.values(webClients[accountType])
.map(({entryUrl}) => buildOrigin(new URL(entryUrl)));

return result;
return buildOrigin(new URL(url));
}

function resolveRequestProxy<T extends AccountType>(
accountType: T,
{requestHeaders, resourceType}: RequestDetails,
origins: Record<AccountType, string[]>,
origins: { readonly [k in AccountType]: readonly string[] },
): RequestProxy | null {
const originHeader = (
String(resourceType).toUpperCase() === "XHR" &&
Expand Down Expand Up @@ -156,10 +157,10 @@ function resolveRequestProxy<T extends AccountType>(
// since over time the server may start giving other headers
const responseHeadersPatchHandlers: {
[k in AccountType]: (
arg: { requestProxy: RequestProxy, responseDetails: ResponseDetails; },
arg: { requestProxy: RequestProxy, details: ResponseDetails; },
) => ResponseDetails["responseHeaders"];
} = (() => {
const commonPatch: typeof responseHeadersPatchHandlers[AccountType] = ({requestProxy, responseDetails: {responseHeaders}}) => {
const commonPatch: typeof responseHeadersPatchHandlers[AccountType] = ({requestProxy, details: {responseHeaders}}) => {
patchResponseHeader(
responseHeaders,
{
Expand Down Expand Up @@ -190,102 +191,45 @@ const responseHeadersPatchHandlers: {
};

const result: typeof responseHeadersPatchHandlers = {
protonmail: ({requestProxy, responseDetails}) => {
const {responseHeaders} = responseDetails;

commonPatch({requestProxy, responseDetails});

patchResponseHeader(
responseHeaders,
{
name: HEADERS.response.accessControlAllowCredentials,
values: ["true"],
},
{extend: false},
);
patchResponseHeader(
responseHeaders,
{
name: HEADERS.response.accessControlAllowHeaders,
values: [
...(requestProxy.headers.accessControlRequestHeaders || {
values: [
"authorization",
"cache-control",
"content-type",
"Date",
"x-eo-uid",
"x-pm-apiversion",
"x-pm-appversion",
"x-pm-session",
"x-pm-uid",
],
}).values,
],
},
);
patchResponseHeader(
responseHeaders,
{
name: HEADERS.response.accessControlExposeHeaders,
values: ["Date"],
},
);

return responseHeaders;
protonmail: ({requestProxy, details}) => {
return commonPatch({requestProxy, details});
},
tutanota: ({requestProxy, responseDetails}) => {
const {responseHeaders} = responseDetails;

commonPatch({requestProxy, responseDetails});

patchResponseHeader(
responseHeaders,
{
name: HEADERS.response.accessControlAllowHeaders,
values: [
...(requestProxy.headers.accessControlRequestHeaders || {
values: [
"content-type",
"v",
],
}).values,
],
},
);

return responseHeaders;
tutanota: ({requestProxy, details}) => {
return commonPatch({requestProxy, details});
},
};

return result;
})();

function patchResponseHeader(
headers: ResponseDetails["responseHeaders"],
patch: ReturnType<typeof getHeader>,
headers: HeadersReceivedResponse["responseHeaders"],
patch: ReadonlyDeep<ReturnType<typeof getHeader>>,
{replace, extend = true, _default = true}: { replace?: boolean; extend?: boolean; _default?: boolean } = {},
): void {
if (!patch) {
if (!patch || !headers) {
return;
}

const header: Exclude<ReturnType<typeof getHeader>, null> =
getHeader(headers, patch.name) || {name: patch.name, values: []};

if (_default && !header.values.length) {
headers[header.name] = patch.values;
headers[header.name] = [...patch.values];
return;
}

headers[header.name] = replace
? patch.values
? [...patch.values]
: extend
? [...header.values, ...patch.values]
: header.values;
}

function getHeader(headers: HeadersMap, nameCriteria: string): { name: string, values: string[]; } | null {
function getHeader(
headers: Exclude<HeadersReceivedResponse["responseHeaders"] | BeforeSendResponse["requestHeaders"], undefined>,
nameCriteria: string,
): { name: string, values: string[]; } | null {
const names = Object.keys(headers);
const resolvedIndex = names.findIndex((name) => name.toLowerCase() === nameCriteria.toLowerCase());
const resolvedName = resolvedIndex !== -1
Expand Down
8 changes: 4 additions & 4 deletions src/electron-main/window/about.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@ const resolveContent: (ctx: Context) => Promise<Unpacked<ReturnType<typeof injec
},
),
(() => {
const versions: typeof process.versions & Electron.Versions = process.versions;
const versionsProps: ReadonlyArray<Readonly<{ prop: keyof typeof versions; title: string; }>> = [
const props = [
{prop: "electron", title: "Electron"},
{prop: "chrome", title: "Chromium"},
{prop: "node", title: "Node"},
{prop: "v8", title: "V8"},
];
] as const;
const {versions} = process;
return `<ul class="list-versions align-items-left justify-content-center font-weight-light">
${
versionsProps
props
.map(({prop, title}) => sanitizeHtml(`<li>${title}: ${versions[prop]}</li>`))
.join("")
}
Expand Down
Loading

0 comments on commit 00f9c40

Please sign in to comment.