From 6525d1872d3e7b79b1ffd6dd724ae0b9e2f3bc2d Mon Sep 17 00:00:00 2001 From: Hillary Mutisya <150286414+hillary-mutisya@users.noreply.github.com> Date: Tue, 28 Jan 2025 23:34:57 -0800 Subject: [PATCH 1/4] Change inline browser preload script to CommonJS This gets around a race condition when loading ESM preload scripts: https://github.com/electron/electron/issues/40777 --- .../browser/src/electron/agentActivation.ts | 22 ++++++----- .../browser/src/extension/contentScript.ts | 7 ++-- .../browser/src/extension/webTypeAgentMain.ts | 39 +++++++------------ .../shell/electron.vite-cjs.config.mts | 26 +++++++++++++ ts/packages/shell/electron.vite.config.mts | 1 - ts/packages/shell/package.json | 9 +++-- ts/packages/shell/src/main/index.ts | 30 +++++++------- ts/packages/shell/src/preload/webView.ts | 35 +++++++---------- 8 files changed, 91 insertions(+), 78 deletions(-) create mode 100644 ts/packages/shell/electron.vite-cjs.config.mts diff --git a/ts/packages/agents/browser/src/electron/agentActivation.ts b/ts/packages/agents/browser/src/electron/agentActivation.ts index 66557f6ab..405bf9070 100644 --- a/ts/packages/agents/browser/src/electron/agentActivation.ts +++ b/ts/packages/agents/browser/src/electron/agentActivation.ts @@ -10,8 +10,13 @@ declare global { } let siteAgent: string = ""; +let siteAgentInitialized = false; function setupSiteAgent() { if (window.browserConnect) { + if (siteAgentInitialized) { + return; + } + const pageUrl = window.location.href; const host = new URL(pageUrl).host; @@ -57,20 +62,19 @@ function setupSiteAgent() { siteAgent = "browser.instacart"; window.browserConnect.enableSiteAgent("browser.instacart"); } + + siteAgentInitialized = true; } else { console.log("browserconnect not found by UI events script"); } } -window.addEventListener("message", (event) => { - if (event.data === "setupSiteAgent") { - setupSiteAgent(); - } - if ( - event.data === "disableSiteAgent" && - siteAgent && - window.browserConnect - ) { +document.addEventListener("DOMContentLoaded", () => { + setupSiteAgent(); +}); + +window.addEventListener("beforeunload", (event) => { + if (siteAgent !== undefined && window.browserConnect !== undefined) { window.browserConnect.disableSiteAgent(siteAgent); } }); diff --git a/ts/packages/agents/browser/src/extension/contentScript.ts b/ts/packages/agents/browser/src/extension/contentScript.ts index 7117d63ca..4dfc1e7b1 100644 --- a/ts/packages/agents/browser/src/extension/contentScript.ts +++ b/ts/packages/agents/browser/src/extension/contentScript.ts @@ -730,9 +730,10 @@ window.addEventListener( "message", async (event) => { if ( - event.data.source == "preload" && - event.data.target == "contentScript" && - event.data.messageType == "scriptActionRequest" + event.data !== undefined && + event.data.source === "preload" && + event.data.target === "contentScript" && + event.data.messageType === "scriptActionRequest" ) { await handleScriptAction(event.data.body, (response) => { window.top?.postMessage( diff --git a/ts/packages/agents/browser/src/extension/webTypeAgentMain.ts b/ts/packages/agents/browser/src/extension/webTypeAgentMain.ts index f11dfda81..83c428902 100644 --- a/ts/packages/agents/browser/src/extension/webTypeAgentMain.ts +++ b/ts/packages/agents/browser/src/extension/webTypeAgentMain.ts @@ -10,6 +10,7 @@ import { createRpc } from "agent-rpc/rpc"; import { createAgentRpcServer } from "agent-rpc/server"; import { isWebAgentMessageFromDispatcher } from "../../dist/common/webAgentMessageTypes.mjs"; import { + WebAgentDisconnectMessageFromDispatcher, WebAgentRegisterMessage, WebAgentRpcMessage, } from "../common/webAgentMessageTypes.mjs"; @@ -51,32 +52,20 @@ function ensureDynamicTypeAgentManager(): DynamicTypeAgentManager { } const messageChannelProvider = createGenericChannelProvider( (message: any) => { - const rpcMessage = { + window.postMessage({ source: "webAgent", method: "webAgent/message", params: message, - } as WebAgentRpcMessage; - - if (window.webAgentApi) { - window.webAgentApi.sendWebAgentMessage(rpcMessage); - } else { - window.postMessage(rpcMessage); - } + } as WebAgentRpcMessage); }, ); const registerChannel = createGenericChannel((message: any) => { - const rpcMessage = { + window.postMessage({ source: "webAgent", method: "webAgent/register", params: message, - } as WebAgentRegisterMessage; - - if (window.webAgentApi) { - window.webAgentApi.sendWebAgentMessage(rpcMessage); - } else { - window.postMessage(rpcMessage); - } + } as WebAgentRegisterMessage); }); const rpc = createRpc( @@ -123,13 +112,7 @@ function ensureDynamicTypeAgentManager(): DynamicTypeAgentManager { } }; - if (window.webAgentApi) { - window.webAgentApi.onWebAgentMessage((event) => { - messageHandler(event); - }); - } else { - window.addEventListener("message", messageHandler); - } + window.addEventListener("message", messageHandler); return manager; } @@ -140,5 +123,13 @@ global.registerTypeAgent = async ( agent: AppAgent, ): Promise => { const manager = ensureDynamicTypeAgentManager(); - return manager.addTypeAgent(name, manifest, agent); + await manager.addTypeAgent(name, manifest, agent); + + window.addEventListener("beforeunload", (event) => { + window.postMessage({ + source: "webAgent", + method: "webAgent/disconnect", + params: name, + }); + }); }; diff --git a/ts/packages/shell/electron.vite-cjs.config.mts b/ts/packages/shell/electron.vite-cjs.config.mts new file mode 100644 index 000000000..7fcd25f95 --- /dev/null +++ b/ts/packages/shell/electron.vite-cjs.config.mts @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { defineConfig, externalizeDepsPlugin } from "electron-vite"; +import { resolve } from "path"; + +export default defineConfig({ + preload: { + plugins: [externalizeDepsPlugin()], + build: { + sourcemap: true, + rollupOptions: { + input: { + webview: resolve(__dirname, "src/preload/webView.ts"), + }, + output: { + // For the CJS preload + format: "cjs", + entryFileNames: "[name].cjs", + dir: "out/preload-cjs", + preserveModules: true, // Ensure each module is preserved in the output + }, + }, + }, + }, +}); diff --git a/ts/packages/shell/electron.vite.config.mts b/ts/packages/shell/electron.vite.config.mts index 868363f46..f2d34e69f 100644 --- a/ts/packages/shell/electron.vite.config.mts +++ b/ts/packages/shell/electron.vite.config.mts @@ -18,7 +18,6 @@ export default defineConfig({ rollupOptions: { input: { index: resolve(__dirname, "src/preload/index.ts"), - webview: resolve(__dirname, "src/preload/webView.ts"), }, }, }, diff --git a/ts/packages/shell/package.json b/ts/packages/shell/package.json index f58abb2c4..3ff395c53 100644 --- a/ts/packages/shell/package.json +++ b/ts/packages/shell/package.json @@ -13,18 +13,19 @@ "type": "module", "main": "./out/main/index.js", "scripts": { - "build": "concurrently npm:typecheck npm:build:electron", - "build:electron": "electron-vite build", + "build": "concurrently npm:typecheck npm:build:electron npm:build:electron:cjs", + "build:electron": "electron-vite build --config electron.vite.config.mts", + "build:electron:cjs": "electron-vite build --config electron.vite-cjs.config.mts", "build:linux": "npm run build && electron-builder --linux --config", "build:mac": "npm run build && electron-builder --mac --config", "build:win": "npm run build && electron-builder --win --config", "clean": "rimraf --glob out *.tsbuildinfo *.done.build.log", - "dev": "electron-vite dev", + "dev": "npm run build:electron:cjs && electron-vite dev", "postinstall": "cross-env \"npm_execpath=\" electron-builder install-app-deps", "prettier": "prettier --check . --ignore-path ../../.prettierignore", "prettier:fix": "prettier --write . --ignore-path ../../.prettierignore", "shell:test": "npx playwright test", - "start": "electron-vite preview", + "start": "npm run build:electron:cjs && electron-vite preview", "typecheck": "concurrently npm:typecheck:node npm:typecheck:web", "typecheck:node": "tsc -p tsconfig.node.json", "typecheck:web": "tsc -p tsconfig.web.json" diff --git a/ts/packages/shell/src/main/index.ts b/ts/packages/shell/src/main/index.ts index db3ffbcff..e8017c6b7 100644 --- a/ts/packages/shell/src/main/index.ts +++ b/ts/packages/shell/src/main/index.ts @@ -12,6 +12,7 @@ import { DevicePermissionHandlerHandlerDetails, WebContents, BrowserView, + session, } from "electron"; import path, { join } from "node:path"; import { electronApp, optimizer, is } from "@electron-toolkit/utils"; @@ -103,7 +104,7 @@ function setContentSize() { const time = performance.now(); debugShell("Starting..."); -function createWindow() { +async function createWindow() { debugShell("Creating window", performance.now() - time); // Create the browser window. @@ -131,6 +132,14 @@ function createWindow() { mainWindow.webContents.setUserAgent(userAgent); + const browserExtensionPath = join( + app.getAppPath(), + "../agents/browser/dist/electron", + ); + await session.defaultSession.loadExtension(browserExtensionPath, { + allowFileAccess: true, + }); + chatView = new BrowserView({ webPreferences: { preload: join(__dirname, "../preload/index.mjs"), @@ -280,7 +289,7 @@ function createWindow() { if (!inlineBrowserView && mainWindowSize) { inlineBrowserView = new BrowserView({ webPreferences: { - preload: join(__dirname, "../preload/webview.mjs"), + preload: join(__dirname, "../preload-cjs/webview.cjs"), sandbox: false, }, }); @@ -299,9 +308,6 @@ function createWindow() { } inlineBrowserView?.webContents.loadURL(targetUrl.toString()); - inlineBrowserView?.webContents.on("did-finish-load", () => { - inlineBrowserView?.webContents.send("init-site-agent"); - }); }; ShellSettings.getinstance().onCloseInlineBrowser = (): void => { @@ -567,10 +573,11 @@ async function initialize() { debugShell("Ready", performance.now() - time); // Set app user model id for windows electronApp.setAppUserModelId("com.electron"); + app.commandLine.appendSwitch("disable-http-cache"); await initializeSpeech(); - const { mainWindow, chatView } = createWindow(); + const { mainWindow, chatView } = await createWindow(); let settingSummary: string = ""; function updateSummary(dispatcher: Dispatcher) { @@ -646,19 +653,12 @@ async function initialize() { // see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils app.on("browser-window-created", async (_, window) => { optimizer.watchWindowShortcuts(window); - const browserExtensionPath = join( - app.getAppPath(), - "../agents/browser/dist/electron", - ); - await window.webContents.session.loadExtension(browserExtensionPath, { - allowFileAccess: true, - }); }); - app.on("activate", function () { + app.on("activate", async function () { // On macOS it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. - if (BrowserWindow.getAllWindows().length === 0) createWindow(); + if (BrowserWindow.getAllWindows().length === 0) await createWindow(); }); // On windows, we will spin up a local end point that listens diff --git a/ts/packages/shell/src/preload/webView.ts b/ts/packages/shell/src/preload/webView.ts index 49cdb1ecd..5a327f6ec 100644 --- a/ts/packages/shell/src/preload/webView.ts +++ b/ts/packages/shell/src/preload/webView.ts @@ -6,9 +6,6 @@ const { webFrame } = require("electron"); import DOMPurify from "dompurify"; import { ipcRenderer } from "electron"; -import EventEmitter from "events"; - -const internalEventEmitter = new EventEmitter(); ipcRenderer.on("received-from-browser-ipc", async (_, data) => { if (data.error) { @@ -16,10 +13,10 @@ ipcRenderer.on("received-from-browser-ipc", async (_, data) => { return; } - if (data.method && data.method.indexOf("/") > 0) { + if (data.method !== undefined && data.method.indexOf("/") > 0) { const [schema, actionName] = data.method?.split("/"); - if (schema == "browser") { + if (schema === "browser") { if (actionName == "siteTranslatorStatus") { if (data.body.status == "initializing") { console.log(`Initializing ${data.body.translator}`); @@ -49,8 +46,8 @@ ipcRenderer.on("received-from-browser-ipc", async (_, data) => { id: data.id, result: message, }); - } else if (schema == "webAgent") { - internalEventEmitter.emit("web-agent-message", data); + } else if (schema === "webAgent") { + window.postMessage(data); } console.log( @@ -59,10 +56,6 @@ ipcRenderer.on("received-from-browser-ipc", async (_, data) => { } }); -ipcRenderer.on("init-site-agent", () => { - window.postMessage("setupSiteAgent"); -}); - function sendToBrowserAgent(message: any) { ipcRenderer.send("send-to-browser-ipc", message); } @@ -436,7 +429,10 @@ async function runSiteAction(schemaName: string, action: any) { return confirmationMessage; } -await ipcRenderer.invoke("init-browser-ipc"); +console.log("Preload: setting up browser IPC"); + +// await ipcRenderer.invoke("init-browser-ipc"); +ipcRenderer.invoke("init-browser-ipc"); contextBridge.exposeInMainWorld("browserConnect", { enableSiteAgent: (translatorName) => { @@ -457,15 +453,10 @@ contextBridge.exposeInMainWorld("browserConnect", { }, }); -contextBridge.exposeInMainWorld("webAgentApi", { - onWebAgentMessage: (callback: (message: any) => void) => { - internalEventEmitter.on("web-agent-message", callback); - }, - sendWebAgentMessage: (message: any) => { - sendToBrowserAgent(message); - }, +window.addEventListener("message", (event) => { + if (event.data !== undefined && event.data.source === "webAgent") { + sendToBrowserAgent(event.data); + } }); -window.onbeforeunload = () => { - window.postMessage("disableSiteAgent"); -}; +console.log("Preload: completed"); From 4338de14b4db5cc05d0464b8ef72d6e3a9e2c5f0 Mon Sep 17 00:00:00 2001 From: Hillary Mutisya <150286414+hillary-mutisya@users.noreply.github.com> Date: Wed, 29 Jan 2025 09:31:43 -0800 Subject: [PATCH 2/4] Remove obsolete logs --- ts/packages/agents/browser/src/extension/contentScript.ts | 4 +--- ts/packages/shell/src/preload/webView.ts | 7 +------ 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/ts/packages/agents/browser/src/extension/contentScript.ts b/ts/packages/agents/browser/src/extension/contentScript.ts index 4dfc1e7b1..351bd8c8e 100644 --- a/ts/packages/agents/browser/src/extension/contentScript.ts +++ b/ts/packages/agents/browser/src/extension/contentScript.ts @@ -530,11 +530,9 @@ async function awaitPageIncrementalUpdates() { detector .detect() .then(() => { - console.log("Page incremental load completed."); resolve("true"); }) - .catch((error: Error) => { - console.error("Failed to detect page load completion:", error); + .catch((_error: Error) => { resolve("false"); }); }); diff --git a/ts/packages/shell/src/preload/webView.ts b/ts/packages/shell/src/preload/webView.ts index 5a327f6ec..82980008c 100644 --- a/ts/packages/shell/src/preload/webView.ts +++ b/ts/packages/shell/src/preload/webView.ts @@ -429,10 +429,7 @@ async function runSiteAction(schemaName: string, action: any) { return confirmationMessage; } -console.log("Preload: setting up browser IPC"); - -// await ipcRenderer.invoke("init-browser-ipc"); -ipcRenderer.invoke("init-browser-ipc"); +await ipcRenderer.invoke("init-browser-ipc"); contextBridge.exposeInMainWorld("browserConnect", { enableSiteAgent: (translatorName) => { @@ -458,5 +455,3 @@ window.addEventListener("message", (event) => { sendToBrowserAgent(event.data); } }); - -console.log("Preload: completed"); From eeae789630044de17f9b700b7ceb1ef1d2a731a8 Mon Sep 17 00:00:00 2001 From: Hillary Mutisya <150286414+hillary-mutisya@users.noreply.github.com> Date: Wed, 29 Jan 2025 09:35:37 -0800 Subject: [PATCH 3/4] Remove experimental fix --- ts/packages/shell/src/main/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ts/packages/shell/src/main/index.ts b/ts/packages/shell/src/main/index.ts index e8017c6b7..4a2ceeec0 100644 --- a/ts/packages/shell/src/main/index.ts +++ b/ts/packages/shell/src/main/index.ts @@ -573,7 +573,6 @@ async function initialize() { debugShell("Ready", performance.now() - time); // Set app user model id for windows electronApp.setAppUserModelId("com.electron"); - app.commandLine.appendSwitch("disable-http-cache"); await initializeSpeech(); From bfffe269a01252308b2a97bb84a32385cda307ce Mon Sep 17 00:00:00 2001 From: Hillary Mutisya <150286414+hillary-mutisya@users.noreply.github.com> Date: Wed, 29 Jan 2025 10:16:06 -0800 Subject: [PATCH 4/4] Update build warnings --- ts/packages/shell/package.json | 2 +- ts/packages/shell/src/preload/webView.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ts/packages/shell/package.json b/ts/packages/shell/package.json index 3ff395c53..53c714e89 100644 --- a/ts/packages/shell/package.json +++ b/ts/packages/shell/package.json @@ -15,7 +15,7 @@ "scripts": { "build": "concurrently npm:typecheck npm:build:electron npm:build:electron:cjs", "build:electron": "electron-vite build --config electron.vite.config.mts", - "build:electron:cjs": "electron-vite build --config electron.vite-cjs.config.mts", + "build:electron:cjs": "electron-vite build --config electron.vite-cjs.config.mts --logLevel error", "build:linux": "npm run build && electron-builder --linux --config", "build:mac": "npm run build && electron-builder --mac --config", "build:win": "npm run build && electron-builder --win --config", diff --git a/ts/packages/shell/src/preload/webView.ts b/ts/packages/shell/src/preload/webView.ts index 82980008c..e1448a07b 100644 --- a/ts/packages/shell/src/preload/webView.ts +++ b/ts/packages/shell/src/preload/webView.ts @@ -429,7 +429,7 @@ async function runSiteAction(schemaName: string, action: any) { return confirmationMessage; } -await ipcRenderer.invoke("init-browser-ipc"); +ipcRenderer.invoke("init-browser-ipc"); contextBridge.exposeInMainWorld("browserConnect", { enableSiteAgent: (translatorName) => {