Skip to content

Commit

Permalink
revert(electron): revert http protocl in electron (toeverything#8655)
Browse files Browse the repository at this point in the history
fix AF-1583
  • Loading branch information
pengx17 committed Nov 1, 2024
1 parent 1973cea commit c915a3b
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 55 deletions.
4 changes: 2 additions & 2 deletions packages/frontend/apps/electron/scripts/generate-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ if (!process.env.SKIP_WEB_BUILD) {
const fullpath = path.join(affineWebOutDir, file);
let content = await fs.readFile(fullpath, 'utf-8');
// replace # sourceMappingURL=76-6370cd185962bc89.js.map
// to # sourceMappingURL=/{dir}/76-6370cd185962bc89.js.map
// to # sourceMappingURL=assets://./{dir}/76-6370cd185962bc89.js.map
content = content.replace(/# sourceMappingURL=(.*)\.map/g, (_, p1) => {
return `# sourceMappingURL=assets:///${dir}/${p1}.map`;
return `# sourceMappingURL=assets://./${dir}/${p1}.map`;
});
try {
await fs.writeFile(fullpath, content);
Expand Down
2 changes: 0 additions & 2 deletions packages/frontend/apps/electron/src/main/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,5 @@ const API_URL_MAPPING = {
internal: `https://insider.affine.pro`,
};

export const DEV_SERVER_URL = process.env.DEV_SERVER_URL;

export const CLOUD_BASE_URL =
process.env.DEV_SERVER_URL || API_URL_MAPPING[buildType];
4 changes: 1 addition & 3 deletions packages/frontend/apps/electron/src/main/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { CLOUD_BASE_URL } from './config';

export const mainWindowOrigin = CLOUD_BASE_URL;
export const mainWindowOrigin = process.env.DEV_SERVER_URL || 'file://.';
export const onboardingViewUrl = `${mainWindowOrigin}${mainWindowOrigin.endsWith('/') ? '' : '/'}onboarding`;
export const shellViewUrl = `${mainWindowOrigin}${mainWindowOrigin.endsWith('/') ? '' : '/'}shell.html`;
export const customThemeViewUrl = `${mainWindowOrigin}${mainWindowOrigin.endsWith('/') ? '' : '/'}theme-editor`;
133 changes: 86 additions & 47 deletions packages/frontend/apps/electron/src/main/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,37 @@ import { join } from 'node:path';

import { net, protocol, session } from 'electron';

import { CLOUD_BASE_URL, DEV_SERVER_URL } from './config';
import { CLOUD_BASE_URL } from './config';
import { logger } from './logger';
import { isOfflineModeEnabled } from './utils';
import { getCookies } from './windows-manager';

protocol.registerSchemesAsPrivileged([
{
scheme: 'assets',
privileges: {
secure: false,
corsEnabled: true,
supportFetchAPI: true,
standard: true,
bypassCSP: true,
},
},
]);

protocol.registerSchemesAsPrivileged([
{
scheme: 'file',
privileges: {
secure: false,
corsEnabled: true,
supportFetchAPI: true,
standard: true,
bypassCSP: true,
stream: true,
},
},
]);

const NETWORK_REQUESTS = ['/api', '/ws', '/socket.io', '/graphql'];
const webStaticDir = join(__dirname, '../resources/web-static');
Expand All @@ -13,57 +41,49 @@ function isNetworkResource(pathname: string) {
return NETWORK_REQUESTS.some(opt => pathname.startsWith(opt));
}

async function fetchLocalResource(request: Request) {
const url = new URL(request.url);
const pathname = url.pathname;
// this will be file types (in the web-static folder)
let filepath = '';
// if is a file type, load the file in resources
if (pathname.split('/').at(-1)?.includes('.')) {
filepath = join(webStaticDir, decodeURIComponent(pathname));
} else {
// else, fallback to load the index.html instead
filepath = join(webStaticDir, 'index.html');
}
return net.fetch('file://' + filepath, request);
}

async function handleHttpRequest(request: Request) {
const url = new URL(request.url);
const pathname = url.pathname;
const sameSite = url.host === new URL(CLOUD_BASE_URL).host;

const isStaticResource = sameSite && !isNetworkResource(pathname);
if (isStaticResource) {
return fetchLocalResource(request);
}
return net.fetch(request, {
async function handleFileRequest(request: Request) {
const clonedRequest = Object.assign(request.clone(), {
bypassCustomProtocolHandlers: true,
});
}

// mainly for loading sourcemap
// seems handle for http/https does not work for sourcemaps
async function handleAssetRequest(request: Request) {
return fetchLocalResource(request);
const urlObject = new URL(request.url);
if (isNetworkResource(urlObject.pathname)) {
// just pass through (proxy)
return net.fetch(
CLOUD_BASE_URL + urlObject.pathname + urlObject.search,
clonedRequest
);
} else {
// this will be file types (in the web-static folder)
let filepath = '';
// if is a file type, load the file in resources
if (urlObject.pathname.split('/').at(-1)?.includes('.')) {
// Sanitize pathname to prevent path traversal attacks
const decodedPath = decodeURIComponent(urlObject.pathname);
const normalizedPath = join(webStaticDir, decodedPath).normalize();
if (!normalizedPath.startsWith(webStaticDir)) {
// Attempted path traversal - reject by using empty path
filepath = join(webStaticDir, '');
} else {
filepath = normalizedPath;
}
} else {
// else, fallback to load the index.html instead
filepath = join(webStaticDir, 'index.html');
}
return net.fetch('file://' + filepath, clonedRequest);
}
}

export function registerProtocol() {
const isSecure = CLOUD_BASE_URL.startsWith('https://');

// do not proxy request when DEV_SERVER_URL is set (for local dev)
if (!DEV_SERVER_URL) {
protocol.handle(isSecure ? 'https' : 'http', request => {
return handleHttpRequest(request);
});
protocol.handle('file', request => {
return handleFileRequest(request);
});

protocol.handle('assets', request => {
return handleAssetRequest(request);
});
}
protocol.handle('assets', request => {
return handleFileRequest(request);
});

// hack for CORS
// todo: should use a whitelist
// todo(@pengx17): remove this
session.defaultSession.webRequest.onHeadersReceived(
(responseDetails, callback) => {
const { responseHeaders } = responseDetails;
Expand Down Expand Up @@ -97,15 +117,21 @@ export function registerProtocol() {
const protocol = url.protocol;
const origin = url.origin;

const sameSite =
url.host === new URL(CLOUD_BASE_URL).host || protocol === 'file:';

// offline whitelist
// 1. do not block non-api request for DEV_SERVER_URL
// 1. do not block non-api request for http://localhost || file:// (local dev assets)
// 2. do not block devtools
// 3. block all other requests
const blocked = (() => {
if (!isOfflineModeEnabled()) {
return false;
}
if (origin === DEV_SERVER_URL && !isNetworkResource(pathname)) {
if (
(protocol === 'file:' || origin.startsWith('http://localhost')) &&
!isNetworkResource(pathname)
) {
return false;
}
if ('devtools:' === protocol) {
Expand All @@ -122,6 +148,19 @@ export function registerProtocol() {
return;
}

// session cookies are set to file:// on production
// if sending request to the cloud, attach the session cookie (to affine cloud server)
if (isNetworkResource(pathname) && sameSite) {
const cookie = getCookies();
if (cookie) {
const cookieString = cookie.map(c => `${c.name}=${c.value}`).join('; ');
details.requestHeaders['cookie'] = cookieString;
}

// add the referer and origin headers
details.requestHeaders['referer'] ??= CLOUD_BASE_URL;
details.requestHeaders['origin'] ??= CLOUD_BASE_URL;
}
callback({
cancel: false,
requestHeaders: details.requestHeaders,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { app, shell } from 'electron';
app.on('web-contents-created', (_, contents) => {
const isInternalUrl = (url: string) => {
return (
process.env.DEV_SERVER_URL && url.startsWith(process.env.DEV_SERVER_URL)
(process.env.DEV_SERVER_URL &&
url.startsWith(process.env.DEV_SERVER_URL)) ||
url.startsWith('affine://') ||
url.startsWith('file://.')
);
};
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,10 @@ export class WebContentViewsManager {
};
}

export function getCookies() {
return WebContentViewsManager.instance.cookies;
}

// there is no proper way to listen to webContents resize event
// we will rely on window.resize event in renderer instead
export async function handleWebContentsResize(webContents?: WebContents) {
Expand Down

0 comments on commit c915a3b

Please sign in to comment.