webcomponent: enhance CommandsManager and OpenViduMeet for improved event handling and initialization
This commit is contained in:
parent
233ec74871
commit
2fa5a53d24
@ -1,23 +1,163 @@
|
|||||||
|
import { WebComponentCommand } from '../models/command.model';
|
||||||
|
import { WebComponentEvent } from '../models/event.model';
|
||||||
import { InboundCommandMessage } from '../models/message.type';
|
import { InboundCommandMessage } from '../models/message.type';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles sending messages to the iframe.
|
* Manages communication between a parent application and an embedded iframe
|
||||||
|
* for the OpenVidu Meet web component. The `CommandsManager` facilitates sending commands to the iframe,
|
||||||
|
* subscribing to and unsubscribing from custom events, and managing allowed origins for secure messaging.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
*/
|
*/
|
||||||
export class CommandsManager {
|
export class CommandsManager {
|
||||||
private iframe: HTMLIFrameElement;
|
private iframe: HTMLIFrameElement;
|
||||||
private allowedOrigin: string;
|
private allowedOrigin: string;
|
||||||
|
/**
|
||||||
|
* A map to store event handlers for different events.
|
||||||
|
* This allows for dynamic event handling and can be used to add or remove event listeners.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type {Map<string, Set<Function>>}
|
||||||
|
*/
|
||||||
|
private eventHandlers: Map<string, Set<Function>> = new Map();
|
||||||
|
|
||||||
constructor(iframe: HTMLIFrameElement, allowedOrigin: string) {
|
constructor(iframe: HTMLIFrameElement, allowedOrigin: string) {
|
||||||
this.iframe = iframe;
|
this.iframe = iframe;
|
||||||
this.allowedOrigin = allowedOrigin;
|
this.allowedOrigin = allowedOrigin;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sendMessage(message: InboundCommandMessage, targetOrigin?: string): void {
|
/**
|
||||||
targetOrigin = targetOrigin || this.allowedOrigin;
|
* Initializes the command manager by sending an `INITIALIZE` command message.
|
||||||
this.iframe.contentWindow?.postMessage(message, targetOrigin);
|
* The message payload includes the current domain (`window.location.origin`).
|
||||||
|
* This method is typically called to set up the initial state or configuration
|
||||||
|
* required for the web component to function properly.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
public initialize() {
|
||||||
|
const message: InboundCommandMessage = {
|
||||||
|
command: WebComponentCommand.INITIALIZE,
|
||||||
|
payload: { domain: window.location.origin }
|
||||||
|
};
|
||||||
|
this.sendMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to an event
|
||||||
|
* @param eventName Name of the event to listen for
|
||||||
|
* @param callback Function to be called when the event is triggered
|
||||||
|
* @returns The component instance for chaining
|
||||||
|
*/
|
||||||
|
public on(element: HTMLElement, eventName: string, callback: (detail: any) => void): this {
|
||||||
|
if (!(Object.values(WebComponentEvent) as string[]).includes(eventName)) {
|
||||||
|
console.warn(`Event "${eventName}" is not supported.`);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create event listener that will call the callback
|
||||||
|
const listener = ((event: CustomEvent) => {
|
||||||
|
callback(event.detail);
|
||||||
|
}) as EventListener;
|
||||||
|
|
||||||
|
// Store reference to original callback for off() method
|
||||||
|
if (!this.eventHandlers.has(eventName)) {
|
||||||
|
this.eventHandlers.set(eventName, new Set());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store both the callback and listener to match them later
|
||||||
|
const handlers = this.eventHandlers.get(eventName);
|
||||||
|
// @ts-ignore - To store both values together
|
||||||
|
callback._listener = listener;
|
||||||
|
handlers?.add(callback);
|
||||||
|
|
||||||
|
// Register with standard DOM API
|
||||||
|
|
||||||
|
element.addEventListener(eventName, listener);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to an event that will be triggered only once
|
||||||
|
* @param eventName Name of the event to listen for
|
||||||
|
* @param callback Function to be called when the event is triggered
|
||||||
|
* @returns The component instance for chaining
|
||||||
|
*/
|
||||||
|
public once(element: HTMLElement, eventName: string, callback: (detail: any) => void): this {
|
||||||
|
if (!(Object.values(WebComponentEvent) as string[]).includes(eventName)) {
|
||||||
|
console.warn(`Event "${eventName}" is not supported.`);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a wrapper that will call the callback and then unsubscribe
|
||||||
|
const wrapperCallback = (detail: any) => {
|
||||||
|
// Unsubscribe first to prevent any possibility of duplicate calls
|
||||||
|
this.off(element, eventName, wrapperCallback);
|
||||||
|
// Call the original callback
|
||||||
|
callback(detail);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.on(element, eventName, wrapperCallback);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe from an event
|
||||||
|
* @param eventName Name of the event to stop listening for
|
||||||
|
* @param callback Optional callback to remove (if not provided, removes all handlers for this event)
|
||||||
|
* @returns The component instance for chaining
|
||||||
|
*/
|
||||||
|
public off(element: HTMLElement, eventName: string, callback?: (detail: any) => void): this {
|
||||||
|
if (!callback) {
|
||||||
|
// Remove all handlers for this event
|
||||||
|
const handlers = this.eventHandlers.get(eventName);
|
||||||
|
if (handlers) {
|
||||||
|
handlers.forEach((handler) => {
|
||||||
|
// @ts-ignore - To match the stored listener
|
||||||
|
element.removeEventListener(eventName, handler._listener);
|
||||||
|
});
|
||||||
|
handlers.clear();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Remove specific handler
|
||||||
|
const handlers = this.eventHandlers.get(eventName);
|
||||||
|
if (handlers && handlers.has(callback)) {
|
||||||
|
// @ts-ignore - To match the stored listener
|
||||||
|
element.removeEventListener(eventName, callback._listener);
|
||||||
|
handlers.delete(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public endMeeting() {
|
||||||
|
const message: InboundCommandMessage = { command: WebComponentCommand.END_MEETING };
|
||||||
|
this.sendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public leaveRoom() {
|
||||||
|
const message: InboundCommandMessage = { command: WebComponentCommand.LEAVE_ROOM };
|
||||||
|
this.sendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// public toggleChat() {
|
||||||
|
// const message: ParentMessage = { action: WebComponentActionType.TOGGLE_CHAT };
|
||||||
|
// this.commandsManager.sendMessage(message);
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the allowed origin for the current instance.
|
||||||
|
*
|
||||||
|
* @param newOrigin - The new origin to be set as allowed.
|
||||||
|
*/
|
||||||
public setAllowedOrigin(newOrigin: string): void {
|
public setAllowedOrigin(newOrigin: string): void {
|
||||||
this.allowedOrigin = newOrigin;
|
this.allowedOrigin = newOrigin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sendMessage(message: InboundCommandMessage, targetOrigin?: string): void {
|
||||||
|
targetOrigin = targetOrigin || this.allowedOrigin;
|
||||||
|
this.iframe.contentWindow?.postMessage(message, targetOrigin);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,14 +34,6 @@ export class OpenViduMeet extends HTMLElement {
|
|||||||
private loadTimeout: any;
|
private loadTimeout: any;
|
||||||
private iframeLoaded = false;
|
private iframeLoaded = false;
|
||||||
private errorMessage: string | null = null;
|
private errorMessage: string | null = null;
|
||||||
/**
|
|
||||||
* A map to store event handlers for different events.
|
|
||||||
* This allows for dynamic event handling and can be used to add or remove event listeners.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @type {Map<string, Set<Function>>}
|
|
||||||
*/
|
|
||||||
private eventHandlers: Map<string, Set<Function>> = new Map();
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@ -63,13 +55,7 @@ export class OpenViduMeet extends HTMLElement {
|
|||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
// Send initialization message to the iframe
|
// Send initialization message to the iframe
|
||||||
// after READY event from the iframe is received
|
// after READY event from the iframe is received
|
||||||
this.once(WebComponentEvent.READY, () => {
|
this.once(WebComponentEvent.READY, () => this.commandsManager.initialize());
|
||||||
const message: InboundCommandMessage = {
|
|
||||||
command: WebComponentCommand.INITIALIZE,
|
|
||||||
payload: { domain: window.location.origin }
|
|
||||||
};
|
|
||||||
this.commandsManager.sendMessage(message);
|
|
||||||
});
|
|
||||||
this.eventsManager.listen();
|
this.eventsManager.listen();
|
||||||
this.render();
|
this.render();
|
||||||
this.updateIframeSrc();
|
this.updateIframeSrc();
|
||||||
@ -179,6 +165,9 @@ export class OpenViduMeet extends HTMLElement {
|
|||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- WebComponent Commands ----
|
||||||
|
// These methods send commands to the iframe.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribe to an event
|
* Subscribe to an event
|
||||||
* @param eventName Name of the event to listen for
|
* @param eventName Name of the event to listen for
|
||||||
@ -186,31 +175,7 @@ export class OpenViduMeet extends HTMLElement {
|
|||||||
* @returns The component instance for chaining
|
* @returns The component instance for chaining
|
||||||
*/
|
*/
|
||||||
public on(eventName: string, callback: (detail: any) => void): this {
|
public on(eventName: string, callback: (detail: any) => void): this {
|
||||||
if (!(Object.values(WebComponentEvent) as string[]).includes(eventName)) {
|
this.commandsManager.on(this, eventName, callback);
|
||||||
console.warn(`Event "${eventName}" is not supported.`);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create event listener that will call the callback
|
|
||||||
const listener = ((event: CustomEvent) => {
|
|
||||||
callback(event.detail);
|
|
||||||
}) as EventListener;
|
|
||||||
|
|
||||||
// Store reference to original callback for off() method
|
|
||||||
if (!this.eventHandlers.has(eventName)) {
|
|
||||||
this.eventHandlers.set(eventName, new Set());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store both the callback and listener to match them later
|
|
||||||
const handlers = this.eventHandlers.get(eventName);
|
|
||||||
// @ts-ignore - To store both values together
|
|
||||||
callback._listener = listener;
|
|
||||||
handlers?.add(callback);
|
|
||||||
|
|
||||||
// Register with standard DOM API
|
|
||||||
|
|
||||||
this.addEventListener(eventName, listener);
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,21 +186,7 @@ export class OpenViduMeet extends HTMLElement {
|
|||||||
* @returns The component instance for chaining
|
* @returns The component instance for chaining
|
||||||
*/
|
*/
|
||||||
public once(eventName: string, callback: (detail: any) => void): this {
|
public once(eventName: string, callback: (detail: any) => void): this {
|
||||||
if (!(Object.values(WebComponentEvent) as string[]).includes(eventName)) {
|
this.commandsManager.once(this, eventName, callback);
|
||||||
console.warn(`Event "${eventName}" is not supported.`);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a wrapper that will call the callback and then unsubscribe
|
|
||||||
const wrapperCallback = (detail: any) => {
|
|
||||||
// Unsubscribe first to prevent any possibility of duplicate calls
|
|
||||||
this.off(eventName, wrapperCallback);
|
|
||||||
// Call the original callback
|
|
||||||
callback(detail);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.on(eventName, wrapperCallback);
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,44 +197,22 @@ export class OpenViduMeet extends HTMLElement {
|
|||||||
* @returns The component instance for chaining
|
* @returns The component instance for chaining
|
||||||
*/
|
*/
|
||||||
public off(eventName: string, callback?: (detail: any) => void): this {
|
public off(eventName: string, callback?: (detail: any) => void): this {
|
||||||
if (!callback) {
|
this.commandsManager.off(this, eventName, callback);
|
||||||
// Remove all handlers for this event
|
|
||||||
const handlers = this.eventHandlers.get(eventName);
|
|
||||||
if (handlers) {
|
|
||||||
handlers.forEach((handler) => {
|
|
||||||
// @ts-ignore - Access stored listener
|
|
||||||
this.removeEventListener(eventName, handler._listener);
|
|
||||||
});
|
|
||||||
handlers.clear();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Remove specific handler
|
|
||||||
const handlers = this.eventHandlers.get(eventName);
|
|
||||||
if (handlers && handlers.has(callback)) {
|
|
||||||
// @ts-ignore - Access stored listener
|
|
||||||
this.removeEventListener(eventName, callback._listener);
|
|
||||||
handlers.delete(callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- WebComponent Commands ----
|
/**
|
||||||
// These methods send commands to the OpenVidu Meet iframe.
|
* Ends the current meeting by delegating the action to the commands manager.
|
||||||
|
* This method should be called when the user wants to terminate the ongoing session.
|
||||||
|
*/
|
||||||
public endMeeting() {
|
public endMeeting() {
|
||||||
const message: InboundCommandMessage = { command: WebComponentCommand.END_MEETING };
|
this.commandsManager.endMeeting();
|
||||||
this.commandsManager.sendMessage(message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Leaves the current video conference room.
|
||||||
|
*/
|
||||||
public leaveRoom() {
|
public leaveRoom() {
|
||||||
const message: InboundCommandMessage = { command: WebComponentCommand.LEAVE_ROOM };
|
this.commandsManager.leaveRoom();
|
||||||
this.commandsManager.sendMessage(message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// public toggleChat() {
|
|
||||||
// const message: ParentMessage = { action: WebComponentActionType.TOGGLE_CHAT };
|
|
||||||
// this.commandsManager.sendMessage(message);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user