En el desarrollo de una extensión para VSCode, se integra una sub-aplicación Vue 3 mediante iframes. Para gestionar una comunicación frecuente y limpia entre ambos, se encapsula la lógica de mensajes utilizando el patrón Promise y la biblioteca mitt para el sistema de publicación-suscripción.
La comunicación se basa en parent.postMessage para enviar mensajes a la base y window.addEventListener('message', ...) para recibirlos. Para mejorar la estructura en el código Vue 3, se crea una capa de abstracción.
Definición de tipos TypeScript
// src/messaging/types.ts
export enum INCOMING_EVENTS {
APIS = 'FetchApis',
SETTINGS = 'LoadSettings'
}
export enum OUTGOING_EVENTS {
APIS = 'FetchApis',
SNIPPET = 'GenerateSnippet'
}
export type INCOMING_EVENT_KEYS = keyof typeof INCOMING_EVENTS;
export type OUTGOING_EVENT_KEYS = keyof typeof OUTGOING_EVENTS;
type EventMap<K extends string, V extends Record<K, unknown>> = {
[P in K]: V[P]
};
export type EventBusType = EventMap<
INCOMING_EVENT_KEYS,
{
APIS: DataModel.Content[]
SETTINGS: ConfigModel.Configuration
}
>;
Configuración del listener de mensajes
// src/messaging/receiver.ts
import mitt from 'mitt'
import { INCOMING_EVENT_KEYS, INCOMING_EVENTS } from './types'
import type { EventBusType } from './types'
export const messageBus = mitt<EventBusType>()
const resolveEventKey = (identifier: string): INCOMING_EVENT_KEYS | null => {
for (const [key, value] of Object.entries(INCOMING_EVENTS)) {
if (value === identifier) return key as INCOMING_EVENT_KEYS
}
return null
}
const payloadProcessors: {
[K in INCOMING_EVENT_KEYS]: (raw: any) => EventBusType[K]
} = {
APIS: (raw) => {
if (!Array.isArray(raw)) throw new Error('Formato de APIs inválido')
return raw.map<DataModel.Content>((item: any) => ({
body: typeof item.body === 'string' ? item.body : JSON.stringify(item.body),
kind: item.kind as DataModel.EntryType
}))
},
SETTINGS: () => ({
sources: [],
resolver: {}
})
}
export const initializeMessageReceiver = () => {
window.addEventListener('message', (event: MessageEvent) => {
if (!event.data) return
const { payload, eventId } = event.data
if (!eventId) return
const busKey = resolveEventKey(eventId)
if (!busKey) return
messageBus.emit(busKey, payloadProcessors[busKey](payload))
})
}
Métodos para enviar mensajes
// src/messaging/sender.ts
import {
OUTGOING_EVENTS,
OUTGOING_EVENT_KEYS,
EventBusType,
INCOMING_EVENT_KEYS
} from './types'
import { messageBus } from './receiver'
export const sendToParent = <K extends OUTGOING_EVENT_KEYS, T = any>(
eventKey: K,
payload?: T
) => {
parent.postMessage(
{ eventId: OUTGOING_EVENTS[eventKey], payload: payload || {} },
'*'
)
}
const awaitResponse = <
SendKey extends OUTGOING_EVENT_KEYS,
ListenKey extends INCOMING_EVENT_KEYS,
T = any
>(
sendEvent: SendKey,
listenEvent: ListenKey,
data?: T,
timeoutMs = 3000
): Promise<EventBusType[ListenKey]> => {
return new Promise((resolve, reject) => {
sendToParent(sendEvent, data)
const timer = setTimeout(() => {
reject(new Error('Tiempo de espera excedido'))
messageBus.off(listenEvent)
}, timeoutMs)
messageBus.on(listenEvent, (responseData) => {
resolve(responseData)
clearTimeout(timer)
messageBus.off(listenEvent)
})
})
}
export const fetchApisWithResponse = async () => {
return awaitResponse('APIS', 'APIS', undefined, 180000)
}
export const emitCodeSnippet = (data: unknown) => {
sendToParent('SNIPPET', data)
}
Exportación centralizada
// src/messaging/index.ts
export * from './types'
export * from './receiver'
export * from './sender'
Entegración en la aplicación
// src/main.ts
import { createApp } from 'vue'
import { initializeMessageReceiver } from './messaging'
const app = createApp(App)
initializeMessageReceiver()
app.mount('#app')
// Ejemplo de uso en un componente
import { onMounted } from 'vue'
import { fetchApisWithResponse, messageBus } from '@/messaging'
onMounted(async () => {
try {
const apiData = await fetchApisWithResponse()
console.log('Datos de APIs recibidos:', apiData)
} catch (error) {
console.error('Error al obtener APIs:', error)
}
// Alternativamente, suscribirse a eventos específicos
messageBus.on('APIS', (event) => {
// Manejar evento de APIs entrante
})
})