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

Интеграция через 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

Ссылка на заголовок
  1. Host рендерит HostedTree и создает receiver.
  2. Host загружает remote URL в скрытый iframe.
  3. Remote в iframe экспортирует run/release.
  4. На load host вызывает run(receiver.receive, hostBridge).
  5. Remote монтируется и синхронизирует дерево по каналу.
  6. При teardown host вызывает release().

Security-checklist для iframe

Ссылка на заголовок
  1. Ограничивайте origin: используйте явные target origin вместо широких wildcard.
  2. Валидируйте remote URL: применяйте allowlist для источников extension runtime.
  3. Ужесточайте iframe-атрибуты: настраивайте sandbox под вашу trust-модель.
  4. Применяйте CSP/frame policy: синхронизируйте frame-src/child-src с разрешенными origin.

Частые ошибки

Ссылка на заголовок
  • Вызов run(...) до загрузки iframe.
  • Отсутствие host-компонента в provider.
  • Несериализуемые payload на границе.
  • Отсутствие release() и утечки retained-ссылок.

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

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