Settings gets a Phone link window: when enabled, the IDE main process runs a small TLS proxy on the local network (self-signed certificate, pinned by its fingerprint) in front of the loopback backend and pairs a phone by QR code or an 8-character code. Each phone gets its own revocable token; the backend session token never leaves the computer. The root page, terminal sockets and secret or settings-changing endpoints are not reachable from a phone. The old WebRTC pairing window is replaced. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B8ppceaR5s6cvMnm7uGRnf
46 lines
2.3 KiB
JavaScript
46 lines
2.3 KiB
JavaScript
// "Telefon bağlantısı" window: a thin IPC shell around the controller built in
|
||
// electron/mobile-lan-proxy.ts (createMobileLink). The controller lives in the
|
||
// main process for the whole app run — closing this window only cancels a
|
||
// pairing in progress; paired phones stay connected.
|
||
import { join } from 'node:path'
|
||
import { BrowserWindow, ipcMain } from 'electron'
|
||
|
||
const CODED = /^aiturk-mobile:[a-z_]+$/
|
||
|
||
export function createMobileWindow({ assets, link, show = true }) {
|
||
const window = new BrowserWindow({ width: 560, height: 800, show, title: 'AITURK · Telefon bağlantısı',
|
||
webPreferences: { preload: join(assets, 'mobile-preload.cjs'), contextIsolation: true,
|
||
nodeIntegration: false, sandbox: true } })
|
||
window.setMenu(null)
|
||
window.webContents.setWindowOpenHandler(() => ({ action: 'deny' }))
|
||
window.webContents.on('will-navigate', event => event.preventDefault())
|
||
const actions = {
|
||
state: () => link.state(),
|
||
enable: () => link.enable(),
|
||
disable: () => link.disable(),
|
||
'pair.begin': () => link.pairBegin(),
|
||
'pair.cancel': () => link.pairCancel(),
|
||
devices: () => link.devices(),
|
||
'device.revoke': params => link.deviceRevoke(params?.deviceId),
|
||
'firewall.retry': () => link.firewallRetry(),
|
||
'firewall.remove': () => link.firewallRemove(),
|
||
}
|
||
const handler = async (event, action, params) => {
|
||
if (event.sender !== window.webContents || event.senderFrame !== window.webContents.mainFrame) throw new Error('aiturk-mobile:forbidden')
|
||
if (typeof action !== 'string' || !Object.hasOwn(actions, action)) throw new Error('aiturk-mobile:unsupported')
|
||
try { return await actions[action](params) } catch (error) {
|
||
// Only fixed codes cross to the renderer; never upstream error text.
|
||
throw new Error(CODED.test(error?.message) ? error.message : 'aiturk-mobile:failed')
|
||
}
|
||
}
|
||
const unsubscribe = link.onEvent(event => { if (!window.isDestroyed()) window.webContents.send('aiturk-mobile-event', event) })
|
||
ipcMain.handle('aiturk-mobile', handler)
|
||
window.on('closed', () => {
|
||
unsubscribe(); ipcMain.removeHandler('aiturk-mobile')
|
||
// The code on screen is gone with the window; do not leave pairing open behind it.
|
||
void link.pairCancel().catch(() => {})
|
||
})
|
||
void window.loadFile(join(assets, 'mobile-host.html'))
|
||
return window
|
||
}
|