feat: bump electron (#14151)

This commit is contained in:
DarkSky
2025-12-26 09:41:16 +08:00
committed by GitHub
parent 2e38898937
commit ca386283c5
15 changed files with 203 additions and 87 deletions
@@ -1,8 +1,8 @@
export const mainHost = '.';
export const anotherHost = 'another-host';
export const mainWindowOrigin = `file://${mainHost}`;
export const anotherOrigin = `file://${anotherHost}`;
export const mainWindowOrigin = `assets://${mainHost}`;
export const anotherOrigin = `assets://${anotherHost}`;
export const onboardingViewUrl = `${mainWindowOrigin}/onboarding`;
export const shellViewUrl = `${mainWindowOrigin}/shell.html`;
@@ -12,16 +12,15 @@ protocol.registerSchemesAsPrivileged([
{
scheme: 'assets',
privileges: {
secure: false,
secure: true,
allowServiceWorkers: true,
corsEnabled: true,
supportFetchAPI: true,
standard: true,
bypassCSP: true,
stream: true,
},
},
]);
protocol.registerSchemesAsPrivileged([
{
scheme: 'file',
privileges: {
@@ -36,6 +35,17 @@ protocol.registerSchemesAsPrivileged([
]);
const webStaticDir = join(resourcesPath, 'web-static');
const localWhiteListDirs = [
path.resolve(app.getPath('sessionData')).toLowerCase(),
path.resolve(app.getPath('temp')).toLowerCase(),
];
function isPathInWhiteList(filepath: string) {
const lowerFilePath = filepath.toLowerCase();
return localWhiteListDirs.some(whitelistDir =>
lowerFilePath.startsWith(whitelistDir)
);
}
async function handleFileRequest(request: Request) {
const urlObject = new URL(request.url);
@@ -45,11 +55,14 @@ async function handleFileRequest(request: Request) {
}
const isAbsolutePath = urlObject.host !== '.';
const isFontRequest =
urlObject.pathname &&
/\.(woff2?|ttf|otf)$/i.test(urlObject.pathname.split('?')[0] ?? '');
// Redirect to webpack dev server if defined
if (process.env.DEV_SERVER_URL && !isAbsolutePath) {
if (process.env.DEV_SERVER_URL && !isAbsolutePath && !isFontRequest) {
const devServerUrl = new URL(
urlObject.pathname,
`${urlObject.pathname}${urlObject.search}`,
process.env.DEV_SERVER_URL
);
return net.fetch(devServerUrl.toString(), request);
@@ -83,14 +96,7 @@ async function handleFileRequest(request: Request) {
filepath = path.resolve(filepath.replace(/^\//, ''));
}
// security check if the filepath is within app.getPath('sessionData')
const sessionDataPath = path
.resolve(app.getPath('sessionData'))
.toLowerCase();
const tempPath = path.resolve(app.getPath('temp')).toLowerCase();
if (
!filepath.toLowerCase().startsWith(sessionDataPath) &&
!filepath.toLowerCase().startsWith(tempPath)
) {
if (urlObject.host !== 'local-file' || !isPathInWhiteList(filepath)) {
throw new Error('Invalid filepath');
}
}
@@ -102,7 +108,20 @@ async function handleFileRequest(request: Request) {
const corsWhitelist = [
/^(?:[a-zA-Z0-9-]+\.)*googlevideo\.com$/,
/^(?:[a-zA-Z0-9-]+\.)*youtube\.com$/,
/^(?:[a-zA-Z0-9-]+\.)*youtube-nocookie\.com$/,
/^(?:[a-zA-Z0-9-]+\.)*gstatic\.com$/,
/^(?:[a-zA-Z0-9-]+\.)*googleapis\.com$/,
/^localhost(?::\d+)?$/,
/^127\.0\.0\.1(?::\d+)?$/,
/^insider\.affine\.pro$/,
/^app\.affine\.pro$/,
];
const needRefererDomains = [
/^(?:[a-zA-Z0-9-]+\.)*youtube\.com$/,
/^(?:[a-zA-Z0-9-]+\.)*youtube-nocookie\.com$/,
/^(?:[a-zA-Z0-9-]+\.)*googlevideo\.com$/,
];
const defaultReferer = 'https://client.affine.local/';
export function registerProtocol() {
protocol.handle('file', request => {
@@ -159,6 +178,31 @@ export function registerProtocol() {
delete responseHeaders['access-control-allow-headers'];
delete responseHeaders['Access-Control-Allow-Origin'];
delete responseHeaders['Access-Control-Allow-Headers'];
} else if (
!needRefererDomains.some(domainRegex => domainRegex.test(hostname))
) {
if (
!responseHeaders['access-control-allow-origin'] &&
!responseHeaders['Access-Control-Allow-Origin']
) {
responseHeaders['Access-Control-Allow-Origin'] = ['*'];
}
if (
!responseHeaders['access-control-allow-headers'] &&
!responseHeaders['Access-Control-Allow-Headers']
) {
responseHeaders['Access-Control-Allow-Headers'] = [
'Origin, X-Requested-With, Content-Type, Accept, Authorization',
];
}
if (
!responseHeaders['access-control-allow-methods'] &&
!responseHeaders['Access-Control-Allow-Methods']
) {
responseHeaders['Access-Control-Allow-Methods'] = [
'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
];
}
}
// to allow url embedding, remove "x-frame-options",
@@ -217,7 +261,7 @@ export function registerProtocol() {
const url = new URL(details.url);
(async () => {
// session cookies are set to file:// on production
// session cookies are set to assets:// on production
// if sending request to the cloud, attach the session cookie (to affine cloud server)
if (
url.protocol === 'http:' ||
@@ -235,6 +279,14 @@ export function registerProtocol() {
delete details.requestHeaders['cookie'];
details.requestHeaders['Cookie'] = cookieString;
}
const hostname = url.hostname;
const needReferer = needRefererDomains.some(regex =>
regex.test(hostname)
);
if (needReferer && !details.requestHeaders['Referer']) {
details.requestHeaders['Referer'] = defaultReferer;
}
})()
.catch(err => {
logger.error('error handling before send headers', err);
@@ -686,6 +686,22 @@ export async function getRawAudioBuffers(
};
}
function assertRecordingFilepath(filepath: string) {
const normalizedPath = path.normalize(filepath);
const normalizedBase = path.normalize(SAVED_RECORDINGS_DIR + path.sep);
if (!normalizedPath.toLowerCase().startsWith(normalizedBase.toLowerCase())) {
throw new Error('Invalid recording filepath');
}
return normalizedPath;
}
export async function readRecordingFile(filepath: string) {
const normalizedPath = assertRecordingFilepath(filepath);
return fsp.readFile(normalizedPath);
}
export async function readyRecording(id: number, buffer: Buffer) {
logger.info('readyRecording', id);
@@ -19,6 +19,7 @@ import {
handleBlockCreationFailed,
handleBlockCreationSuccess,
pauseRecording,
readRecordingFile,
readyRecording,
recordingStatus$,
removeRecording,
@@ -53,6 +54,9 @@ export const recordingHandlers = {
getRawAudioBuffers: async (_, id: number, cursor?: number) => {
return getRawAudioBuffers(id, cursor);
},
readRecordingFile: async (_, filepath: string) => {
return readRecordingFile(filepath);
},
// save the encoded recording buffer to the file system
readyRecording: async (_, id: number, buffer: Uint8Array) => {
return readyRecording(id, Buffer.from(buffer));
@@ -1,5 +1,6 @@
import { app } from 'electron';
import { anotherHost, mainHost } from './constants';
import { openExternalSafely } from './security/open-external';
const extractRedirectTarget = (rawUrl: string) => {
@@ -33,7 +34,20 @@ const extractRedirectTarget = (rawUrl: string) => {
app.on('web-contents-created', (_, contents) => {
const isInternalUrl = (url: string) => {
return url.startsWith('file://.');
try {
const parsed = new URL(url);
if (
parsed.protocol === 'assets:' &&
(parsed.hostname === mainHost || parsed.hostname === anotherHost)
) {
return true;
}
if (parsed.protocol === 'file:' && parsed.hostname === mainHost) {
// legacy allowance for older file:// loads
return true;
}
} catch {}
return false;
};
/**
* Block navigation to origins not on the allowlist.