frontend: Improve webcomponent event and command types

This commit is contained in:
Carlos Santos 2025-05-07 16:31:55 +02:00
parent e75b21fa49
commit 326ee174c6
9 changed files with 194 additions and 69 deletions

View File

@ -25,7 +25,8 @@ import {
RoomService,
SessionStorageService
} from '../../services';
import { OpenViduMeetMessage, WebComponentEventType } from 'webcomponent/src/types/message.type';
import { OutboundEventMessage } from 'webcomponent/src/models/message.type';
import { WebComponentEvent } from 'webcomponent/src/models/event.model';
@Component({
selector: 'app-video-room',
@ -114,11 +115,11 @@ export class VideoRoomComponent implements OnInit, OnDestroy {
}
onParticipantConnected(event: ParticipantModel) {
const message: OpenViduMeetMessage = {
eventType: WebComponentEventType.LOCAL_PARTICIPANT_CONNECTED,
const message: OutboundEventMessage = {
event: WebComponentEvent.JOIN,
payload: {
roomId: event.getProperties().room?.name,
participantName: event.name
roomId: event.getProperties().room?.name || '',
participantName: event.name!
}
};
this.wcManagerService.sendMessageToParent(message);
@ -129,8 +130,8 @@ export class VideoRoomComponent implements OnInit, OnDestroy {
const redirectURL = this.ctxService.getLeaveRedirectURL() || '/disconnected';
const isExternalURL = /^https?:\/\//.test(redirectURL);
const message: OpenViduMeetMessage = {
eventType: WebComponentEventType.LOCAL_PARTICIPANT_LEFT,
const message: OutboundEventMessage = {
event: WebComponentEvent.LEFT,
payload: {
roomId: event.roomName,
participantName: event.participantName

View File

@ -8,12 +8,8 @@ import {
OpenViduService,
LoggerService
} from 'projects/shared-meet-components/src/public-api';
import {
OpenViduMeetMessage,
ParentMessage,
WebComponentActionType,
WebComponentEventType
} from 'webcomponent/src/types/message.type';
import { WebComponentCommand } from 'webcomponent/src/models/command.model';
import { OutboundEventMessage, InboundCommandMessage } from 'webcomponent/src/models/message.type';
/**
* Service to manage the commands from OpenVidu Meet WebComponent/Iframe.
@ -43,12 +39,12 @@ export class WebComponentManagerService {
this.isListenerStarted = true;
// Listen for messages from the iframe
window.addEventListener('message', async (event) => {
const message: ParentMessage = event.data;
const message: InboundCommandMessage = event.data;
const parentDomain = this.contextService.getParentDomain();
const { action, payload } = message;
const { command, payload } = message;
if (!parentDomain) {
if (action === WebComponentActionType.INITIALIZE) {
if (command === WebComponentCommand.INITIALIZE) {
if (!payload || !('domain' in payload)) {
console.error('Parent domain not provided in message payload');
return;
@ -66,19 +62,19 @@ export class WebComponentManagerService {
console.debug('Message received from parent:', event.data);
// TODO: reject if room is not connected
switch (action) {
case WebComponentActionType.END_MEETING:
switch (command) {
case WebComponentCommand.END_MEETING:
// Moderator only
if (this.contextService.isModeratorParticipant()) {
const roomId = this.contextService.getRoomId();
await this.httpService.endMeeting(roomId);
}
break;
case WebComponentActionType.TOGGLE_CHAT:
// Toggle chat
this.panelService.togglePanel(PanelType.CHAT);
break;
case WebComponentActionType.LEAVE_ROOM:
// case WebComponentCommand.TOGGLE_CHAT:
// Toggle chat
// this.panelService.togglePanel(PanelType.CHAT);
// break;
case WebComponentCommand.LEAVE_ROOM:
// Leave room.
await this.openviduService.disconnectRoom();
break;
@ -94,7 +90,7 @@ export class WebComponentManagerService {
window.removeEventListener('message', this.startCommandsListener);
}
sendMessageToParent(event: OpenViduMeetMessage /*| RoomDisconnectedEvent*/) {
sendMessageToParent(event: OutboundEventMessage) {
if (!this.contextService.isEmbeddedMode()) return;
this.log.d('Sending message to parent :', event);
const origin = this.contextService.getParentDomain();

View File

@ -1,4 +1,4 @@
import { ParentMessage } from '../types/message.type';
import { InboundCommandMessage } from '../models/message.type';
/**
* Handles sending messages to the iframe.
@ -12,7 +12,7 @@ export class CommandsManager {
this.allowedOrigin = allowedOrigin;
}
public sendMessage(message: ParentMessage, targetOrigin?: string): void {
public sendMessage(message: InboundCommandMessage, targetOrigin?: string): void {
targetOrigin = targetOrigin || this.allowedOrigin;
this.iframe.contentWindow?.postMessage(message, targetOrigin);
}

View File

@ -1,4 +1,4 @@
import { OpenViduMeetMessage } from '../types/message.type';
import { OutboundEventMessage } from '../models/message.type';
export class EventsManager {
private element: HTMLElement;
@ -12,9 +12,9 @@ export class EventsManager {
}
private handleMessage(event: MessageEvent) {
const message: OpenViduMeetMessage = event.data;
const message: OutboundEventMessage = event.data;
// Validate message origin (security measure)
if (!message || !message.eventType) {
if (!message || !message.event) {
// console.warn('Invalid message:', message);
return;
}
@ -22,8 +22,8 @@ export class EventsManager {
this.dispatchEvent(message);
}
private dispatchEvent(message: OpenViduMeetMessage) {
const event = new CustomEvent(message.eventType, {
private dispatchEvent(message: OutboundEventMessage) {
const event = new CustomEvent(message.event, {
detail: message.payload,
bubbles: true,
composed: true

View File

@ -1,4 +1,5 @@
import { ParentMessage, WebComponentActionType } from '../types/message.type';
import { WebComponentCommand } from '../models/command.model';
import { InboundCommandMessage } from '../models/message.type';
import { CommandsManager } from './CommandsManager';
import { EventsManager } from './EventsManager';
@ -68,8 +69,8 @@ export class OpenViduMeet extends HTMLElement {
this.shadowRoot?.appendChild(style);
this.shadowRoot?.appendChild(this.iframe);
this.iframe.onload = () => {
const message: ParentMessage = {
action: WebComponentActionType.INITIALIZE,
const message: InboundCommandMessage = {
command: WebComponentCommand.INITIALIZE,
payload: { domain: window.location.origin }
};
this.commandsManager.sendMessage(message);
@ -101,17 +102,17 @@ export class OpenViduMeet extends HTMLElement {
// Public methods
public endMeeting() {
const message: ParentMessage = { action: WebComponentActionType.END_MEETING };
const message: InboundCommandMessage = { command: WebComponentCommand.END_MEETING };
this.commandsManager.sendMessage(message);
}
public leaveRoom() {
const message: ParentMessage = { action: WebComponentActionType.LEAVE_ROOM };
const message: InboundCommandMessage = { command: WebComponentCommand.LEAVE_ROOM };
this.commandsManager.sendMessage(message);
}
public toggleChat() {
const message: ParentMessage = { action: WebComponentActionType.TOGGLE_CHAT };
this.commandsManager.sendMessage(message);
}
// public toggleChat() {
// const message: ParentMessage = { action: WebComponentActionType.TOGGLE_CHAT };
// this.commandsManager.sendMessage(message);
// }
}

View File

@ -0,0 +1,49 @@
/**
* All available commands that can be sent to the WebComponent.
*/
export enum WebComponentCommand {
/**
* Initializes the WebComponent with the given configuration.
* @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'
}
/**
* Type definitions for command payloads.
* Each property corresponds to a command in {@link WebComponentCommand}.
* @category Communication
*/
export interface CommandPayloads {
/**
* Payload for the INITIALIZE command.
* @private
*/
[WebComponentCommand.INITIALIZE]: {
domain: string;
};
[WebComponentCommand.END_MEETING]: void;
[WebComponentCommand.LEAVE_ROOM]: void;
// [WebComponentCommand.TOGGLE_CHAT]: void;
}
/**
* Gets the type-safe payload for a specific command.
* This type allows TypeScript to infer the correct payload type based on the command.
* @category Type Helpers
* @private
*/
export type CommandPayloadFor<T extends WebComponentCommand> = T extends keyof CommandPayloads
? CommandPayloads[T]
: never;

View File

@ -0,0 +1,45 @@
/**
* All available events that can be emitted by the WebComponent.
* @category Communication
*/
export enum WebComponentEvent {
/**
* 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'
}
/**
* Type definitions for event payloads.
* Each property corresponds to an event in {@link WebComponentEvent}.
* @category Communication
*/
export interface EventPayloads {
[WebComponentEvent.JOIN]: {
roomId: string;
participantName: string;
};
[WebComponentEvent.LEFT]: {
roomId: string;
participantName: string;
};
[WebComponentEvent.MEETING_ENDED]: {
roomId: string;
};
}
/**
* Gets the type-safe payload for a specific event.
* This type allows TypeScript to infer the correct payload type based on the event.
* @category Type Helpers
* @private
*/
export type EventPayloadFor<T extends WebComponentEvent> = T extends keyof EventPayloads ? EventPayloads[T] : never;

View File

@ -0,0 +1,62 @@
import { CommandPayloadFor, WebComponentCommand } from './command.model';
import { EventPayloadFor, WebComponentEvent } from './event.model';
/**
* Represents all possible messages exchanged between the host application and WebComponent.
* @category Communication
*/
export type WebComponentMessage = InboundCommandMessage<WebComponentCommand> | OutboundEventMessage<WebComponentEvent>;
/**
* Message sent from the host application to the WebComponent.
* Contains a command with an optional type-safe payload.
* @category Communication
*/
export interface InboundCommandMessage<T extends WebComponentCommand = WebComponentCommand> {
/** The command to execute in the WebComponent */
command: T;
/** Optional payload with additional data for the command */
payload?: CommandPayloadFor<T>;
}
/**
* Message sent from the WebComponent to the host application.
* Contains an event type with an optional type-safe payload.
* @category Communication
*/
export interface OutboundEventMessage<T extends WebComponentEvent = WebComponentEvent> {
/** The type of event being emitted */
event: T;
/** Optional payload with additional data about the event */
payload?: EventPayloadFor<T>;
}
/**
* Helper function to create a properly typed command message.
* @param command The command to send
* @param payload The payload for the command
* @returns A properly formatted command message
* @category Utilities
* @private
*/
export function createCommandMessage<T extends WebComponentCommand>(
command: T,
payload?: CommandPayloadFor<T>
): InboundCommandMessage<T> {
return { command, payload };
}
/**
* Helper function to create a properly typed event message.
* @param event The event type
* @param payload The payload for the event
* @returns A properly formatted event message
* @category Utilities
* @private
*/
export function createEventMessage<T extends WebComponentEvent>(
event: T,
payload?: EventPayloadFor<T>
): OutboundEventMessage<T> {
return { event, payload };
}

View File

@ -1,29 +0,0 @@
export type WebComponentMessage = ParentMessage | OpenViduMeetMessage;
/**
* Message sent from the parent to the OpenViduMeet component.
*/
export interface ParentMessage {
action: WebComponentActionType;
payload?: Record<string, any>;
}
/**
* Message sent from the OpenViduMeet component to the parent.
*/
export interface OpenViduMeetMessage {
eventType: WebComponentEventType;
payload?: Record<string, unknown>;
}
export enum WebComponentActionType {
INITIALIZE = 'initialize',
END_MEETING = 'endMeeting',
LEAVE_ROOM = 'leaveRoom',
TOGGLE_CHAT = 'toggleChat'
}
export enum WebComponentEventType {
LOCAL_PARTICIPANT_CONNECTED = 'join',
LOCAL_PARTICIPANT_LEFT = 'left'
}