mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-15 05:37:32 +00:00
build(electron): use live origin http protocol instead of file:// (#8464)
fix AF-1428
This commit is contained in:
@@ -71,9 +71,9 @@ if (!process.env.SKIP_WEB_BUILD) {
|
|||||||
const fullpath = path.join(affineWebOutDir, file);
|
const fullpath = path.join(affineWebOutDir, file);
|
||||||
let content = await fs.readFile(fullpath, 'utf-8');
|
let content = await fs.readFile(fullpath, 'utf-8');
|
||||||
// replace # sourceMappingURL=76-6370cd185962bc89.js.map
|
// replace # sourceMappingURL=76-6370cd185962bc89.js.map
|
||||||
// to # sourceMappingURL=assets://./{dir}/76-6370cd185962bc89.js.map
|
// to # sourceMappingURL=/{dir}/76-6370cd185962bc89.js.map
|
||||||
content = content.replace(/# sourceMappingURL=(.*)\.map/g, (_, p1) => {
|
content = content.replace(/# sourceMappingURL=(.*)\.map/g, (_, p1) => {
|
||||||
return `# sourceMappingURL=assets://./${dir}/${p1}.map`;
|
return `# sourceMappingURL=assets:///${dir}/${p1}.map`;
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await fs.writeFile(fullpath, content);
|
await fs.writeFile(fullpath, content);
|
||||||
|
|||||||
@@ -30,5 +30,7 @@ const API_URL_MAPPING = {
|
|||||||
internal: `https://insider.affine.pro`,
|
internal: `https://insider.affine.pro`,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const DEV_SERVER_URL = process.env.DEV_SERVER_URL;
|
||||||
|
|
||||||
export const CLOUD_BASE_URL =
|
export const CLOUD_BASE_URL =
|
||||||
process.env.DEV_SERVER_URL || API_URL_MAPPING[buildType];
|
process.env.DEV_SERVER_URL || API_URL_MAPPING[buildType];
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
export const mainWindowOrigin = process.env.DEV_SERVER_URL || 'file://.';
|
import { CLOUD_BASE_URL } from './config';
|
||||||
|
|
||||||
|
export const mainWindowOrigin = CLOUD_BASE_URL;
|
||||||
export const onboardingViewUrl = `${mainWindowOrigin}${mainWindowOrigin.endsWith('/') ? '' : '/'}onboarding`;
|
export const onboardingViewUrl = `${mainWindowOrigin}${mainWindowOrigin.endsWith('/') ? '' : '/'}onboarding`;
|
||||||
export const shellViewUrl = `${mainWindowOrigin}${mainWindowOrigin.endsWith('/') ? '' : '/'}shell.html`;
|
export const shellViewUrl = `${mainWindowOrigin}${mainWindowOrigin.endsWith('/') ? '' : '/'}shell.html`;
|
||||||
export const customThemeViewUrl = `${mainWindowOrigin}${mainWindowOrigin.endsWith('/') ? '' : '/'}theme-editor`;
|
export const customThemeViewUrl = `${mainWindowOrigin}${mainWindowOrigin.endsWith('/') ? '' : '/'}theme-editor`;
|
||||||
|
|||||||
@@ -2,37 +2,9 @@ import { join } from 'node:path';
|
|||||||
|
|
||||||
import { net, protocol, session } from 'electron';
|
import { net, protocol, session } from 'electron';
|
||||||
|
|
||||||
import { CLOUD_BASE_URL } from './config';
|
import { CLOUD_BASE_URL, DEV_SERVER_URL } from './config';
|
||||||
import { logger } from './logger';
|
import { logger } from './logger';
|
||||||
import { isOfflineModeEnabled } from './utils';
|
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 NETWORK_REQUESTS = ['/api', '/ws', '/socket.io', '/graphql'];
|
||||||
const webStaticDir = join(__dirname, '../resources/web-static');
|
const webStaticDir = join(__dirname, '../resources/web-static');
|
||||||
@@ -41,39 +13,56 @@ function isNetworkResource(pathname: string) {
|
|||||||
return NETWORK_REQUESTS.some(opt => pathname.startsWith(opt));
|
return NETWORK_REQUESTS.some(opt => pathname.startsWith(opt));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleFileRequest(request: Request) {
|
async function fetchLocalResource(request: Request) {
|
||||||
const clonedRequest = Object.assign(request.clone(), {
|
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;
|
||||||
|
|
||||||
|
console.log('request', request.url);
|
||||||
|
|
||||||
|
const isStaticResource = sameSite && !isNetworkResource(pathname);
|
||||||
|
if (isStaticResource) {
|
||||||
|
return fetchLocalResource(request);
|
||||||
|
}
|
||||||
|
return net.fetch(request, {
|
||||||
bypassCustomProtocolHandlers: true,
|
bypassCustomProtocolHandlers: true,
|
||||||
});
|
});
|
||||||
const urlObject = new URL(request.url);
|
}
|
||||||
if (isNetworkResource(urlObject.pathname)) {
|
|
||||||
// just pass through (proxy)
|
// mainly for loading sourcemap
|
||||||
return net.fetch(
|
// seems handle for http/https does not work for sourcemaps
|
||||||
CLOUD_BASE_URL + urlObject.pathname + urlObject.search,
|
async function handleAssetRequest(request: Request) {
|
||||||
clonedRequest
|
return fetchLocalResource(request);
|
||||||
);
|
|
||||||
} 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('.')) {
|
|
||||||
filepath = join(webStaticDir, decodeURIComponent(urlObject.pathname));
|
|
||||||
} else {
|
|
||||||
// else, fallback to load the index.html instead
|
|
||||||
filepath = join(webStaticDir, 'index.html');
|
|
||||||
}
|
|
||||||
return net.fetch('file://' + filepath, clonedRequest);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerProtocol() {
|
export function registerProtocol() {
|
||||||
protocol.handle('file', request => {
|
const isSecure = CLOUD_BASE_URL.startsWith('https://');
|
||||||
return handleFileRequest(request);
|
|
||||||
});
|
|
||||||
|
|
||||||
protocol.handle('assets', request => {
|
// do not proxy request when DEV_SERVER_URL is set (for local dev)
|
||||||
return handleFileRequest(request);
|
if (!DEV_SERVER_URL) {
|
||||||
});
|
protocol.handle(isSecure ? 'https' : 'http', request => {
|
||||||
|
return handleHttpRequest(request);
|
||||||
|
});
|
||||||
|
|
||||||
|
protocol.handle('assets', request => {
|
||||||
|
return handleAssetRequest(request);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// hack for CORS
|
// hack for CORS
|
||||||
// todo: should use a whitelist
|
// todo: should use a whitelist
|
||||||
@@ -110,21 +99,15 @@ export function registerProtocol() {
|
|||||||
const protocol = url.protocol;
|
const protocol = url.protocol;
|
||||||
const origin = url.origin;
|
const origin = url.origin;
|
||||||
|
|
||||||
const sameSite =
|
|
||||||
url.host === new URL(CLOUD_BASE_URL).host || protocol === 'file:';
|
|
||||||
|
|
||||||
// offline whitelist
|
// offline whitelist
|
||||||
// 1. do not block non-api request for http://localhost || file:// (local dev assets)
|
// 1. do not block non-api request for DEV_SERVER_URL
|
||||||
// 2. do not block devtools
|
// 2. do not block devtools
|
||||||
// 3. block all other requests
|
// 3. block all other requests
|
||||||
const blocked = (() => {
|
const blocked = (() => {
|
||||||
if (!isOfflineModeEnabled()) {
|
if (!isOfflineModeEnabled()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (
|
if (origin === DEV_SERVER_URL && !isNetworkResource(pathname)) {
|
||||||
(protocol === 'file:' || origin.startsWith('http://localhost')) &&
|
|
||||||
!isNetworkResource(pathname)
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ('devtools:' === protocol) {
|
if ('devtools:' === protocol) {
|
||||||
@@ -141,19 +124,6 @@ export function registerProtocol() {
|
|||||||
return;
|
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({
|
callback({
|
||||||
cancel: false,
|
cancel: false,
|
||||||
requestHeaders: details.requestHeaders,
|
requestHeaders: details.requestHeaders,
|
||||||
|
|||||||
@@ -3,10 +3,7 @@ import { app, shell } from 'electron';
|
|||||||
app.on('web-contents-created', (_, contents) => {
|
app.on('web-contents-created', (_, contents) => {
|
||||||
const isInternalUrl = (url: string) => {
|
const isInternalUrl = (url: string) => {
|
||||||
return (
|
return (
|
||||||
(process.env.DEV_SERVER_URL &&
|
process.env.DEV_SERVER_URL && url.startsWith(process.env.DEV_SERVER_URL)
|
||||||
url.startsWith(process.env.DEV_SERVER_URL)) ||
|
|
||||||
url.startsWith('affine://') ||
|
|
||||||
url.startsWith('file://.')
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { join } from 'node:path';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
app,
|
app,
|
||||||
type CookiesSetDetails,
|
|
||||||
session,
|
session,
|
||||||
type View,
|
type View,
|
||||||
type WebContents,
|
type WebContents,
|
||||||
@@ -28,7 +27,6 @@ import { mainWindowOrigin, shellViewUrl } from '../constants';
|
|||||||
import { ensureHelperProcess } from '../helper-process';
|
import { ensureHelperProcess } from '../helper-process';
|
||||||
import { logger } from '../logger';
|
import { logger } from '../logger';
|
||||||
import { globalStateStorage } from '../shared-storage/storage';
|
import { globalStateStorage } from '../shared-storage/storage';
|
||||||
import { parseCookie } from '../utils';
|
|
||||||
import { getCustomThemeWindow } from './custom-theme-window';
|
import { getCustomThemeWindow } from './custom-theme-window';
|
||||||
import { getMainWindow, MainWindowManager } from './main-window';
|
import { getMainWindow, MainWindowManager } from './main-window';
|
||||||
import {
|
import {
|
||||||
@@ -738,17 +736,6 @@ export class WebContentViewsManager {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
setCookie = async (cookiesSetDetails: CookiesSetDetails) => {
|
|
||||||
const views = this.allViews;
|
|
||||||
if (!views) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logger.info('setting cookie to main window view(s)', cookiesSetDetails);
|
|
||||||
for (const view of views) {
|
|
||||||
await view.webContents.session.cookies.set(cookiesSetDetails);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
getViewById = (id: string) => {
|
getViewById = (id: string) => {
|
||||||
if (id === 'shell') {
|
if (id === 'shell') {
|
||||||
return this.shellView;
|
return this.shellView;
|
||||||
@@ -855,30 +842,6 @@ export class WebContentViewsManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setCookie(cookie: CookiesSetDetails): Promise<void>;
|
|
||||||
export async function setCookie(origin: string, cookie: string): Promise<void>;
|
|
||||||
|
|
||||||
export async function setCookie(
|
|
||||||
arg0: CookiesSetDetails | string,
|
|
||||||
arg1?: string
|
|
||||||
) {
|
|
||||||
const details =
|
|
||||||
typeof arg1 === 'string' && typeof arg0 === 'string'
|
|
||||||
? parseCookie(arg0, arg1)
|
|
||||||
: arg0;
|
|
||||||
|
|
||||||
logger.info('setting cookie to main window', details);
|
|
||||||
|
|
||||||
if (typeof details !== 'object') {
|
|
||||||
throw new Error('invalid cookie details');
|
|
||||||
}
|
|
||||||
return WebContentViewsManager.instance.setCookie(details);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCookies() {
|
|
||||||
return WebContentViewsManager.instance.cookies;
|
|
||||||
}
|
|
||||||
|
|
||||||
// there is no proper way to listen to webContents resize event
|
// there is no proper way to listen to webContents resize event
|
||||||
// we will rely on window.resize event in renderer instead
|
// we will rely on window.resize event in renderer instead
|
||||||
export async function handleWebContentsResize(webContents?: WebContents) {
|
export async function handleWebContentsResize(webContents?: WebContents) {
|
||||||
|
|||||||
Reference in New Issue
Block a user