| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475 |
- import { BrowserWindow, ipcMain } from 'electron'
- /** Local authenticated viewer. Agent ownership and control leases remain server-authoritative. */
- export function installComputerViewer({ owner, origin, preload, quitting = () => false }) {
- let viewer, viewerURL, currentTargetURL, approvedClose = false, opening = Promise.resolve()
- const trusted = event => event.sender === owner()?.webContents && event.senderFrame === event.sender.mainFrame
- && new URL(event.senderFrame.url).origin === origin()
- const targetURL = (id, target = {}) => {
- if (typeof id !== 'string' || !/^[\w-]{1,128}$/.test(id)) throw new Error('电脑窗口请求无效')
- if (!target || typeof target !== 'object' || Array.isArray(target)
- || Object.keys(target).some(key => !['backend', 'host'].includes(key))
- || (target.backend !== undefined && !['cloud', 'vm', 'managed-browser'].includes(target.backend))
- || target.host !== undefined)
- throw new Error('所选电脑无效,请重新选择')
- const url = new URL(`/conversations/computer/${encodeURIComponent(id)}`, origin())
- if (target.backend) url.searchParams.set('backend', target.backend)
- return url.href
- }
- ipcMain.handle('desktop-computer:open', (event, id, target) => {
- if (!trusted(event)) throw new Error('电脑窗口请求无效')
- const requestedURL = targetURL(id, target)
- const pending = opening.catch(() => {}).then(() => open(requestedURL))
- opening = pending
- return pending
- })
- async function open(url) {
- if (quitting()) return false
- if (viewer && !viewer.isDestroyed()) {
- if (currentTargetURL === url) { viewer.show(); viewer.focus(); return true }
- // The old page must return its lease before another target can open.
- const previous = viewer
- await new Promise((resolve, reject) => {
- const closed = () => { clearTimeout(timeout); resolve() }
- const timeout = setTimeout(() => {
- previous.removeListener('closed', closed)
- if (!previous.isDestroyed()) { previous.show(); previous.focus() }
- reject(new Error('当前电脑尚未交还,请在电脑窗口交还并关闭后重试'))
- }, 15000)
- previous.once('closed', closed)
- previous.close()
- })
- }
- if (quitting()) return false
- viewerURL = url; currentTargetURL = url; approvedClose = false
- const current = viewer = new BrowserWindow({ parent: owner(), modal: false, width: 1220, height: 820,
- minWidth: 760, minHeight: 520, show: false, title: '电脑接管 · 夭夭 AI', backgroundColor: '#111315', autoHideMenuBar: true,
- webPreferences: { preload, sandbox: true, contextIsolation: true, nodeIntegration: false, webSecurity: true } })
- current.webContents.setWindowOpenHandler(() => ({ action: 'deny' }))
- current.webContents.on('will-navigate', (event, target) => { if (target !== url) event.preventDefault() })
- current.webContents.on('will-redirect', event => event.preventDefault())
- current.webContents.on('will-attach-webview', event => event.preventDefault())
- current.on('close', event => {
- if (!approvedClose && !quitting() && !current.webContents.isCrashed()) {
- event.preventDefault(); current.webContents.send('desktop-computer:request-close')
- }
- })
- current.on('closed', () => { if (viewer === current) { viewer = undefined; viewerURL = undefined; currentTargetURL = undefined } })
- current.once('ready-to-show', () => { current.show(); current.focus() })
- try { await current.loadURL(url) } catch (error) { approvedClose = true; current.close(); throw error }
- return true
- }
- ipcMain.handle('desktop-computer:target', (event, target) => {
- if (event.sender !== viewer?.webContents || event.senderFrame !== event.sender.mainFrame
- || event.senderFrame.url !== viewerURL) throw new Error('电脑窗口身份无效')
- currentTargetURL = targetURL(decodeURIComponent(new URL(viewerURL).pathname.split('/').at(-1)), target)
- })
- ipcMain.handle('desktop-computer:closed', event => {
- if (event.sender !== viewer?.webContents || event.senderFrame !== event.sender.mainFrame
- || event.senderFrame.url !== viewerURL) throw new Error('电脑窗口身份无效')
- approvedClose = true; viewer.close()
- })
- const close = () => { approvedClose = true; viewer?.destroy() }
- close.owns = contents => contents === viewer?.webContents
- return close
- }
|