diff --git a/frontend/webcomponent/src/components/CommandsManager.ts b/frontend/webcomponent/src/components/CommandsManager.ts index 65e6be1..837d5a1 100644 --- a/frontend/webcomponent/src/components/CommandsManager.ts +++ b/frontend/webcomponent/src/components/CommandsManager.ts @@ -56,6 +56,17 @@ export class CommandsManager { this.sendMessage(message); } + /** + * Updates the target origin used when sending messages to the iframe. + * This should be called once the iframe URL is known to improve security. + * + * @param newOrigin - The origin of the content loaded in the iframe + * (e.g. 'https://meet.example.com') + */ + public setTargetOrigin(newOrigin: string): void { + this.targetIframeOrigin = newOrigin; + } + /** * Subscribe to an event * @param eventName Name of the event to listen for @@ -85,9 +96,7 @@ export class CommandsManager { handlers?.add(callback); // Register with standard DOM API - element.addEventListener(eventName, listener); - return this; } @@ -112,7 +121,6 @@ export class CommandsManager { }; this.on(element, eventName, wrapperCallback); - return this; } @@ -156,30 +164,16 @@ export class CommandsManager { this.sendMessage(message); } - // public toggleChat() { - // const message: ParentMessage = { action: WebComponentActionType.TOGGLE_CHAT }; - // this.commandsManager.sendMessage(message); - // } - - /** - * Updates the target origin used when sending messages to the iframe. - * This should be called once the iframe URL is known to improve security. - * - * @param newOrigin - The origin of the content loaded in the iframe - * (e.g. 'https://meet.example.com') - */ - public setTargetOrigin(newOrigin: string): void { - this.targetIframeOrigin = newOrigin; - } - /** * Sends a message to the iframe using window.postMessage * * @param message - The message to send to the iframe - * @param explicitTargetOrigin - Optional override for the target origin + * @param targetOrigin - Optional override for the target origin */ - private sendMessage(message: WebComponentInboundCommandMessage, explicitTargetOrigin?: string): void { - explicitTargetOrigin = explicitTargetOrigin || this.targetIframeOrigin; - this.iframe.contentWindow?.postMessage(message, explicitTargetOrigin); + private sendMessage( + message: WebComponentInboundCommandMessage, + targetOrigin: string = this.targetIframeOrigin + ): void { + this.iframe.contentWindow?.postMessage(message, targetOrigin); } } diff --git a/frontend/webcomponent/src/components/EventsManager.ts b/frontend/webcomponent/src/components/EventsManager.ts index 0916294..5eb9144 100644 --- a/frontend/webcomponent/src/components/EventsManager.ts +++ b/frontend/webcomponent/src/components/EventsManager.ts @@ -2,9 +2,22 @@ import { WebComponentOutboundEventMessage } from '../typings/ce/message.type'; export class EventsManager { private element: HTMLElement; + private targetIframeOrigin: string; - constructor(element: HTMLElement) { + constructor(element: HTMLElement, initialTargetOrigin: string) { this.element = element; + this.targetIframeOrigin = initialTargetOrigin; + } + + /** + * Updates the target origin used when sending messages to the iframe. + * This should be called once the iframe URL is known to improve security. + * + * @param newOrigin - The origin of the content loaded in the iframe + * (e.g. 'https://meet.example.com') + */ + public setTargetOrigin(newOrigin: string): void { + this.targetIframeOrigin = newOrigin; } public listen() { @@ -18,8 +31,12 @@ export class EventsManager { private handleMessage(event: MessageEvent) { const message: WebComponentOutboundEventMessage = event.data; // Validate message origin (security measure) + if (event.origin !== this.targetIframeOrigin) { + console.warn('Message from unknown origin:', event.origin); + return; + } + if (!message || !message.event) { - // console.warn('Invalid message:', message); return; } diff --git a/frontend/webcomponent/src/components/OpenViduMeet.ts b/frontend/webcomponent/src/components/OpenViduMeet.ts index 340dfe7..b62900f 100644 --- a/frontend/webcomponent/src/components/OpenViduMeet.ts +++ b/frontend/webcomponent/src/components/OpenViduMeet.ts @@ -5,14 +5,16 @@ import styles from '../assets/css/styles.css'; /** * The `OpenViduMeet` web component provides an interface for embedding an OpenVidu Meet room within a web page. - * It allows for dynamic configuration through attributes and provides methods to interact with the OpenVidu Meet. + * It can also be used to view a recording of a meeting. + * It allows for dynamic configuration through attributes and provides methods to interact with OpenVidu Meet. * * @example * ```html - * + * * ``` * - * @attribute roomUrl - The base URL of the OpenVidu Meet room. This attribute is required. + * @attribute room-url - The base URL of the OpenVidu Meet room. This attribute is required unless `recording-url` is provided. + * @attribute recording-url - The URL of a recording to view. If this is provided, the `room-url` is not required. * * @public */ @@ -43,7 +45,7 @@ export class OpenViduMeet extends HTMLElement { ); this.commandsManager = new CommandsManager(this.iframe, this.targetIframeOrigin); - this.eventsManager = new EventsManager(this); + this.eventsManager = new EventsManager(this, this.targetIframeOrigin); // Listen for changes in attributes to update the iframe src const observer = new MutationObserver(() => this.updateIframeSrc()); @@ -117,7 +119,8 @@ export class OpenViduMeet extends HTMLElement { this.loadTimeout = null; this.iframe.onload = null; }; - // this.iframe.onload = this.handleIframeLoaded.bind(this); + + // Handle iframe errors this.iframe.onerror = (event: Event | string) => { console.error('Iframe error:', event); clearTimeout(this.loadTimeout); @@ -140,6 +143,7 @@ export class OpenViduMeet extends HTMLElement { const url = new URL(baseUrl); this.targetIframeOrigin = url.origin; this.commandsManager.setTargetOrigin(this.targetIframeOrigin); + this.eventsManager.setTargetOrigin(this.targetIframeOrigin); // Update query params Array.from(this.attributes).forEach((attr) => { diff --git a/typings/src/webcomponent/command.model.ts b/typings/src/webcomponent/command.model.ts index cb55901..1c71217 100644 --- a/typings/src/webcomponent/command.model.ts +++ b/typings/src/webcomponent/command.model.ts @@ -2,23 +2,21 @@ * All available commands that can be sent to the WebComponent. */ export enum WebComponentCommand { - /** - * Initializes the WebComponent with the given configuration. - * This command is sent from the webcomponent to the iframe for intialice the domain. - * @private - */ - INITIALIZE = 'INITIALIZE', - - /** - * Ends the current meeting for all participants. - * This command is only available for the moderator. - */ - END_MEETING = 'END_MEETING', - /** - * Disconnects the local participant from the current room. - */ - LEAVE_ROOM = 'LEAVE_ROOM' - // TOGGLE_CHAT = 'TOGGLE_CHAT' + /** + * Initializes the WebComponent with the given configuration. + * This command is sent from the webcomponent to the iframe for intialice the domain. + * @private + */ + INITIALIZE = 'INITIALIZE', + /** + * Ends the current meeting for all participants. + * This command is only available for the moderator. + */ + END_MEETING = 'END_MEETING', + /** + * Disconnects the local participant from the current room. + */ + LEAVE_ROOM = 'LEAVE_ROOM' } /** @@ -27,16 +25,16 @@ export enum WebComponentCommand { * @category Communication */ export interface WebComponentCommandPayloads { - /** - * Payload for the INITIALIZE command. - * @private - */ - [WebComponentCommand.INITIALIZE]: { - domain: string; - }; - [WebComponentCommand.END_MEETING]: void; - [WebComponentCommand.LEAVE_ROOM]: void; - // [WebComponentCommand.TOGGLE_CHAT]: void; + /** + * Payload for the INITIALIZE command. + * @private + */ + [WebComponentCommand.INITIALIZE]: { + domain: string; + }; + [WebComponentCommand.END_MEETING]: void; + [WebComponentCommand.LEAVE_ROOM]: void; + // [WebComponentCommand.TOGGLE_CHAT]: void; } /** @@ -46,5 +44,5 @@ export interface WebComponentCommandPayloads { * @private */ export type WenComponentCommandPayloadFor = T extends keyof WebComponentCommandPayloads - ? WebComponentCommandPayloads[T] - : never; + ? WebComponentCommandPayloads[T] + : never; diff --git a/typings/src/webcomponent/event.model.ts b/typings/src/webcomponent/event.model.ts index dd13cf8..0052e44 100644 --- a/typings/src/webcomponent/event.model.ts +++ b/typings/src/webcomponent/event.model.ts @@ -3,23 +3,23 @@ * @category Communication */ export enum WebComponentEvent { - /** - * Event emitted when application is ready to receive commands. - * @private - */ - READY = 'READY', - /** - * Event emitted when the local participant joins the room. - */ - JOIN = 'JOIN', - /** - * Event emitted when the local participant leaves the room. - */ - LEFT = 'LEFT', - /** - * Event emitted when a moderator ends the meeting. - */ - MEETING_ENDED = 'MEETING_ENDED' + /** + * Event emitted when application is ready to receive commands. + * @private + */ + READY = 'READY', + /** + * Event emitted when the local participant joins the room. + */ + JOIN = 'JOIN', + /** + * Event emitted when the local participant leaves the room. + */ + LEFT = 'LEFT', + /** + * Event emitted when a moderator ends the meeting. + */ + MEETING_ENDED = 'MEETING_ENDED' } /** @@ -28,23 +28,23 @@ export enum WebComponentEvent { * @category Communication */ export interface WebComponentEventPayloads { - /** - * Payload for the {@link WebComponentEvent.READY} event. - * @private - */ - [WebComponentEvent.READY]: {}; - [WebComponentEvent.JOIN]: { - roomId: string; - participantName: string; - }; - [WebComponentEvent.LEFT]: { - roomId: string; - participantName: string; - reason: string; - }; - [WebComponentEvent.MEETING_ENDED]: { - roomId: string; - }; + /** + * Payload for the {@link WebComponentEvent.READY} event. + * @private + */ + [WebComponentEvent.READY]: {}; + [WebComponentEvent.JOIN]: { + roomId: string; + participantName: string; + }; + [WebComponentEvent.LEFT]: { + roomId: string; + participantName: string; + reason: string; + }; + [WebComponentEvent.MEETING_ENDED]: { + roomId: string; + }; } /** @@ -53,4 +53,6 @@ export interface WebComponentEventPayloads { * @category Type Helpers * @private */ -export type WebComponentEventPayloadFor = T extends keyof WebComponentEventPayloads ? WebComponentEventPayloads[T] : never; +export type WebComponentEventPayloadFor = T extends keyof WebComponentEventPayloads + ? WebComponentEventPayloads[T] + : never; diff --git a/typings/src/webcomponent/message.type.ts b/typings/src/webcomponent/message.type.ts index 5b19815..654577e 100644 --- a/typings/src/webcomponent/message.type.ts +++ b/typings/src/webcomponent/message.type.ts @@ -5,7 +5,9 @@ import { WebComponentEventPayloadFor, WebComponentEvent } from './event.model.js * Represents all possible messages exchanged between the host application and WebComponent. * @category Communication */ -export type WebComponentMessage = WebComponentInboundCommandMessage | WebComponentOutboundEventMessage; +export type WebComponentMessage = + | WebComponentInboundCommandMessage + | WebComponentOutboundEventMessage; /** * Message sent from the host application to the WebComponent. @@ -13,10 +15,10 @@ export type WebComponentMessage = WebComponentInboundCommandMessage { - /** The command to execute in the WebComponent */ - command: T; - /** Optional payload with additional data for the command */ - payload?: WenComponentCommandPayloadFor; + /** The command to execute in the WebComponent */ + command: T; + /** Optional payload with additional data for the command */ + payload?: WenComponentCommandPayloadFor; } /** @@ -25,10 +27,10 @@ export interface WebComponentInboundCommandMessage { - /** The type of event being emitted */ - event: T; - /** Optional payload with additional data about the event */ - payload?: WebComponentEventPayloadFor; + /** The type of event being emitted */ + event: T; + /** Optional payload with additional data about the event */ + payload?: WebComponentEventPayloadFor; } /** @@ -40,10 +42,10 @@ export interface WebComponentOutboundEventMessage( - command: T, - payload?: WenComponentCommandPayloadFor + command: T, + payload?: WenComponentCommandPayloadFor ): WebComponentInboundCommandMessage { - return { command, payload }; + return { command, payload }; } /** @@ -55,8 +57,8 @@ export function createWebComponentCommandMessage( * @private */ export function createWebComponentEventMessage( - event: T, - payload?: WebComponentEventPayloadFor + event: T, + payload?: WebComponentEventPayloadFor ): WebComponentOutboundEventMessage { - return { event, payload }; + return { event, payload }; }