Skip to content

Commit eb99064

Browse files
authored
refactor(desktop-electron): enable contextIsolation and sandbox (#23523)
1 parent 4964ce4 commit eb99064

File tree

10 files changed

+55
-54
lines changed

10 files changed

+55
-54
lines changed

packages/desktop-electron/electron.vite.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ export default defineConfig({
5353
build: {
5454
rollupOptions: {
5555
input: { index: "src/preload/index.ts" },
56+
output: {
57+
format: "cjs",
58+
entryFileNames: "[name].js",
59+
},
5660
},
5761
},
5862
},

packages/desktop-electron/src/main/index.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -195,15 +195,10 @@ async function initialize() {
195195
logger.log("loading task finished")
196196
})()
197197

198-
const globals = {
199-
updaterEnabled: UPDATER_ENABLED,
200-
deepLinks: pendingDeepLinks,
201-
}
202-
203198
if (needsMigration) {
204199
const show = await Promise.race([loadingTask.then(() => false), delay(1_000).then(() => true)])
205200
if (show) {
206-
overlay = createLoadingWindow(globals)
201+
overlay = createLoadingWindow()
207202
await delay(1_000)
208203
}
209204
}
@@ -215,7 +210,7 @@ async function initialize() {
215210
await loadingComplete.promise
216211
}
217212

218-
mainWindow = createMainWindow(globals)
213+
mainWindow = createMainWindow()
219214
wireMenu()
220215

221216
overlay?.close()
@@ -252,6 +247,8 @@ registerIpcHandlers({
252247
initEmitter.off("step", listener)
253248
}
254249
},
250+
getWindowConfig: () => ({ updaterEnabled: UPDATER_ENABLED }),
251+
consumeInitialDeepLinks: () => pendingDeepLinks.splice(0),
255252
getDefaultServerUrl: () => getDefaultServerUrl(),
256253
setDefaultServerUrl: (url) => setDefaultServerUrl(url),
257254
getWslConfig: () => Promise.resolve(getWslConfig()),

packages/desktop-electron/src/main/ipc.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@ import { execFile } from "node:child_process"
22
import { BrowserWindow, Notification, app, clipboard, dialog, ipcMain, shell } from "electron"
33
import type { IpcMainEvent, IpcMainInvokeEvent } from "electron"
44

5-
import type { InitStep, ServerReadyData, SqliteMigrationProgress, TitlebarTheme, WslConfig } from "../preload/types"
5+
import type {
6+
InitStep,
7+
ServerReadyData,
8+
SqliteMigrationProgress,
9+
TitlebarTheme,
10+
WindowConfig,
11+
WslConfig,
12+
} from "../preload/types"
613
import { getStore } from "./store"
714
import { setTitlebar } from "./windows"
815

@@ -14,6 +21,8 @@ const pickerFilters = (ext?: string[]) => {
1421
type Deps = {
1522
killSidecar: () => void
1623
awaitInitialization: (sendStep: (step: InitStep) => void) => Promise<ServerReadyData>
24+
getWindowConfig: () => Promise<WindowConfig> | WindowConfig
25+
consumeInitialDeepLinks: () => Promise<string[]> | string[]
1726
getDefaultServerUrl: () => Promise<string | null> | string | null
1827
setDefaultServerUrl: (url: string | null) => Promise<void> | void
1928
getWslConfig: () => Promise<WslConfig>
@@ -37,6 +46,8 @@ export function registerIpcHandlers(deps: Deps) {
3746
const send = (step: InitStep) => event.sender.send("init-step", step)
3847
return deps.awaitInitialization(send)
3948
})
49+
ipcMain.handle("get-window-config", () => deps.getWindowConfig())
50+
ipcMain.handle("consume-initial-deep-links", () => deps.consumeInitialDeepLinks())
4051
ipcMain.handle("get-default-server-url", () => deps.getDefaultServerUrl())
4152
ipcMain.handle("set-default-server-url", (_event: IpcMainInvokeEvent, url: string | null) =>
4253
deps.setDefaultServerUrl(url),

packages/desktop-electron/src/main/menu.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export function createMenu(deps: Deps) {
4747
{
4848
label: "New Window",
4949
accelerator: "Cmd+Shift+N",
50-
click: () => createMainWindow({ updaterEnabled: UPDATER_ENABLED }),
50+
click: () => createMainWindow(),
5151
},
5252
{ type: "separator" },
5353
{ role: "close" },

packages/desktop-electron/src/main/windows.ts

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,6 @@ import { dirname, isAbsolute, join, relative, resolve } from "node:path"
44
import { fileURLToPath, pathToFileURL } from "node:url"
55
import type { TitlebarTheme } from "../preload/types"
66

7-
type Globals = {
8-
updaterEnabled: boolean
9-
deepLinks?: string[]
10-
}
11-
127
const root = dirname(fileURLToPath(import.meta.url))
138
const rendererRoot = join(root, "../renderer")
149
const rendererProtocol = "oc"
@@ -68,7 +63,7 @@ export function setDockIcon() {
6863
if (!icon.isEmpty()) app.dock?.setIcon(icon)
6964
}
7065

71-
export function createMainWindow(globals: Globals) {
66+
export function createMainWindow() {
7267
const state = windowState({
7368
defaultWidth: 1280,
7469
defaultHeight: 800,
@@ -98,15 +93,16 @@ export function createMainWindow(globals: Globals) {
9893
}
9994
: {}),
10095
webPreferences: {
101-
preload: join(root, "../preload/index.mjs"),
102-
sandbox: false,
96+
preload: join(root, "../preload/index.js"),
97+
contextIsolation: true,
98+
nodeIntegration: false,
99+
sandbox: true,
103100
},
104101
})
105102

106103
state.manage(win)
107104
loadWindow(win, "index.html")
108105
wireZoom(win)
109-
injectGlobals(win, globals)
110106

111107
win.once("ready-to-show", () => {
112108
win.show()
@@ -115,7 +111,7 @@ export function createMainWindow(globals: Globals) {
115111
return win
116112
}
117113

118-
export function createLoadingWindow(globals: Globals) {
114+
export function createLoadingWindow() {
119115
const mode = tone()
120116
const win = new BrowserWindow({
121117
width: 640,
@@ -134,13 +130,14 @@ export function createLoadingWindow(globals: Globals) {
134130
}
135131
: {}),
136132
webPreferences: {
137-
preload: join(root, "../preload/index.mjs"),
138-
sandbox: false,
133+
preload: join(root, "../preload/index.js"),
134+
contextIsolation: true,
135+
nodeIntegration: false,
136+
sandbox: true,
139137
},
140138
})
141139

142140
loadWindow(win, "loading.html")
143-
injectGlobals(win, globals)
144141

145142
return win
146143
}
@@ -174,20 +171,6 @@ function loadWindow(win: BrowserWindow, html: string) {
174171

175172
void win.loadURL(`${rendererProtocol}://${rendererHost}/${html}`)
176173
}
177-
178-
function injectGlobals(win: BrowserWindow, globals: Globals) {
179-
win.webContents.on("dom-ready", () => {
180-
const deepLinks = globals.deepLinks ?? []
181-
const data = {
182-
updaterEnabled: globals.updaterEnabled,
183-
deepLinks: Array.isArray(deepLinks) ? deepLinks.splice(0) : deepLinks,
184-
}
185-
void win.webContents.executeJavaScript(
186-
`window.__OPENCODE__ = Object.assign(window.__OPENCODE__ ?? {}, ${JSON.stringify(data)})`,
187-
)
188-
})
189-
}
190-
191174
function wireZoom(win: BrowserWindow) {
192175
win.webContents.setZoomFactor(1)
193176
win.webContents.on("zoom-changed", () => {

packages/desktop-electron/src/preload/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ const api: ElectronAPI = {
1111
ipcRenderer.removeListener("init-step", handler)
1212
})
1313
},
14+
getWindowConfig: () => ipcRenderer.invoke("get-window-config"),
15+
consumeInitialDeepLinks: () => ipcRenderer.invoke("consume-initial-deep-links"),
1416
getDefaultServerUrl: () => ipcRenderer.invoke("get-default-server-url"),
1517
setDefaultServerUrl: (url) => ipcRenderer.invoke("set-default-server-url", url),
1618
getWslConfig: () => ipcRenderer.invoke("get-wsl-config"),

packages/desktop-electron/src/preload/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,16 @@ export type TitlebarTheme = {
1515
mode: "light" | "dark"
1616
}
1717

18+
export type WindowConfig = {
19+
updaterEnabled: boolean
20+
}
21+
1822
export type ElectronAPI = {
1923
killSidecar: () => Promise<void>
2024
installCli: () => Promise<string>
2125
awaitInitialization: (onStep: (step: InitStep) => void) => Promise<ServerReadyData>
26+
getWindowConfig: () => Promise<WindowConfig>
27+
consumeInitialDeepLinks: () => Promise<string[]>
2228
getDefaultServerUrl: () => Promise<string | null>
2329
setDefaultServerUrl: (url: string | null) => Promise<void>
2430
getWslConfig: () => Promise<WslConfig>

packages/desktop-electron/src/renderer/env.d.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ declare global {
44
interface Window {
55
api: ElectronAPI
66
__OPENCODE__?: {
7-
updaterEnabled?: boolean
8-
wsl?: boolean
97
deepLinks?: string[]
108
}
119
}

packages/desktop-electron/src/renderer/index.tsx

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import { createEffect, createResource, onCleanup, onMount, Show } from "solid-js
2020
import { render } from "solid-js/web"
2121
import pkg from "../../package.json"
2222
import { initI18n, t } from "./i18n"
23-
import { UPDATER_ENABLED } from "./updater"
2423
import { webviewZoom } from "./webview-zoom"
2524
import "./styles.css"
2625
import { useTheme } from "@opencode-ai/ui/theme"
@@ -43,8 +42,7 @@ const emitDeepLinks = (urls: string[]) => {
4342
}
4443

4544
const listenForDeepLinks = () => {
46-
const startUrls = window.__OPENCODE__?.deepLinks ?? []
47-
if (startUrls.length) emitDeepLinks(startUrls)
45+
void window.api.consumeInitialDeepLinks().then((urls) => emitDeepLinks(urls))
4846
return window.api.onDeepLink((urls) => emitDeepLinks(urls))
4947
}
5048

@@ -57,13 +55,18 @@ const createPlatform = (): Platform => {
5755
return undefined
5856
})()
5957

58+
const isWslEnabled = async () => {
59+
if (os !== "windows") return false
60+
return window.api.getWslConfig().then((config) => config.enabled).catch(() => false)
61+
}
62+
6063
const wslHome = async () => {
61-
if (os !== "windows" || !window.__OPENCODE__?.wsl) return undefined
64+
if (!(await isWslEnabled())) return undefined
6265
return window.api.wslPath("~", "windows").catch(() => undefined)
6366
}
6467

6568
const handleWslPicker = async <T extends string | string[]>(result: T | null): Promise<T | null> => {
66-
if (!result || !window.__OPENCODE__?.wsl) return result
69+
if (!result || !(await isWslEnabled())) return result
6770
if (Array.isArray(result)) {
6871
return Promise.all(result.map((path) => window.api.wslPath(path, "linux").catch(() => path))) as any
6972
}
@@ -137,7 +140,7 @@ const createPlatform = (): Platform => {
137140
if (os === "windows") {
138141
const resolvedApp = app ? await window.api.resolveAppPath(app).catch(() => null) : null
139142
const resolvedPath = await (async () => {
140-
if (window.__OPENCODE__?.wsl) {
143+
if (await isWslEnabled()) {
141144
const converted = await window.api.wslPath(path, "windows").catch(() => null)
142145
if (converted) return converted
143146
}
@@ -159,12 +162,14 @@ const createPlatform = (): Platform => {
159162
storage,
160163

161164
checkUpdate: async () => {
162-
if (!UPDATER_ENABLED()) return { updateAvailable: false }
165+
const config = await window.api.getWindowConfig().catch(() => ({ updaterEnabled: false }))
166+
if (!config.updaterEnabled) return { updateAvailable: false }
163167
return window.api.checkUpdate()
164168
},
165169

166170
update: async () => {
167-
if (!UPDATER_ENABLED()) return
171+
const config = await window.api.getWindowConfig().catch(() => ({ updaterEnabled: false }))
172+
if (!config.updaterEnabled) return
168173
await window.api.installUpdate()
169174
},
170175

@@ -194,11 +199,7 @@ const createPlatform = (): Platform => {
194199
return fetch(input, init)
195200
},
196201

197-
getWslEnabled: async () => {
198-
const next = await window.api.getWslConfig().catch(() => null)
199-
if (next) return next.enabled
200-
return window.__OPENCODE__!.wsl ?? false
201-
},
202+
getWslEnabled: () => isWslEnabled(),
202203

203204
setWslEnabled: async (enabled) => {
204205
await window.api.setWslConfig({ enabled })
@@ -249,6 +250,7 @@ listenForDeepLinks()
249250

250251
render(() => {
251252
const platform = createPlatform()
253+
const [windowConfig] = createResource(() => window.api.getWindowConfig().catch(() => ({ updaterEnabled: false })))
252254
const loadLocale = async () => {
253255
const current = await platform.storage?.("opencode.global.dat").getItem("language")
254256
const legacy = current ? undefined : await platform.storage?.().getItem("language.v1")
@@ -325,7 +327,7 @@ render(() => {
325327
return (
326328
<PlatformProvider value={platform}>
327329
<AppBaseProviders locale={locale.latest}>
328-
<Show when={!defaultServer.loading && !sidecar.loading && !windowCount.loading && !locale.loading}>
330+
<Show when={!defaultServer.loading && !sidecar.loading && !windowConfig.loading && !windowCount.loading && !locale.loading}>
329331
{(_) => {
330332
return (
331333
<AppInterface

packages/desktop-electron/src/renderer/updater.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import { initI18n, t } from "./i18n"
22

3-
export const UPDATER_ENABLED = () => window.__OPENCODE__?.updaterEnabled ?? false
4-
53
export async function runUpdater({ alertOnFail }: { alertOnFail: boolean }) {
64
await initI18n()
75
try {

0 commit comments

Comments
 (0)