Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix race condition when running preload script for inline browser #619

Merged
merged 5 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions ts/packages/agents/browser/src/electron/agentActivation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
}
});
11 changes: 5 additions & 6 deletions ts/packages/agents/browser/src/extension/contentScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
});
Expand Down Expand Up @@ -730,9 +728,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(
Expand Down
39 changes: 15 additions & 24 deletions ts/packages/agents/browser/src/extension/webTypeAgentMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<DynamicTypeAgentManagerInvokeFunctions>(
Expand Down Expand Up @@ -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;
}
Expand All @@ -140,5 +123,13 @@ global.registerTypeAgent = async (
agent: AppAgent,
): Promise<void> => {
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,
});
});
};
26 changes: 26 additions & 0 deletions ts/packages/shell/electron.vite-cjs.config.mts
Original file line number Diff line number Diff line change
@@ -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
},
},
},
},
});
1 change: 0 additions & 1 deletion ts/packages/shell/electron.vite.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export default defineConfig({
rollupOptions: {
input: {
index: resolve(__dirname, "src/preload/index.ts"),
webview: resolve(__dirname, "src/preload/webView.ts"),
},
},
},
Expand Down
9 changes: 5 additions & 4 deletions ts/packages/shell/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 --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",
"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"
Expand Down
29 changes: 14 additions & 15 deletions ts/packages/shell/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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,
},
});
Expand All @@ -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 => {
Expand Down Expand Up @@ -570,7 +576,7 @@ async function initialize() {

await initializeSpeech();

const { mainWindow, chatView } = createWindow();
const { mainWindow, chatView } = await createWindow();

let settingSummary: string = "";
function updateSummary(dispatcher: Dispatcher) {
Expand Down Expand Up @@ -646,19 +652,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
Expand Down
32 changes: 9 additions & 23 deletions ts/packages/shell/src/preload/webView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,17 @@ 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) {
console.error(data.error);
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}`);
Expand Down Expand Up @@ -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(
Expand All @@ -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);
}
Expand Down Expand Up @@ -436,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) => {
Expand All @@ -457,15 +450,8 @@ 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");
};