Интеграция через iframe
Для транспорт-независимой архитектуры сначала смотрите Обзор.
Зачем iframe-режим
Ссылка на заголовокIframe transport это базовый browser-сценарий для изолированных remote-расширений:
- host и remote работают в разных browser context;
- обмен идет через
postMessageи@remote-ui/rpc; - host сохраняет контроль рендера через provider и receiver.
Пример на стороне host
Ссылка на заголовокimport type { Channel } from '@omnicajs/vue-remote/host'import type { Endpoint } from '@remote-ui/rpc'
import { createApp, defineComponent, h, onBeforeUnmount, onMounted, ref } from 'vue'import { HostedTree, createProvider, createReceiver } from '@omnicajs/vue-remote/host'import { createEndpoint, fromIframe } from '@remote-ui/rpc'
import VButton from './components/VButton.vue'import VInput from './components/VInput.vue'
type HostBridge = { track(event: { type: string; payload: Record<string, unknown> }): void;}
type RemoteApi = { run(channel: Channel, bridge: HostBridge): Promise<void>; release(): void;}
const provider = createProvider({ VButton, VInput })
export function mountIframeRemote(remoteUrl: string, bridge: HostBridge) { return createApp(defineComponent({ setup() { const iframe = ref<HTMLIFrameElement | null>(null) const receiver = createReceiver() let endpoint: Endpoint<RemoteApi> | null = null
onMounted(() => { endpoint = createEndpoint<RemoteApi>(fromIframe(iframe.value as HTMLIFrameElement, { terminate: false, })) })
onBeforeUnmount(() => endpoint?.call.release())
return () => [ h(HostedTree, { provider, receiver }), h('iframe', { ref: iframe, src: remoteUrl, style: { display: 'none' }, onLoad: () => endpoint?.call.run(receiver.receive, bridge), }), ] }, }))}Пример на стороне remote
Ссылка на заголовокimport { createEndpoint, fromInsideIframe, release, retain } from '@remote-ui/rpc'import { createRemoteRenderer, createRemoteRoot, defineRemoteComponent } from '@omnicajs/vue-remote/remote'import { defineComponent, h, ref } from 'vue'
type HostBridge = { track(event: { type: string; payload: Record<string, unknown> }): void;}
const VButton = defineRemoteComponent('VButton')const VInput = defineRemoteComponent('VInput', [ 'update:value',] as unknown as { 'update:value': (value: string) => true;})
const endpoint = createEndpoint(fromInsideIframe())let onRelease = () => {}
endpoint.expose({ async run(channel, bridge: HostBridge) { retain(channel) retain(bridge)
const root = createRemoteRoot(channel, { components: ['VButton', 'VInput'], }) await root.mount()
const app = createRemoteRenderer(root).createApp(defineComponent({ setup() { const text = ref('') return () => [ h(VInput, { value: text.value, 'onUpdate:value': (value: string) => text.value = value, }), h(VButton, { onClick: () => bridge.track({ type: 'remote.clear', payload: { value: text.value }, }), }, 'Clear'), ] }, }))
app.mount(root)
onRelease = () => { release(channel) release(bridge) app.unmount() } },
release() { onRelease() },})Последовательность запуска iframe
Ссылка на заголовок- Host рендерит
HostedTreeи создаетreceiver. - Host загружает remote URL в скрытый iframe.
- Remote в iframe экспортирует
run/release. - На
loadhost вызываетrun(receiver.receive, hostBridge). - Remote монтируется и синхронизирует дерево по каналу.
- При teardown host вызывает
release().
Security-checklist для iframe
Ссылка на заголовок- Ограничивайте origin: используйте явные target origin вместо широких wildcard.
- Валидируйте remote URL: применяйте allowlist для источников extension runtime.
- Ужесточайте iframe-атрибуты:
настраивайте
sandboxпод вашу trust-модель. - Применяйте CSP/frame policy:
синхронизируйте
frame-src/child-srcс разрешенными origin.
Частые ошибки
Ссылка на заголовок- Вызов
run(...)до загрузки iframe. - Отсутствие host-компонента в provider.
- Несериализуемые payload на границе.
- Отсутствие
release()и утечки retained-ссылок.