Integración con iframe
Para una arquitectura agnóstica al transporte, empieza por Resumen.
Por qué usar el modo iframe
Enlace al encabezadoEl transporte por iframe es la configuración base en navegador para extensiones remotas aisladas:
- host y remoto se ejecutan en contextos de navegador separados;
- la comunicación usa
postMessagea través de@remote-ui/rpc; - el host conserva el control del render mediante los límites de provider y receiver.
Ejemplo del lado host
Enlace al encabezadoimport 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), }), ] }, }))}Ejemplo del lado remoto
Enlace al encabezadoimport { 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() },})Secuencia de arranque del iframe
Enlace al encabezado- El host renderiza
HostedTreey creareceiver. - El host carga la URL remota en un iframe oculto.
- El iframe remoto expone
run/release. - En el evento
loaddel iframe, el host llama arun(receiver.receive, hostBridge). - El remoto monta y sincroniza actualizaciones del árbol por el canal.
- El host llama a
release()durante el teardown.
Checklist de seguridad para iframe
Enlace al encabezado- Restrict origins: prefiere origins de destino explícitos frente a comodines permisivos.
- Validate remote URLs: usa allowlists para las fuentes del runtime de extensiones.
- Harden iframe attributes:
configura las capacidades de
sandboxsegún tu modelo de confianza. - Enforce CSP/frame policy:
alinea
frame-src/child-srccon los origins de extensión aprobados.
Errores frecuentes
Enlace al encabezado- Llamar a
run(...)antes de que cargue el iframe. - Olvidar registrar componentes host en el provider.
- Pasar payloads no serializables a través de la frontera.
- Olvidar
release()y filtrar referencias retenidas.