Skip to content

Window Transport

window.open transport is a good fit when remote runtime needs its own visible workspace:

  • extension behaves like a tool panel or editor;
  • remote UI should be isolated from host layout and lifecycle;
  • you want explicit postMessage boundaries without iframe embedding.

Host opens a popup window and both sides exchange messages through postMessage. @remote-ui/rpc can work here through a custom 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
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()
},
})
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 blocking: open popup from a direct user gesture.
  2. Origin checks: always validate event.origin and event.source.
  3. Lifecycle sync: handle popup close and host unload symmetrically.
  4. Security model: this is a transport boundary, not full policy governance.