Перейти к основному содержимому

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()
},
})

Практические ограничения

Ссылка на заголовок
  1. Блокировка popup: открывайте окно только из user gesture.
  2. Проверки origin: всегда валидируйте event.origin и event.source.
  3. Синхронизация lifecycle: обрабатывайте закрытие popup и выгрузку host симметрично.
  4. Security-модель: transport boundary не заменяет policy governance.

Связанные разделы

Ссылка на заголовок