testapp: add kick participant functionality and fix view recordings link
- Enhanced the web component to include a kick participant feature. - Updated the UI to allow moderators to input participant identity for kicking. - Modified the room controller to handle the new kick participant request. - Refactored code for better readability and maintainability across various services and controllers. - Updated the index.mustache and room.mustache templates to reflect changes in functionality.
This commit is contained in:
parent
0a028226f3
commit
2b7466c6e3
@ -1,9 +1,10 @@
|
|||||||
const socket = (window as any).io();
|
const socket = (window as any).io();
|
||||||
|
|
||||||
let meet: {
|
let meet: {
|
||||||
endMeeting: () => void;
|
endMeeting: () => void;
|
||||||
leaveRoom: () => void;
|
leaveRoom: () => void;
|
||||||
on: (event: string, callback: (event: CustomEvent<any>) => void) => void;
|
kickParticipant: (participantIdentity: string) => void;
|
||||||
|
on: (event: string, callback: (event: CustomEvent<any>) => void) => void;
|
||||||
};
|
};
|
||||||
let roomId: string | undefined;
|
let roomId: string | undefined;
|
||||||
let showAllWebhooksCheckbox: HTMLInputElement | null;
|
let showAllWebhooksCheckbox: HTMLInputElement | null;
|
||||||
@ -12,238 +13,243 @@ let showAllWebhooksCheckbox: HTMLInputElement | null;
|
|||||||
* Add a component event to the events log
|
* Add a component event to the events log
|
||||||
*/
|
*/
|
||||||
const addEventToLog = (eventType: string, eventMessage: string): void => {
|
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.className = `event-${eventType}`;
|
||||||
li.textContent = `[ ${eventType} ] : ${eventMessage}`;
|
li.textContent = `[ ${eventType} ] : ${eventMessage}`;
|
||||||
eventsList.insertBefore(li, eventsList.firstChild);
|
eventsList.insertBefore(li, eventsList.firstChild);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const escapeHtml = (unsafe: string): string => {
|
const escapeHtml = (unsafe: string): string => {
|
||||||
return unsafe
|
return unsafe
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, '&')
|
||||||
.replace(/</g, '<')
|
.replace(/</g, '<')
|
||||||
.replace(/>/g, '>')
|
.replace(/>/g, '>')
|
||||||
.replace(/"/g, '"')
|
.replace(/"/g, '"')
|
||||||
.replace(/'/g, ''');
|
.replace(/'/g, ''');
|
||||||
};
|
};
|
||||||
|
|
||||||
const getWebhookEventsFromStorage = (roomId: string): any[] => {
|
const getWebhookEventsFromStorage = (roomId: string): any[] => {
|
||||||
const data = localStorage.getItem('webhookEventsByRoom');
|
const data = localStorage.getItem('webhookEventsByRoom');
|
||||||
if (!data) return [];
|
if (!data) {
|
||||||
const map = JSON.parse(data);
|
return [];
|
||||||
return map[roomId] || [];
|
}
|
||||||
|
|
||||||
|
const map = JSON.parse(data);
|
||||||
|
return map[roomId] || [];
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveWebhookEventToStorage = (roomId: string, event: any): void => {
|
const saveWebhookEventToStorage = (roomId: string, event: any): void => {
|
||||||
const data = localStorage.getItem('webhookEventsByRoom');
|
const data = localStorage.getItem('webhookEventsByRoom');
|
||||||
const map = data ? JSON.parse(data) : {};
|
const map = data ? JSON.parse(data) : {};
|
||||||
if (!map[roomId]) map[roomId] = [];
|
if (!map[roomId]) {
|
||||||
map[roomId].push(event);
|
map[roomId] = [];
|
||||||
localStorage.setItem('webhookEventsByRoom', JSON.stringify(map));
|
}
|
||||||
|
|
||||||
|
map[roomId].push(event);
|
||||||
|
localStorage.setItem('webhookEventsByRoom', JSON.stringify(map));
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearWebhookEventsByRoom = (roomId: string): void => {
|
const clearWebhookEventsByRoom = (roomId: string): void => {
|
||||||
const data = localStorage.getItem('webhookEventsByRoom');
|
const data = localStorage.getItem('webhookEventsByRoom');
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
const map = JSON.parse(data);
|
|
||||||
if (map[roomId]) {
|
const map = JSON.parse(data);
|
||||||
map[roomId] = [];
|
if (map[roomId]) {
|
||||||
localStorage.setItem('webhookEventsByRoom', JSON.stringify(map));
|
map[roomId] = [];
|
||||||
}
|
localStorage.setItem('webhookEventsByRoom', JSON.stringify(map));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const shouldShowWebhook = (event: any): boolean => {
|
const shouldShowWebhook = (event: any): boolean => {
|
||||||
return showAllWebhooksCheckbox?.checked || event.data.roomId === roomId;
|
return showAllWebhooksCheckbox?.checked || event.data.roomId === roomId;
|
||||||
};
|
};
|
||||||
|
|
||||||
const listenWebhookServerEvents = () => {
|
const listenWebhookServerEvents = () => {
|
||||||
socket.on('webhookEvent', (event: any) => {
|
socket.on('webhookEvent', (event: any) => {
|
||||||
console.log('Webhook received:', event);
|
console.log('Webhook received:', event);
|
||||||
const webhookRoomId = event.data.roomId;
|
const webhookRoomId = event.data.roomId;
|
||||||
|
|
||||||
if (webhookRoomId) {
|
if (webhookRoomId) {
|
||||||
saveWebhookEventToStorage(webhookRoomId, event);
|
saveWebhookEventToStorage(webhookRoomId, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shouldShowWebhook(event)) {
|
if (!shouldShowWebhook(event)) {
|
||||||
console.log('Ignoring webhook event:', event);
|
console.log('Ignoring webhook event:', event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
addWebhookEventElement(event);
|
addWebhookEventElement(event);
|
||||||
|
|
||||||
// Clean up the previous events
|
// Clean up the previous events
|
||||||
const isMeetingEnded = event.event === 'meetingEnded';
|
const isMeetingEnded = event.event === 'meetingEnded';
|
||||||
if (isMeetingEnded) clearWebhookEventsByRoom(webhookRoomId);
|
if (isMeetingEnded) clearWebhookEventsByRoom(webhookRoomId);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderStoredWebhookEvents = (roomId: string) => {
|
const renderStoredWebhookEvents = (roomId: string) => {
|
||||||
const webhookLogList = document.getElementById('webhook-log-list');
|
const webhookLogList = document.getElementById('webhook-log-list');
|
||||||
if (webhookLogList) {
|
if (webhookLogList) {
|
||||||
while (webhookLogList.firstChild) {
|
while (webhookLogList.firstChild) {
|
||||||
webhookLogList.removeChild(webhookLogList.firstChild);
|
webhookLogList.removeChild(webhookLogList.firstChild);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const events = getWebhookEventsFromStorage(roomId);
|
|
||||||
events.forEach((event) => addWebhookEventElement(event));
|
const events = getWebhookEventsFromStorage(roomId);
|
||||||
|
events.forEach((event) => addWebhookEventElement(event));
|
||||||
};
|
};
|
||||||
|
|
||||||
const addWebhookEventElement = (event: any) => {
|
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 = event.creationDate;
|
const itemId = event.creationDate;
|
||||||
const headerClassName = `webhook-${event.event}`;
|
const headerClassName = `webhook-${event.event}`;
|
||||||
const collapseId = `collapse-${itemId}`;
|
const collapseId = `collapse-${itemId}`;
|
||||||
|
|
||||||
// Create accordion item container
|
// Create accordion item container
|
||||||
const accordionItem = document.createElement('div');
|
const accordionItem = document.createElement('div');
|
||||||
accordionItem.className = 'accordion-item';
|
accordionItem.className = 'accordion-item';
|
||||||
|
|
||||||
// Create header
|
// Create header
|
||||||
const header = document.createElement('h2');
|
const header = document.createElement('h2');
|
||||||
header.classList.add(headerClassName, 'accordion-header');
|
header.classList.add(headerClassName, 'accordion-header');
|
||||||
|
|
||||||
// Create header button
|
// Create header button
|
||||||
const button = document.createElement('button');
|
const button = document.createElement('button');
|
||||||
button.className = 'accordion-button';
|
button.className = 'accordion-button';
|
||||||
button.type = 'button';
|
button.type = 'button';
|
||||||
button.setAttribute('data-bs-toggle', 'collapse');
|
button.setAttribute('data-bs-toggle', 'collapse');
|
||||||
button.setAttribute('data-bs-target', `#${collapseId}`);
|
button.setAttribute('data-bs-target', `#${collapseId}`);
|
||||||
button.setAttribute('aria-expanded', 'true');
|
button.setAttribute('aria-expanded', 'true');
|
||||||
button.setAttribute('aria-controls', collapseId);
|
button.setAttribute('aria-controls', collapseId);
|
||||||
button.style.padding = '10px';
|
button.style.padding = '10px';
|
||||||
|
|
||||||
if (event.event === 'meetingStarted') {
|
if (event.event === 'meetingStarted') {
|
||||||
button.classList.add('bg-success');
|
button.classList.add('bg-success');
|
||||||
}
|
}
|
||||||
if (event.event === 'meetingEnded') {
|
if (event.event === 'meetingEnded') {
|
||||||
button.classList.add('bg-danger');
|
button.classList.add('bg-danger');
|
||||||
}
|
}
|
||||||
if (event.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
|
|
||||||
const date = new Date(event.creationDate);
|
|
||||||
|
|
||||||
const formattedDate = date.toLocaleString('es-ES', {
|
// Format the header text with event name and timestamp
|
||||||
// year: 'numeric',
|
const date = new Date(event.creationDate);
|
||||||
// month: '2-digit',
|
const formattedDate = date.toLocaleString('es-ES', {
|
||||||
// day: '2-digit',
|
// year: 'numeric',
|
||||||
hour: '2-digit',
|
// month: '2-digit',
|
||||||
minute: '2-digit',
|
// day: '2-digit',
|
||||||
second: '2-digit',
|
hour: '2-digit',
|
||||||
hour12: false,
|
minute: '2-digit',
|
||||||
});
|
second: '2-digit',
|
||||||
button.innerHTML = `[${formattedDate}] <strong>${event.event}</strong>`;
|
hour12: false
|
||||||
|
});
|
||||||
|
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', headerClassName);
|
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
|
||||||
const bodyDiv = document.createElement('div');
|
const bodyDiv = document.createElement('div');
|
||||||
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(event, 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)}</code></pre>`;
|
||||||
formattedJson
|
|
||||||
)}</code></pre>`;
|
|
||||||
|
|
||||||
// Assemble the components
|
// Assemble the components
|
||||||
header.appendChild(button);
|
header.appendChild(button);
|
||||||
collapseDiv.appendChild(bodyDiv);
|
collapseDiv.appendChild(bodyDiv);
|
||||||
accordionItem.appendChild(header);
|
accordionItem.appendChild(header);
|
||||||
accordionItem.appendChild(collapseDiv);
|
accordionItem.appendChild(collapseDiv);
|
||||||
|
|
||||||
// Insert at the top of the list (latest events first)
|
// Insert at the top of the list (latest events first)
|
||||||
if (webhookLogList.firstChild) {
|
if (webhookLogList.firstChild) {
|
||||||
webhookLogList.insertBefore(accordionItem, webhookLogList.firstChild);
|
webhookLogList.insertBefore(accordionItem, webhookLogList.firstChild);
|
||||||
} else {
|
} else {
|
||||||
webhookLogList.appendChild(accordionItem);
|
webhookLogList.appendChild(accordionItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limit the number of items to prevent performance issues
|
// Limit the number of items to prevent performance issues
|
||||||
const maxItems = 50;
|
const maxItems = 50;
|
||||||
while (webhookLogList.children.length > maxItems) {
|
while (webhookLogList.children.length > maxItems) {
|
||||||
webhookLogList.removeChild(webhookLogList.lastChild!);
|
webhookLogList.removeChild(webhookLogList.lastChild!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Listen to events from openvidu-meet
|
// Listen to events from openvidu-meet
|
||||||
|
|
||||||
const listenWebComponentEvents = () => {
|
const listenWebComponentEvents = () => {
|
||||||
const meet = document.querySelector('openvidu-meet') as any;
|
const meet = document.querySelector('openvidu-meet') as any;
|
||||||
if (!meet) {
|
if (!meet) {
|
||||||
console.error('openvidu-meet component not found');
|
console.error('openvidu-meet component not found');
|
||||||
alert('openvidu-meet component not found in the DOM');
|
alert('openvidu-meet component not found in the DOM');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
meet.on('JOIN', (event: CustomEvent<any>) => {
|
meet.on('JOIN', (event: CustomEvent<any>) => {
|
||||||
console.log('JOIN event received:', event);
|
console.log('JOIN event received:', event);
|
||||||
addEventToLog('JOIN', JSON.stringify(event));
|
addEventToLog('JOIN', JSON.stringify(event));
|
||||||
});
|
});
|
||||||
meet.on('LEFT', (event: CustomEvent<any>) => {
|
meet.on('LEFT', (event: CustomEvent<any>) => {
|
||||||
console.log('LEFT event received:', event);
|
console.log('LEFT event received:', event);
|
||||||
addEventToLog('LEFT', JSON.stringify(event));
|
addEventToLog('LEFT', JSON.stringify(event));
|
||||||
});
|
});
|
||||||
|
meet.on('MEETING_ENDED', (event: CustomEvent<any>) => {
|
||||||
meet.on('MEETING_ENDED', (event: CustomEvent<any>) => {
|
console.log('MEETING_ENDED event received:', event);
|
||||||
console.log('MEETING_ENDED event received:', event);
|
addEventToLog('MEETING_ENDED', JSON.stringify(event));
|
||||||
addEventToLog('MEETING_ENDED', JSON.stringify(event));
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set up commands for the web component
|
// Set up commands for the web component
|
||||||
const setUpWebComponentCommands = () => {
|
const setUpWebComponentCommands = () => {
|
||||||
if (!meet) {
|
if (!meet) {
|
||||||
console.error('openvidu-meet component not found');
|
console.error('openvidu-meet component not found');
|
||||||
alert('openvidu-meet component not found in the DOM');
|
alert('openvidu-meet component not found in the DOM');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// End meeting button click handler
|
// End meeting button click handler
|
||||||
document
|
document.getElementById('end-meeting-btn')?.addEventListener('click', () => meet.endMeeting());
|
||||||
.getElementById('end-meeting-btn')
|
|
||||||
?.addEventListener('click', () => meet.endMeeting());
|
|
||||||
|
|
||||||
// Leave room button click handler
|
// Leave room button click handler
|
||||||
document
|
document.getElementById('leave-room-btn')?.addEventListener('click', () => meet.leaveRoom());
|
||||||
.getElementById('leave-room-btn')
|
|
||||||
?.addEventListener('click', () => meet.leaveRoom());
|
|
||||||
|
|
||||||
// Toggle chat button click handler
|
// Kick participant button click handler
|
||||||
// document
|
document.getElementById('kick-participant-btn')?.addEventListener('click', () => {
|
||||||
// .getElementById('toggle-chat-btn')
|
const participantIdentity = (
|
||||||
// ?.addEventListener('click', () => meet.toggleChat());
|
document.getElementById('participant-identity-input') as HTMLInputElement
|
||||||
|
).value.trim();
|
||||||
|
if (participantIdentity) {
|
||||||
|
meet.kickParticipant(participantIdentity);
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
roomId = document.getElementById('room-id')?.textContent?.trim();
|
roomId = document.getElementById('room-id')?.textContent?.trim();
|
||||||
showAllWebhooksCheckbox = document.getElementById(
|
showAllWebhooksCheckbox = document.getElementById('show-all-webhooks') as HTMLInputElement;
|
||||||
'show-all-webhooks'
|
meet = document.querySelector('openvidu-meet') as any;
|
||||||
) as HTMLInputElement;
|
|
||||||
meet = document.querySelector('openvidu-meet') as any;
|
|
||||||
|
|
||||||
if (!roomId) {
|
if (!roomId) {
|
||||||
console.error('Room ID not found in the DOM');
|
console.error('Room ID not found in the DOM');
|
||||||
alert('Room ID not found in the DOM');
|
alert('Room ID not found in the DOM');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
renderStoredWebhookEvents(roomId);
|
|
||||||
listenWebhookServerEvents();
|
|
||||||
listenWebComponentEvents();
|
|
||||||
setUpWebComponentCommands();
|
|
||||||
|
|
||||||
showAllWebhooksCheckbox?.addEventListener('change', () => {
|
renderStoredWebhookEvents(roomId);
|
||||||
if (roomId) renderStoredWebhookEvents(roomId);
|
listenWebhookServerEvents();
|
||||||
});
|
listenWebComponentEvents();
|
||||||
|
setUpWebComponentCommands();
|
||||||
|
|
||||||
|
showAllWebhooksCheckbox?.addEventListener('change', () => {
|
||||||
|
if (roomId) renderStoredWebhookEvents(roomId);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -109,24 +109,29 @@
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
{{! ONLY RECORDINGS LINK }}
|
{{! SHOW ONLY RECORDINGS LINK }}
|
||||||
<li>
|
<li>
|
||||||
<form action="/join-room" method="post">
|
<form action="/join-room" method="post">
|
||||||
<input
|
<input
|
||||||
type="hidden"
|
type="hidden"
|
||||||
name="roomUrl"
|
name="roomUrl"
|
||||||
value="{{ publisherRoomUrl + '?viewRecordings=true' }}"
|
value="{{ moderatorRoomUrl }}"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="hidden"
|
type="hidden"
|
||||||
name="participantRole"
|
name="participantRole"
|
||||||
value="publisher"
|
value="moderator"
|
||||||
/>
|
/>
|
||||||
<input type="hidden" name="roomId" value="{{ roomId }}" />
|
<input type="hidden" name="roomId" value="{{ roomId }}" />
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="showOnlyRecordings"
|
||||||
|
value="true"
|
||||||
|
/>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
id="join-as-publisher"
|
id="view-recordings"
|
||||||
class="dropdown-item"
|
class="dropdown-item"
|
||||||
>
|
>
|
||||||
View Recordings
|
View Recordings
|
||||||
@ -273,7 +278,6 @@
|
|||||||
<option value="admin-moderator-publisher" selected>
|
<option value="admin-moderator-publisher" selected>
|
||||||
Admin, Moderator & Publisher
|
Admin, Moderator & Publisher
|
||||||
</option>
|
</option>
|
||||||
<option value="public">Public Access</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -42,9 +42,17 @@
|
|||||||
<button id="leave-room-btn" class="btn btn-warning">
|
<button id="leave-room-btn" class="btn btn-warning">
|
||||||
Leave Room
|
Leave Room
|
||||||
</button>
|
</button>
|
||||||
<!-- <button id="toggle-chat-btn" class="btn btn-success">
|
{{#isModerator}}
|
||||||
Toggle Chat
|
<input
|
||||||
</button> -->
|
type="text"
|
||||||
|
id="participant-identity-input"
|
||||||
|
placeholder="Participant Identity"
|
||||||
|
class="form-control mb-2"
|
||||||
|
/>
|
||||||
|
<button id="kick-participant-btn" class="btn btn-info">
|
||||||
|
Kick Participant
|
||||||
|
</button>
|
||||||
|
{{/isModerator}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Events -->
|
<!-- Events -->
|
||||||
@ -82,7 +90,9 @@
|
|||||||
<openvidu-meet
|
<openvidu-meet
|
||||||
room-url="{{ roomUrl }}"
|
room-url="{{ roomUrl }}"
|
||||||
participant-name="{{ participantName }}"
|
participant-name="{{ participantName }}"
|
||||||
leave-redirect-url="https://openvidu.io"
|
{{#showOnlyRecordings}}
|
||||||
|
show-only-recordings=true
|
||||||
|
{{/showOnlyRecordings}}
|
||||||
></openvidu-meet>
|
></openvidu-meet>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,124 +1,112 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import {
|
import { getAllRooms, createRoom, deleteRoom, deleteAllRooms } from '../services/roomService';
|
||||||
getAllRooms,
|
import { deleteAllRecordings, getAllRecordings } from '../services/recordingService';
|
||||||
createRoom,
|
|
||||||
deleteRoom,
|
|
||||||
deleteAllRooms,
|
|
||||||
} from '../services/roomService';
|
|
||||||
import {
|
|
||||||
deleteAllRecordings,
|
|
||||||
getAllRecordings,
|
|
||||||
} from '../services/recordingService';
|
|
||||||
|
|
||||||
export const getHome = async (req: Request, res: Response) => {
|
export const getHome = async (_req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { rooms } = await getAllRooms();
|
const { rooms } = await getAllRooms();
|
||||||
|
|
||||||
//sort rooms by newest first
|
// Sort rooms by newest first
|
||||||
rooms.sort((a, b) => {
|
rooms.sort((a, b) => {
|
||||||
const dateA = new Date(a.creationDate);
|
const dateA = new Date(a.creationDate);
|
||||||
const dateB = new Date(b.creationDate);
|
const dateB = new Date(b.creationDate);
|
||||||
return dateB.getTime() - dateA.getTime();
|
return dateB.getTime() - dateA.getTime();
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`Rooms fetched: ${rooms.length}`);
|
console.log(`Rooms fetched: ${rooms.length}`);
|
||||||
res.render('index', { rooms });
|
res.render('index', { rooms });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching rooms:', error);
|
console.error('Error fetching rooms:', error);
|
||||||
res.status(500).send('Internal Server Error');
|
res.status(500).send('Internal Server Error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const postCreateRoom = async (req: Request, res: Response) => {
|
export const postCreateRoom = async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { roomIdPrefix, autoDeletionDate } = req.body;
|
const { roomIdPrefix, autoDeletionDate } = req.body;
|
||||||
const preferences = processFormPreferences(req.body);
|
const preferences = processFormPreferences(req.body);
|
||||||
|
|
||||||
await createRoom({ roomIdPrefix, autoDeletionDate, preferences });
|
await createRoom({ roomIdPrefix, autoDeletionDate, preferences });
|
||||||
res.redirect('/');
|
res.redirect('/');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating room:', error);
|
console.error('Error creating room:', error);
|
||||||
res.status(500).send('Internal Server Error');
|
res.status(500).send('Internal Server Error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteRoomCtrl = async (req: Request, res: Response) => {
|
export const deleteRoomCtrl = async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { roomId } = req.body;
|
const { roomId } = req.body;
|
||||||
await deleteRoom(roomId);
|
await deleteRoom(roomId);
|
||||||
res.redirect('/');
|
res.redirect('/');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting room:', error);
|
console.error('Error deleting room:', error);
|
||||||
res.status(500).send('Internal Server Error');
|
res.status(500).send('Internal Server Error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteAllRoomsCtrl = async (_req: Request, res: Response) => {
|
export const deleteAllRoomsCtrl = async (_req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const allRooms = await getAllRooms();
|
const allRooms = await getAllRooms();
|
||||||
if (allRooms.rooms.length === 0) {
|
if (allRooms.rooms.length === 0) {
|
||||||
console.log('No rooms to delete');
|
console.log('No rooms to delete');
|
||||||
res.render('index', { rooms: [] });
|
res.render('index', { rooms: [] });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const roomIds = allRooms.rooms.map((room) => room.roomId);
|
|
||||||
console.log(`Deleting ${roomIds.length} rooms`, roomIds);
|
const roomIds = allRooms.rooms.map((room) => room.roomId);
|
||||||
await deleteAllRooms(roomIds);
|
console.log(`Deleting ${roomIds.length} rooms`, roomIds);
|
||||||
res.render('index', { rooms: [] });
|
await deleteAllRooms(roomIds);
|
||||||
} catch (error) {
|
res.render('index', { rooms: [] });
|
||||||
console.error('Error deleting all rooms:', error);
|
} catch (error) {
|
||||||
res.status(500).send('Internal Server Error ' + JSON.stringify(error));
|
console.error('Error deleting all rooms:', error);
|
||||||
return;
|
res.status(500).send('Internal Server Error ' + JSON.stringify(error));
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteAllRecordingsCtrl = async (_req: Request, res: Response) => {
|
export const deleteAllRecordingsCtrl = async (_req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const [{ recordings }, { rooms }] = await Promise.all([
|
const [{ recordings }, { rooms }] = await Promise.all([getAllRecordings(), getAllRooms()]);
|
||||||
getAllRecordings(),
|
if (recordings.length === 0) {
|
||||||
getAllRooms(),
|
console.log('No recordings to delete');
|
||||||
]);
|
res.render('index', { rooms });
|
||||||
if (recordings.length === 0) {
|
return;
|
||||||
console.log('No recordings to delete');
|
}
|
||||||
res.render('index', { rooms });
|
|
||||||
return;
|
const recordingIds = recordings.map((recording) => recording.recordingId);
|
||||||
}
|
await deleteAllRecordings(recordingIds);
|
||||||
const recordingIds = recordings.map((recording) => recording.recordingId);
|
console.log(`Deleted ${recordingIds.length} recordings`);
|
||||||
await deleteAllRecordings(recordingIds);
|
res.render('index', { rooms });
|
||||||
console.log(`Deleted ${recordingIds.length} recordings`);
|
} catch (error) {
|
||||||
res.render('index', { rooms });
|
console.error('Error deleting all recordings:', error);
|
||||||
} catch (error) {
|
res.status(500).send('Internal Server Error ' + JSON.stringify(error));
|
||||||
console.error('Error deleting all recordings:', error);
|
return;
|
||||||
res.status(500).send('Internal Server Error ' + JSON.stringify(error));
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts flat form data to nested MeetRoomPreferences object
|
* Converts flat form data to nested MeetRoomPreferences object
|
||||||
*/
|
*/
|
||||||
const processFormPreferences = (body: any): any => {
|
const processFormPreferences = (body: any): any => {
|
||||||
const preferences = {
|
const preferences = {
|
||||||
chatPreferences: {
|
chatPreferences: {
|
||||||
enabled: body['preferences.chatPreferences.enabled'] === 'on',
|
enabled: body['preferences.chatPreferences.enabled'] === 'on'
|
||||||
},
|
},
|
||||||
recordingPreferences: {
|
recordingPreferences: {
|
||||||
enabled: body['preferences.recordingPreferences.enabled'] === 'on',
|
enabled: body['preferences.recordingPreferences.enabled'] === 'on',
|
||||||
// Only include allowAccessTo if recording is enabled
|
// Only include allowAccessTo if recording is enabled
|
||||||
...(body['preferences.recordingPreferences.enabled'] === 'on' && {
|
...(body['preferences.recordingPreferences.enabled'] === 'on' && {
|
||||||
allowAccessTo:
|
allowAccessTo: body['preferences.recordingPreferences.allowAccessTo'] || 'admin-moderator-publisher'
|
||||||
body['preferences.recordingPreferences.allowAccessTo'] ||
|
})
|
||||||
'admin-moderator-publisher',
|
},
|
||||||
}),
|
virtualBackgroundPreferences: {
|
||||||
},
|
enabled: body['preferences.virtualBackgroundPreferences.enabled'] === 'on'
|
||||||
virtualBackgroundPreferences: {
|
}
|
||||||
enabled:
|
};
|
||||||
body['preferences.virtualBackgroundPreferences.enabled'] === 'on',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return preferences;
|
return preferences;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,56 +5,55 @@ import { ParticipantRole } from '../../../typings/src/participant';
|
|||||||
import { MeetWebhookEvent } from '../../../typings/src/webhook.model';
|
import { MeetWebhookEvent } from '../../../typings/src/webhook.model';
|
||||||
|
|
||||||
interface JoinRoomRequest {
|
interface JoinRoomRequest {
|
||||||
participantRole: ParticipantRole;
|
participantRole: ParticipantRole;
|
||||||
roomUrl: string;
|
roomUrl: string;
|
||||||
roomId: string;
|
roomId: string;
|
||||||
participantName?: string;
|
participantName?: string;
|
||||||
|
showOnlyRecordings?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const joinRoom = (req: Request, res: Response) => {
|
export const joinRoom = (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
participantRole,
|
participantRole,
|
||||||
roomUrl,
|
roomUrl,
|
||||||
roomId,
|
roomId,
|
||||||
participantName = 'User',
|
participantName = 'User',
|
||||||
} = req.body as JoinRoomRequest;
|
showOnlyRecordings
|
||||||
if (!roomUrl) {
|
} = req.body as JoinRoomRequest;
|
||||||
throw new Error('Room URL is required.');
|
if (!roomUrl) {
|
||||||
}
|
throw new Error('Room URL is required.');
|
||||||
res.render('room', {
|
}
|
||||||
roomUrl,
|
|
||||||
participantRole,
|
res.render('room', {
|
||||||
participantName,
|
roomUrl,
|
||||||
roomId,
|
isModerator: participantRole === 'moderator',
|
||||||
isModerator: participantRole === 'moderator',
|
participantName,
|
||||||
});
|
roomId,
|
||||||
} catch (error) {
|
showOnlyRecordings: showOnlyRecordings || false
|
||||||
console.error('Error joining room:', error);
|
});
|
||||||
res.status(500).send('Internal Server Error');
|
} catch (error) {
|
||||||
return;
|
console.error('Error joining room:', error);
|
||||||
}
|
res.status(500).send('Internal Server Error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const handleWebhook = async (
|
export const handleWebhook = async (req: Request, res: Response, io: IOServer): Promise<void> => {
|
||||||
req: Request,
|
try {
|
||||||
res: Response,
|
const webhookEvent = req.body as MeetWebhookEvent;
|
||||||
io: IOServer
|
|
||||||
): Promise<void> => {
|
|
||||||
try {
|
|
||||||
const webhookEvent = req.body as MeetWebhookEvent;
|
|
||||||
|
|
||||||
// Log event details
|
// Log event details
|
||||||
console.info(`Webhook received: ${webhookEvent.event}`);
|
console.info(`Webhook received: ${webhookEvent.event}`);
|
||||||
|
|
||||||
// Broadcast to all connected clients
|
// Broadcast to all connected clients
|
||||||
io.emit('webhookEvent', webhookEvent);
|
io.emit('webhookEvent', webhookEvent);
|
||||||
res.status(200).send('Webhook received');
|
res.status(200).send('Webhook received');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error handling webhook:', error);
|
console.error('Error handling webhook:', error);
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
message: 'Error handling webhook',
|
message: 'Error handling webhook',
|
||||||
error: (error as Error).message,
|
error: (error as Error).message
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
import { Server as IOServer } from 'socket.io';
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { Server as IOServer } from 'socket.io';
|
||||||
import {
|
import {
|
||||||
getHome,
|
deleteAllRecordingsCtrl,
|
||||||
postCreateRoom,
|
deleteAllRoomsCtrl,
|
||||||
deleteRoomCtrl,
|
deleteRoomCtrl,
|
||||||
deleteAllRoomsCtrl,
|
getHome,
|
||||||
deleteAllRecordingsCtrl,
|
postCreateRoom
|
||||||
} from './controllers/homeController';
|
} from './controllers/homeController';
|
||||||
import { handleWebhook, joinRoom } from './controllers/roomController';
|
import { handleWebhook, joinRoom } from './controllers/roomController';
|
||||||
import { configService } from './services/configService';
|
import { configService } from './services/configService';
|
||||||
@ -31,27 +31,24 @@ app.use(express.json());
|
|||||||
|
|
||||||
// Routes
|
// Routes
|
||||||
app.get('/', getHome);
|
app.get('/', getHome);
|
||||||
app.get('/room', joinRoom);
|
|
||||||
app.post('/room', postCreateRoom);
|
app.post('/room', postCreateRoom);
|
||||||
app.post('/room/delete', deleteRoomCtrl);
|
app.post('/room/delete', deleteRoomCtrl);
|
||||||
app.post('/delete-all-rooms', deleteAllRoomsCtrl);
|
app.post('/delete-all-rooms', deleteAllRoomsCtrl);
|
||||||
app.post('/delete-all-recordings', deleteAllRecordingsCtrl);
|
app.post('/delete-all-recordings', deleteAllRecordingsCtrl);
|
||||||
app.post('/join-room', joinRoom);
|
app.post('/join-room', joinRoom);
|
||||||
app.post('/webhook', (req, res) => {
|
app.post('/webhook', (req, res) => {
|
||||||
handleWebhook(req, res, io);
|
handleWebhook(req, res, io);
|
||||||
});
|
});
|
||||||
|
|
||||||
const PORT = configService.serverPort;
|
const PORT = configService.serverPort;
|
||||||
server.listen(PORT, () => {
|
server.listen(PORT, () => {
|
||||||
console.log('-----------------------------------------');
|
console.log('-----------------------------------------');
|
||||||
console.log(`Server running on port ${PORT}`);
|
console.log(`Server running on port ${PORT}`);
|
||||||
console.log(`Meet API URL: ${configService.meetApiUrl}`);
|
console.log(`Visit http://localhost:${PORT}/ to access the app`);
|
||||||
console.log('-----------------------------------------');
|
console.log('-----------------------------------------');
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log(`Visit http://localhost:${PORT}/ to access the app`);
|
|
||||||
|
|
||||||
console.log('Environment variables:');
|
console.log('OpenVidu Meet Configuration:');
|
||||||
console.log(`OPENVIDU_MEET_URL: ${process.env.OPENVIDU_MEET_URL}`);
|
console.log(`Meet API URL: ${configService.meetApiUrl}`);
|
||||||
console.log(`MEET_API_KEY: ${process.env.MEET_API_KEY} `);
|
console.log(`Meet API key: ${configService.meetApiKey}`);
|
||||||
console.log(`PORT: ${process.env.PORT}`);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,15 +3,15 @@ import dotenv from 'dotenv';
|
|||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
export class ConfigService {
|
export class ConfigService {
|
||||||
public meetApiUrl: string;
|
public meetApiUrl: string;
|
||||||
public meetApiKey: string;
|
public meetApiKey: string;
|
||||||
public serverPort: number;
|
public serverPort: number;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.meetApiUrl = process.env.OPENVIDU_MEET_URL!;
|
this.meetApiUrl = process.env.OPENVIDU_MEET_URL!;
|
||||||
this.meetApiKey = process.env.MEET_API_KEY!;
|
this.meetApiKey = process.env.MEET_API_KEY!;
|
||||||
this.serverPort = parseInt(process.env.PORT!, 10);
|
this.serverPort = parseInt(process.env.PORT!, 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const configService = new ConfigService();
|
export const configService = new ConfigService();
|
||||||
|
|||||||
@ -1,41 +1,39 @@
|
|||||||
import { del, get } from '../utils/http';
|
import { del, get } from '../utils/http';
|
||||||
|
// @ts-ignore
|
||||||
import { MeetRecordingInfo } from '../../../typings/src/recording.model';
|
import { MeetRecordingInfo } from '../../../typings/src/recording.model';
|
||||||
import { configService } from './configService';
|
import { configService } from './configService';
|
||||||
|
|
||||||
export const getAllRecordings = async (): Promise<{
|
export const getAllRecordings = async (): Promise<{
|
||||||
pagination: { isTruncated: boolean; nextPageToken?: string };
|
pagination: { isTruncated: boolean; nextPageToken?: string };
|
||||||
recordings: MeetRecordingInfo[];
|
recordings: MeetRecordingInfo[];
|
||||||
}> => {
|
}> => {
|
||||||
const url = `${configService.meetApiUrl}/recordings`;
|
const url = `${configService.meetApiUrl}/recordings`;
|
||||||
|
|
||||||
let { pagination, recordings } = await get<{
|
let { pagination, recordings } = await get<{
|
||||||
pagination: any;
|
pagination: any;
|
||||||
recordings: MeetRecordingInfo[];
|
recordings: MeetRecordingInfo[];
|
||||||
}>(url, {
|
}>(url, {
|
||||||
headers: { 'x-api-key': configService.meetApiKey },
|
headers: { 'x-api-key': configService.meetApiKey }
|
||||||
});
|
});
|
||||||
|
|
||||||
while (pagination.isTruncated) {
|
while (pagination.isTruncated) {
|
||||||
const nextPageUrl = `${url}?nextPageToken=${pagination.nextPageToken}`;
|
const nextPageUrl = `${url}?nextPageToken=${pagination.nextPageToken}`;
|
||||||
const nextPageResult = await get<{
|
const nextPageResult = await get<{
|
||||||
pagination: any;
|
pagination: any;
|
||||||
recordings: MeetRecordingInfo[];
|
recordings: MeetRecordingInfo[];
|
||||||
}>(nextPageUrl, {
|
}>(nextPageUrl, {
|
||||||
headers: { 'x-api-key': configService.meetApiKey },
|
headers: { 'x-api-key': configService.meetApiKey }
|
||||||
});
|
});
|
||||||
recordings.push(...nextPageResult.recordings);
|
recordings.push(...nextPageResult.recordings);
|
||||||
pagination = nextPageResult.pagination;
|
pagination = nextPageResult.pagination;
|
||||||
}
|
}
|
||||||
return { pagination, recordings };
|
|
||||||
|
return { pagination, recordings };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteAllRecordings = async (
|
export const deleteAllRecordings = async (recordingIds: string[]): Promise<void> => {
|
||||||
recordingIds: string[]
|
const url = `${configService.meetApiUrl}/recordings?recordingIds=${recordingIds.join(',')}`;
|
||||||
): Promise<void> => {
|
await del<void>(url, {
|
||||||
const url = `${
|
headers: { 'x-api-key': configService.meetApiKey }
|
||||||
configService.meetApiUrl
|
});
|
||||||
}/recordings?recordingIds=${recordingIds.join(',')}`;
|
|
||||||
await del<void>(url, {
|
|
||||||
headers: { 'x-api-key': configService.meetApiKey },
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,41 +1,45 @@
|
|||||||
import { get, post, del } from '../utils/http';
|
import { del, get, post } from '../utils/http';
|
||||||
import { configService } from './configService';
|
import { configService } from './configService';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { MeetRoom, MeetRoomOptions } from '../../../typings/src/room';
|
import { MeetRoom, MeetRoomOptions } from '../../../typings/src/room';
|
||||||
|
|
||||||
export async function getAllRooms(): Promise<{
|
export async function getAllRooms(): Promise<{
|
||||||
pagination: any;
|
pagination: any;
|
||||||
rooms: MeetRoom[];
|
rooms: MeetRoom[];
|
||||||
}> {
|
}> {
|
||||||
const url = `${configService.meetApiUrl}/rooms`;
|
const url = `${configService.meetApiUrl}/rooms`;
|
||||||
return get<{ pagination: any; rooms: MeetRoom[] }>(url, {
|
return get<{ pagination: any; rooms: MeetRoom[] }>(url, {
|
||||||
headers: { 'x-api-key': configService.meetApiKey },
|
headers: { 'x-api-key': configService.meetApiKey }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createRoom(roomData: MeetRoomOptions): Promise<MeetRoom> {
|
export async function createRoom(roomData: MeetRoomOptions): Promise<MeetRoom> {
|
||||||
const url = `${configService.meetApiUrl}/rooms`;
|
const url = `${configService.meetApiUrl}/rooms`;
|
||||||
// Default to 1 hour if autoDeletionDate is not provided
|
// Default to 1 hour if autoDeletionDate is not provided
|
||||||
if (!roomData.autoDeletionDate)
|
if (!roomData.autoDeletionDate) {
|
||||||
roomData.autoDeletionDate = new Date(Date.now() + 60 * 61 * 1000).getTime();
|
roomData.autoDeletionDate = new Date(Date.now() + 60 * 61 * 1000).getTime();
|
||||||
console.log('Creating room with options:', roomData);
|
} else {
|
||||||
return post<MeetRoom>(url, {
|
// Ensure autoDeletionDate is a timestamp
|
||||||
headers: { 'x-api-key': configService.meetApiKey },
|
roomData.autoDeletionDate = new Date(roomData.autoDeletionDate).getTime();
|
||||||
body: roomData,
|
}
|
||||||
});
|
|
||||||
|
console.log('Creating room with options:', roomData);
|
||||||
|
return post<MeetRoom>(url, {
|
||||||
|
headers: { 'x-api-key': configService.meetApiKey },
|
||||||
|
body: roomData
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteRoom(roomId: string): Promise<void> {
|
export async function deleteRoom(roomId: string): Promise<void> {
|
||||||
const url = `${configService.meetApiUrl}/rooms/${roomId}`;
|
const url = `${configService.meetApiUrl}/rooms/${roomId}`;
|
||||||
await del<void>(url, {
|
await del<void>(url, {
|
||||||
headers: { 'x-api-key': configService.meetApiKey },
|
headers: { 'x-api-key': configService.meetApiKey }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteAllRooms(roomIds: string[]): Promise<void> {
|
export async function deleteAllRooms(roomIds: string[]): Promise<void> {
|
||||||
const url = `${configService.meetApiUrl}/rooms?roomIds=${roomIds.join(',')}`;
|
const url = `${configService.meetApiUrl}/rooms?roomIds=${roomIds.join(',')}`;
|
||||||
|
await del<void>(url, {
|
||||||
await del<void>(url, {
|
headers: { 'x-api-key': configService.meetApiKey }
|
||||||
headers: { 'x-api-key': configService.meetApiKey },
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,70 +1,50 @@
|
|||||||
export interface RequestOptions {
|
export interface RequestOptions {
|
||||||
headers?: Record<string, string>;
|
headers?: Record<string, string>;
|
||||||
queryParams?: Record<string, string>;
|
queryParams?: Record<string, string>;
|
||||||
body?: any;
|
body?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function buildUrl(
|
async function buildUrl(url: string, queryParams?: Record<string, string>): Promise<string> {
|
||||||
url: string,
|
if (!queryParams) return url;
|
||||||
queryParams?: Record<string, string>
|
const params = new URLSearchParams(queryParams as Record<string, string>).toString();
|
||||||
): Promise<string> {
|
return `${url}?${params}`;
|
||||||
if (!queryParams) return url;
|
|
||||||
const params = new URLSearchParams(
|
|
||||||
queryParams as Record<string, string>
|
|
||||||
).toString();
|
|
||||||
return `${url}?${params}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function request<T>(
|
async function request<T>(method: string, url: string, options: RequestOptions = {}): Promise<T> {
|
||||||
method: string,
|
const fullUrl = await buildUrl(url, options.queryParams);
|
||||||
url: string,
|
const fetchOptions: RequestInit = {
|
||||||
options: RequestOptions = {}
|
method,
|
||||||
): Promise<T> {
|
headers: { 'Content-Type': 'application/json', ...(options.headers || {}) },
|
||||||
const fullUrl = await buildUrl(url, options.queryParams);
|
body: options.body ? JSON.stringify(options.body) : undefined
|
||||||
const fetchOptions: RequestInit = {
|
};
|
||||||
method,
|
|
||||||
headers: { 'Content-Type': 'application/json', ...(options.headers || {}) },
|
|
||||||
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
||||||
};
|
|
||||||
const response = await fetch(fullUrl, fetchOptions);
|
|
||||||
if (!response.ok) {
|
|
||||||
const text = await response.text();
|
|
||||||
throw new Error(`HTTP ${response.status}: ${text}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle empty responses (e.g., for DELETE requests)
|
const response = await fetch(fullUrl, fetchOptions);
|
||||||
const text = await response.text();
|
if (!response.ok) {
|
||||||
if (!text) {
|
const text = await response.text();
|
||||||
return {} as T;
|
throw new Error(`HTTP ${response.status}: ${text}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return JSON.parse(text) as T;
|
// Handle empty responses (e.g., for DELETE requests)
|
||||||
|
const text = await response.text();
|
||||||
|
if (!text) {
|
||||||
|
return {} as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.parse(text) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function get<T>(
|
export function get<T>(url: string, options?: Omit<RequestOptions, 'body'>): Promise<T> {
|
||||||
url: string,
|
return request<T>('GET', url, options || {});
|
||||||
options?: Omit<RequestOptions, 'body'>
|
|
||||||
): Promise<T> {
|
|
||||||
return request<T>('GET', url, options || {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function post<T>(
|
export function post<T>(url: string, options?: Omit<RequestOptions, 'body'> & { body: any }): Promise<T> {
|
||||||
url: string,
|
return request<T>('POST', url, options as RequestOptions);
|
||||||
options?: Omit<RequestOptions, 'body'> & { body: any }
|
|
||||||
): Promise<T> {
|
|
||||||
return request<T>('POST', url, options as RequestOptions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function put<T>(
|
export function put<T>(url: string, options?: Omit<RequestOptions, 'body'> & { body: any }): Promise<T> {
|
||||||
url: string,
|
return request<T>('PUT', url, options as RequestOptions);
|
||||||
options?: Omit<RequestOptions, 'body'> & { body: any }
|
|
||||||
): Promise<T> {
|
|
||||||
return request<T>('PUT', url, options as RequestOptions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function del<T>(
|
export function del<T>(url: string, options?: Omit<RequestOptions, 'body'>): Promise<T> {
|
||||||
url: string,
|
return request<T>('DELETE', url, options || {});
|
||||||
options?: Omit<RequestOptions, 'body'>
|
|
||||||
): Promise<T> {
|
|
||||||
return request<T>('DELETE', url, options || {});
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user