From 69c507fdedf28b420e55443cb77f7d3e14b7aedc Mon Sep 17 00:00:00 2001 From: pengx17 Date: Fri, 16 Aug 2024 05:17:47 +0000 Subject: [PATCH] fix(electron): do not use async callback in onBeforeSendHeaders (#7894) it seems using async callback for onBeforeSendHeaders may crash the app. the main reason why it is async is to read the cookies from session. fix AF-1172 --- .../frontend/electron/src/main/deep-link.ts | 17 +----- .../frontend/electron/src/main/protocol.ts | 45 ++++++-------- .../src/main/windows-manager/tab-views.ts | 60 ++++++++----------- 3 files changed, 45 insertions(+), 77 deletions(-) diff --git a/packages/frontend/electron/src/main/deep-link.ts b/packages/frontend/electron/src/main/deep-link.ts index 13b723258..f4d7a8f63 100644 --- a/packages/frontend/electron/src/main/deep-link.ts +++ b/packages/frontend/electron/src/main/deep-link.ts @@ -1,7 +1,6 @@ import path from 'node:path'; -import type { App, BrowserWindow } from 'electron'; -import { ipcMain } from 'electron'; +import type { App } from 'electron'; import { buildType, CLOUD_BASE_URL, isDev } from './config'; import { mainWindowOrigin } from './constants'; @@ -103,20 +102,8 @@ async function handleOauthJwt(url: string) { ), }); - let hiddenWindow: BrowserWindow | null = null; - - ipcMain.once('affine:login', () => { - hiddenWindow?.destroy(); - if (urlObj.searchParams.get('next') === 'onboarding') { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - mainWindow.loadURL(mainWindowOrigin + '/auth/onboarding'); - } - }); - // hacks to refresh auth state in the main window - hiddenWindow = await handleOpenUrlInHiddenWindow( - mainWindowOrigin + '/auth/signIn' - ); + await handleOpenUrlInHiddenWindow(mainWindowOrigin + '/auth/signIn'); } catch (e) { logger.error('failed to open url in popup', e); } diff --git a/packages/frontend/electron/src/main/protocol.ts b/packages/frontend/electron/src/main/protocol.ts index 37dd67ae5..248fedb07 100644 --- a/packages/frontend/electron/src/main/protocol.ts +++ b/packages/frontend/electron/src/main/protocol.ts @@ -3,8 +3,7 @@ import { join } from 'node:path'; import { net, protocol, session } from 'electron'; import { CLOUD_BASE_URL } from './config'; -import { logger } from './logger'; -import { getCookie } from './windows-manager'; +import { getCookies } from './windows-manager'; protocol.registerSchemesAsPrivileged([ { @@ -104,33 +103,23 @@ export function registerProtocol() { ); session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => { - (async () => { - const url = new URL(details.url); - const pathname = url.pathname; - // if sending request to the cloud, attach the session cookie - if (isNetworkResource(pathname)) { - const cookie = await getCookie(CLOUD_BASE_URL); - 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; + const url = new URL(details.url); + const pathname = url.pathname; + // if sending request to the cloud, attach the session cookie + if (isNetworkResource(pathname)) { + const cookie = getCookies(); + if (cookie) { + const cookieString = cookie.map(c => `${c.name}=${c.value}`).join('; '); + details.requestHeaders['cookie'] = cookieString; } - callback({ - cancel: false, - requestHeaders: details.requestHeaders, - }); - })().catch(e => { - logger.error('failed to attach cookie', e); - callback({ - cancel: false, - requestHeaders: details.requestHeaders, - }); + + // add the referer and origin headers + details.requestHeaders['referer'] ??= CLOUD_BASE_URL; + details.requestHeaders['origin'] ??= CLOUD_BASE_URL; + } + callback({ + cancel: false, + requestHeaders: details.requestHeaders, }); }); } diff --git a/packages/frontend/electron/src/main/windows-manager/tab-views.ts b/packages/frontend/electron/src/main/windows-manager/tab-views.ts index b7a6f6fa3..a5e23a33a 100644 --- a/packages/frontend/electron/src/main/windows-manager/tab-views.ts +++ b/packages/frontend/electron/src/main/windows-manager/tab-views.ts @@ -3,6 +3,7 @@ import { join } from 'node:path'; import { app, type CookiesSetDetails, + session, type View, type WebContents, WebContentsView, @@ -22,7 +23,7 @@ import { } from 'rxjs'; import { isMacOS } from '../../shared/utils'; -import { isDev } from '../config'; +import { CLOUD_BASE_URL, isDev } from '../config'; import { mainWindowOrigin, shellViewUrl } from '../constants'; import { ensureHelperProcess } from '../helper-process'; import { logger } from '../logger'; @@ -185,6 +186,8 @@ export class WebContentViewsManager { */ readonly tabAction$ = new Subject(); + cookies: Electron.Cookie[] = []; + readonly activeWorkbenchId$ = this.tabViewsMeta$.pipe( map(m => m?.activeWorkbenchId ?? m?.workbenches[0].id) ); @@ -672,7 +675,6 @@ export class WebContentViewsManager { disposables.push( windowReadyToShow$.subscribe(w => { handleWebContentsResize().catch(logger.error); - const screenSizeChangeEvents = ['resize', 'maximize', 'unmaximize']; const onResize = () => { if (this.activeWorkbenchView) { @@ -689,6 +691,23 @@ export class WebContentViewsManager { // add shell view this.createAndAddView('shell').catch(logger.error); (async () => { + const updateCookies = () => { + session.defaultSession.cookies + .get({ + url: CLOUD_BASE_URL, + }) + .then(cookies => { + this.cookies = cookies; + }) + .catch(err => { + logger.error('failed to get cookies', err); + }); + }; + updateCookies(); + session.defaultSession.cookies.on('changed', () => { + updateCookies(); + }); + if (this.tabViewsMeta.workbenches.length === 0) { // create a default view (e.g., on first launch) await this.addTab(); @@ -705,40 +724,17 @@ export class WebContentViewsManager { }); }; - setCookie = async (cookie: CookiesSetDetails) => { + setCookie = async (cookiesSetDetails: CookiesSetDetails) => { const views = this.allViews; if (!views) { return; } - logger.info('setting cookie to main window view(s)', cookie); + logger.info('setting cookie to main window view(s)', cookiesSetDetails); for (const view of views) { - await view.webContents.session.cookies.set(cookie); + await view.webContents.session.cookies.set(cookiesSetDetails); } }; - removeCookie = async (url: string, name: string) => { - const views = this.allViews; - if (!views) { - return; - } - logger.info('removing cookie from main window view(s)', { url, name }); - for (const view of views) { - await view.webContents.session.cookies.remove(url, name); - } - }; - - getCookie = (url?: string, name?: string) => { - // all webviews share the same session - const view = this.allViews?.at(0); - if (!view) { - return; - } - return view.webContents.session.cookies.get({ - url, - name, - }); - }; - getViewById = (id: string) => { if (id === 'shell') { return this.shellView; @@ -865,12 +861,8 @@ export async function setCookie( return WebContentViewsManager.instance.setCookie(details); } -export async function removeCookie(url: string, name: string): Promise { - return WebContentViewsManager.instance.removeCookie(url, name); -} - -export async function getCookie(url?: string, name?: string) { - return WebContentViewsManager.instance.getCookie(url, name); +export function getCookies() { + return WebContentViewsManager.instance.cookies; } // there is no proper way to listen to webContents resize event