Window Transport
Когда нужен этот режим
Ссылка на заголовокwindow.open transport подходит, когда remote runtime нужен собственный видимый workspace:
- расширение работает как tool-панель или редактор;
- remote UI должен быть отделен от layout и lifecycle host;
- нужна явная postMessage-граница без iframe-встраивания.
Базовая модель
Ссылка на заголовокHost открывает popup-окно, а стороны обмениваются сообщениями через postMessage.
@remote-ui/rpc можно использовать через кастомный MessageEndpoint wrapper.
Host App -> popup = window.open(...) -> createEndpoint(fromWindow(popup)) -> call.run(receiver.receive, hostBridge)
Popup Remote App -> createEndpoint(fromOpener(window.opener)) -> expose run/releaseПример на стороне host
Ссылка на заголовокimport type { MessageEndpoint } from '@remote-ui/rpc'import type { Channel } from '@omnicajs/vue-remote/host'
import { createEndpoint } from '@remote-ui/rpc'import { createReceiver } from '@omnicajs/vue-remote/host'
type RemoteApi = { run(channel: Channel, bridge: { closeRequested(): void }): Promise<void>; release(): void;}
function fromWindow(target: Window, targetOrigin: string): MessageEndpoint { const listeners = new Set<(event: MessageEvent) => void>()
const onMessage = (event: MessageEvent) => { if (event.origin !== targetOrigin) return if (event.source !== target) return for (const listener of listeners) listener(event) }
window.addEventListener('message', onMessage)
return { postMessage(message: any) { target.postMessage(message, targetOrigin) }, addEventListener(event, listener) { if (event === 'message') listeners.add(listener) }, removeEventListener(event, listener) { if (event === 'message') listeners.delete(listener) }, terminate() { window.removeEventListener('message', onMessage) target.close() }, }}
const popup = window.open('/remote/popup.html', 'remote-runtime', 'width=1200,height=800')if (!popup) throw new Error('Popup blocked')
const receiver = createReceiver()const endpoint = createEndpoint<RemoteApi>(fromWindow(popup, window.location.origin))
await endpoint.call.run(receiver.receive, { closeRequested() { popup.close() },})Пример на стороне remote popup
Ссылка на заголовокimport type { MessageEndpoint } from '@remote-ui/rpc'import type { Channel } from '@omnicajs/vue-remote/host'
import { createEndpoint, release, retain } from '@remote-ui/rpc'import { createRemoteRoot, createRemoteRenderer, defineRemoteComponent } from '@omnicajs/vue-remote/remote'import { defineComponent, h } from 'vue'
function fromOpener(opener: Window, targetOrigin: string): MessageEndpoint { const listeners = new Set<(event: MessageEvent) => void>()
const onMessage = (event: MessageEvent) => { if (event.origin !== targetOrigin) return if (event.source !== opener) return for (const listener of listeners) listener(event) }
window.addEventListener('message', onMessage)
return { postMessage(message: any) { opener.postMessage(message, targetOrigin) }, addEventListener(event, listener) { if (event === 'message') listeners.add(listener) }, removeEventListener(event, listener) { if (event === 'message') listeners.delete(listener) }, terminate() { window.removeEventListener('message', onMessage) window.close() }, }}
const endpoint = createEndpoint<{ run(channel: Channel, bridge: { closeRequested(): void }): Promise<void>; release(): void;}>(fromOpener(window.opener as Window, window.location.origin))
let onRelease = () => {}
const VInspectorBadge = defineRemoteComponent('VInspectorBadge')
endpoint.expose({ async run(channel, bridge) { retain(channel) retain(bridge)
const root = createRemoteRoot(channel, { components: ['VInspectorBadge'] }) await root.mount()
const app = createRemoteRenderer(root).createApp(defineComponent({ setup() { return () => h(VInspectorBadge, { tone: 'neutral', label: 'Popup runtime connected', onClose: () => bridge.closeRequested(), }) }, }))
app.mount(root)
onRelease = () => { release(channel) release(bridge) app.unmount() } }, release() { onRelease() },})Практические ограничения
Ссылка на заголовок- Блокировка popup: открывайте окно только из user gesture.
- Проверки origin:
всегда валидируйте
event.originиevent.source. - Синхронизация lifecycle: обрабатывайте закрытие popup и выгрузку host симметрично.
- Security-модель: transport boundary не заменяет policy governance.