mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-08-01 16:43:37 +08:00
feat(desktop): preview-agent bridge with ready/navigated reporting
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
06461a93e9
commit
36a3ffff34
@ -1 +1 @@
|
|||||||
(()=>{(()=>{window.__PREVIEW_AGENT__=!0})();})();
|
(()=>{var g=new Set(["enter-picker","exit-picker","capture"]);function a(s){return JSON.stringify({v:1,...s})}function p(s){try{let e=JSON.parse(s);if(typeof e!=="object"||e===null||e.v!==1||typeof e.type!=="string"||!g.has(e.type))return null;let{v:n,...t}=e;return t}catch{return null}}function i(s){let e=new Map,n=(t)=>s.postToHost(a(t));return{reportReady:()=>n({type:"ready"}),reportNavigated:()=>n({type:"navigated",url:s.location.href,title:s.title}),reportError:(t)=>n({type:"error",message:t}),send:n,on(t,r){let o=e.get(t)??[];o.push(r),e.set(t,o)},handleHostRaw(t){let r=p(t);if(!r)return;for(let o of e.get(r.type)??[])o(r)}}}(()=>{window.__PREVIEW_AGENT__=!0;let e=i({postToHost:(t)=>{let r=window.__TAURI_INTERNALS__;if(r?.invoke)r.invoke("preview_message",{raw:t})},location:window.location,title:document.title});window.__PREVIEW_BRIDGE__=e;let n=()=>{e.reportReady(),e.reportNavigated()};if(document.readyState!=="loading")n();else document.addEventListener("DOMContentLoaded",n);window.addEventListener("popstate",()=>e.reportNavigated())})();})();
|
||||||
|
|||||||
21
desktop/src/preview-agent/bridge.test.ts
Normal file
21
desktop/src/preview-agent/bridge.test.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
import { createBridge } from './bridge'
|
||||||
|
|
||||||
|
describe('createBridge', () => {
|
||||||
|
it('reports ready and navigated via postToHost', () => {
|
||||||
|
const postToHost = vi.fn()
|
||||||
|
const bridge = createBridge({ postToHost, location: { href: 'http://x/a' } as Location, title: 'T' })
|
||||||
|
bridge.reportReady()
|
||||||
|
bridge.reportNavigated()
|
||||||
|
expect(JSON.parse(postToHost.mock.calls[0]![0]!)).toMatchObject({ type: 'ready' })
|
||||||
|
expect(JSON.parse(postToHost.mock.calls[1]![0]!)).toMatchObject({ type: 'navigated', url: 'http://x/a', title: 'T' })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('dispatches host messages to registered handlers', () => {
|
||||||
|
const postToHost = vi.fn(); const onEnter = vi.fn()
|
||||||
|
const bridge = createBridge({ postToHost, location: {} as Location, title: '' })
|
||||||
|
bridge.on('enter-picker', onEnter)
|
||||||
|
bridge.handleHostRaw('{"v":1,"type":"enter-picker"}')
|
||||||
|
expect(onEnter).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
22
desktop/src/preview-agent/bridge.ts
Normal file
22
desktop/src/preview-agent/bridge.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { parseHostMessage, serializeAgentMessage, type AgentMessage, type HostMessage } from './protocol'
|
||||||
|
|
||||||
|
type Deps = { postToHost: (raw: string) => void; location: Location; title: string }
|
||||||
|
|
||||||
|
export function createBridge(deps: Deps) {
|
||||||
|
const handlers = new Map<HostMessage['type'], Array<(m: HostMessage) => void>>()
|
||||||
|
const send = (m: AgentMessage) => deps.postToHost(serializeAgentMessage(m))
|
||||||
|
return {
|
||||||
|
reportReady: () => send({ type: 'ready' }),
|
||||||
|
reportNavigated: () => send({ type: 'navigated', url: deps.location.href, title: deps.title }),
|
||||||
|
reportError: (message: string) => send({ type: 'error', message }),
|
||||||
|
send,
|
||||||
|
on(type: HostMessage['type'], fn: (m: HostMessage) => void) {
|
||||||
|
const arr = handlers.get(type) ?? []; arr.push(fn); handlers.set(type, arr)
|
||||||
|
},
|
||||||
|
handleHostRaw(raw: string) {
|
||||||
|
const msg = parseHostMessage(raw)
|
||||||
|
if (!msg) return
|
||||||
|
for (const fn of handlers.get(msg.type) ?? []) fn(msg)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,19 @@
|
|||||||
// 注入到被预览页面的 IIFE 入口(M3 逐步填实)
|
import { createBridge } from './bridge'
|
||||||
|
|
||||||
;(() => {
|
;(() => {
|
||||||
// 标记注入成功,供宿主探测
|
|
||||||
;(window as unknown as { __PREVIEW_AGENT__?: boolean }).__PREVIEW_AGENT__ = true
|
;(window as unknown as { __PREVIEW_AGENT__?: boolean }).__PREVIEW_AGENT__ = true
|
||||||
|
|
||||||
|
const postToHost = (raw: string) => {
|
||||||
|
const internals = (window as unknown as { __TAURI_INTERNALS__?: { invoke?: (c: string, a: unknown) => void } }).__TAURI_INTERNALS__
|
||||||
|
if (internals?.invoke) internals.invoke('preview_message', { raw })
|
||||||
|
// 回退(M1 证伪 IPC 时启用):new WebSocket('ws://127.0.0.1:'+PORT+'/preview-agent') ...
|
||||||
|
}
|
||||||
|
|
||||||
|
const bridge = createBridge({ postToHost, location: window.location, title: document.title })
|
||||||
|
;(window as unknown as { __PREVIEW_BRIDGE__?: unknown }).__PREVIEW_BRIDGE__ = bridge
|
||||||
|
|
||||||
|
const onReady = () => { bridge.reportReady(); bridge.reportNavigated() }
|
||||||
|
if (document.readyState !== 'loading') onReady()
|
||||||
|
else document.addEventListener('DOMContentLoaded', onReady)
|
||||||
|
window.addEventListener('popstate', () => bridge.reportNavigated())
|
||||||
})()
|
})()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user