testapp: enhance webhook event handling and storage; update room ID handling in forms

This commit is contained in:
Carlos Santos 2025-05-12 12:57:33 +02:00
parent 8a6066a87c
commit 6a8bae5dec
6 changed files with 168 additions and 92 deletions

View File

@ -7,7 +7,7 @@
"build": "tsc", "build": "tsc",
"build:watch": "tsc --watch", "build:watch": "tsc --watch",
"start": "node dist/index.js", "start": "node dist/index.js",
"dev:server": "ts-node-dev --respawn --watch src,public/ts src/index.ts", "dev:server": "ts-node-dev --respawn --watch src,public/ts,public/views src/index.ts",
"dev": "concurrently \"npm:watch:client\" \"npm run dev:server\" --kill-others", "dev": "concurrently \"npm:watch:client\" \"npm run dev:server\" --kill-others",
"build:client": "tsc -p tsconfig.client.json", "build:client": "tsc -p tsconfig.client.json",
"watch:client": "tsc -p tsconfig.client.json --watch" "watch:client": "tsc -p tsconfig.client.json --watch"

View File

@ -7,6 +7,7 @@ const addEventToLog = (eventType: string, eventMessage: string): void => {
const eventsList = document.getElementById('events-list'); const eventsList = document.getElementById('events-list');
if (eventsList) { if (eventsList) {
const li = document.createElement('li'); const li = document.createElement('li');
li.className = `event-${eventType}`;
li.textContent = `[ ${eventType} ] : ${eventMessage}`; li.textContent = `[ ${eventType} ] : ${eventMessage}`;
eventsList.appendChild(li); eventsList.appendChild(li);
} }
@ -19,14 +20,54 @@ const escapeHtml = (unsafe: string): string => {
.replace(/"/g, '"') .replace(/"/g, '"')
.replace(/'/g, '''); .replace(/'/g, ''');
}; };
const getWebhookEventsFromStorage = (roomId: string): any[] => {
const data = localStorage.getItem('webhookEventsByRoom');
if (!data) return [];
const map = JSON.parse(data);
return map[roomId] || [];
};
const saveWebhookEventToStorage = (roomId: string, event: any): void => {
const data = localStorage.getItem('webhookEventsByRoom');
const map = data ? JSON.parse(data) : {};
if (!map[roomId]) map[roomId] = [];
map[roomId].push(event);
localStorage.setItem('webhookEventsByRoom', JSON.stringify(map));
};
const clearWebhookEventsByRoom = (roomId: string): void => {
const data = localStorage.getItem('webhookEventsByRoom');
if (!data) return;
const map = JSON.parse(data);
if (map[roomId]) {
map[roomId] = [];
localStorage.setItem('webhookEventsByRoom', JSON.stringify(map));
}
};
const listenWebhookServerEvents = () => { const listenWebhookServerEvents = () => {
socket.on('webhookEvent', (payload: any) => { socket.on('webhookEvent', (event: any) => {
console.log('Webhook received:', payload); console.log('Webhook received:', event);
const isMeetingEnded = event.event === 'meetingEnded';
const roomId = event.data.roomId;
if (roomId) {
saveWebhookEventToStorage(roomId, event);
}
addWebhookEventElement(event);
// Clean up the previous events
if (isMeetingEnded) clearWebhookEventsByRoom(roomId);
});
};
const addWebhookEventElement = (event: any) => {
const webhookLogList = document.getElementById('webhook-log-list'); const webhookLogList = document.getElementById('webhook-log-list');
if (webhookLogList) { if (webhookLogList) {
// Create unique IDs for this accordion item // Create unique IDs for this accordion item
const itemId = payload.creationDate; const itemId = event.creationDate;
const headerId = `header-${itemId}`; const headerClassName = `webhook-${event.event}`;
const collapseId = `collapse-${itemId}`; const collapseId = `collapse-${itemId}`;
// Create accordion item container // Create accordion item container
@ -35,8 +76,7 @@ const listenWebhookServerEvents = () => {
// Create header // Create header
const header = document.createElement('h2'); const header = document.createElement('h2');
header.className = 'accordion-header'; header.classList.add(headerClassName, 'accordion-header');
header.id = headerId;
// Create header button // Create header button
const button = document.createElement('button'); const button = document.createElement('button');
@ -48,17 +88,17 @@ const listenWebhookServerEvents = () => {
button.setAttribute('aria-controls', collapseId); button.setAttribute('aria-controls', collapseId);
button.style.padding = '10px'; button.style.padding = '10px';
if (payload.event === 'meetingStarted') { if (event.event === 'meetingStarted') {
button.classList.add('bg-success'); button.classList.add('bg-success');
} }
if (payload.event === 'meetingEnded') { if (event.event === 'meetingEnded') {
button.classList.add('bg-danger'); button.classList.add('bg-danger');
} }
if (payload.event.includes('recording')) { if (event.event.includes('recording')) {
button.classList.add('bg-warning'); button.classList.add('bg-warning');
} }
// Format the header text with event name and timestamp // Format the header text with event name and timestamp
const date = new Date(payload.creationDate); const date = new Date(event.creationDate);
const formattedDate = date.toLocaleString('es-ES', { const formattedDate = date.toLocaleString('es-ES', {
// year: 'numeric', // year: 'numeric',
@ -69,13 +109,13 @@ const listenWebhookServerEvents = () => {
second: '2-digit', second: '2-digit',
hour12: false, hour12: false,
}); });
button.innerHTML = `[${formattedDate}] <strong>${payload.event}</strong>`; button.innerHTML = `[${formattedDate}] <strong>${event.event}</strong>`;
// Create collapsible content container // Create collapsible content container
const collapseDiv = document.createElement('div'); const collapseDiv = document.createElement('div');
collapseDiv.id = collapseId; collapseDiv.id = collapseId;
collapseDiv.className = 'accordion-collapse collapse'; collapseDiv.className = 'accordion-collapse collapse';
collapseDiv.setAttribute('aria-labelledby', headerId); collapseDiv.setAttribute('aria-labelledby', headerClassName);
collapseDiv.setAttribute('data-bs-parent', '#webhook-log-list'); collapseDiv.setAttribute('data-bs-parent', '#webhook-log-list');
// Create body content // Create body content
@ -83,7 +123,7 @@ const listenWebhookServerEvents = () => {
bodyDiv.className = 'accordion-body'; bodyDiv.className = 'accordion-body';
// Format JSON with syntax highlighting if possible // Format JSON with syntax highlighting if possible
const formattedJson = JSON.stringify(payload, null, 2); const formattedJson = JSON.stringify(event, null, 2);
bodyDiv.innerHTML = `<pre class="mb-0"><code>${escapeHtml( bodyDiv.innerHTML = `<pre class="mb-0"><code>${escapeHtml(
formattedJson formattedJson
)}</code></pre>`; )}</code></pre>`;
@ -107,7 +147,6 @@ const listenWebhookServerEvents = () => {
webhookLogList.removeChild(webhookLogList.lastChild!); webhookLogList.removeChild(webhookLogList.lastChild!);
} }
} }
});
}; };
// Listen to events from openvidu-meet // Listen to events from openvidu-meet
@ -156,6 +195,14 @@ const setUpWebComponentCommands = () => {
}; };
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const roomId = document.getElementById('room-id')?.textContent?.trim();
if (!roomId) {
console.error('Room ID not found in the DOM');
alert('Room ID not found in the DOM');
return;
}
const events = getWebhookEventsFromStorage(roomId);
events.forEach((event) => addWebhookEventElement(event));
listenWebhookServerEvents(); listenWebhookServerEvents();
listenWebComponentEvents(); listenWebComponentEvents();
setUpWebComponentCommands(); setUpWebComponentCommands();

View File

@ -50,6 +50,11 @@
name="participantRole" name="participantRole"
value="moderator" value="moderator"
/> />
<input
type="hidden"
name="roomId"
value="{{ roomId }}"
/>
<button type="submit" id="join-as-moderator" class="dropdown-item"> <button type="submit" id="join-as-moderator" class="dropdown-item">
Moderator Moderator
@ -68,6 +73,11 @@
name="participantRole" name="participantRole"
value="publisher" value="publisher"
/> />
<input
type="hidden"
name="roomId"
value="{{ roomId }}"
/>
<button type="submit" id="join-as-publisher" class="dropdown-item"> <button type="submit" id="join-as-publisher" class="dropdown-item">
Publisher Publisher
@ -93,7 +103,7 @@
<form action="/room" method="post"> <form action="/room" method="post">
<div class="mb-3"> <div class="mb-3">
<label for="room-id-prefix" class="form-label">Room Prefix</label> <label for="room-id-prefix" class="form-label">Room Prefix *</label>
<input <input
type="text" type="text"
name="roomIdPrefix" name="roomIdPrefix"

View File

@ -17,14 +17,30 @@
<div class="container-fluid"> <div class="container-fluid">
<!-- Left Sidebar Panel --> <!-- Left Sidebar Panel -->
<div id="control-panel" class="d-flex flex-column"> <div id="control-panel" class="d-flex flex-column">
<h3>{{ participantRole }}</h3> <span>
<span id="participant-role" class="h5">
{{#isModerator}}<span class="badge bg-success">Moderator</span
>{{/isModerator}}
{{^isModerator
}}<span class="badge bg-secondary">Participant</span
>{{/isModerator}}
</span>
<span id="room-id" class="text-muted">
{{ roomId }}
</span>
</span>
<!-- Commands --> <!-- Commands -->
<div class="section"> <div class="section">
<h5 class="title">Commands</h5> <h5 class="title">Commands</h5>
{{#isModerator}} {{#isModerator}}
<button id="end-meeting-btn" class="btn btn-danger">End Meeting</button> <button id="end-meeting-btn" class="btn btn-danger">
End Meeting
</button>
{{/isModerator}} {{/isModerator}}
<button id="leave-room-btn" class="btn btn-warning">Leave Room</button> <button id="leave-room-btn" class="btn btn-warning">
Leave Room
</button>
<button id="toggle-chat-btn" class="btn btn-success"> <button id="toggle-chat-btn" class="btn btn-success">
Toggle Chat Toggle Chat
</button> </button>

View File

@ -7,6 +7,7 @@ import { MeetWebhookEvent } from '../../../typings/src/webhook.model';
interface JoinRoomRequest { interface JoinRoomRequest {
participantRole: ParticipantRole; participantRole: ParticipantRole;
roomUrl: string; roomUrl: string;
roomId: string;
participantName?: string; participantName?: string;
} }
@ -15,6 +16,7 @@ export const joinRoom = (req: Request, res: Response) => {
const { const {
participantRole, participantRole,
roomUrl, roomUrl,
roomId,
participantName = 'User', participantName = 'User',
} = req.body as JoinRoomRequest; } = req.body as JoinRoomRequest;
if (!roomUrl) { if (!roomUrl) {
@ -24,6 +26,7 @@ export const joinRoom = (req: Request, res: Response) => {
roomUrl, roomUrl,
participantRole, participantRole,
participantName, participantName,
roomId,
isModerator: participantRole === 'moderator', isModerator: participantRole === 'moderator',
}); });
} catch (error) { } catch (error) {

View File

@ -44,5 +44,5 @@ server.listen(PORT, () => {
console.log(`Meet API URL: ${configService.meetApiUrl}`); console.log(`Meet API URL: ${configService.meetApiUrl}`);
console.log('-----------------------------------------'); console.log('-----------------------------------------');
console.log(''); console.log('');
console.log('Visit http://localhost:3000/ to access the app'); console.log(`Visit http://localhost:${PORT}/ to access the app`);
}); });