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:
juancarmore 2025-07-08 23:57:04 +02:00
parent 0a028226f3
commit 2b7466c6e3
10 changed files with 457 additions and 471 deletions

View File

@ -3,6 +3,7 @@ const socket = (window as any).io();
let meet: { let meet: {
endMeeting: () => void; endMeeting: () => void;
leaveRoom: () => void; leaveRoom: () => void;
kickParticipant: (participantIdentity: string) => void;
on: (event: string, callback: (event: CustomEvent<any>) => void) => void; on: (event: string, callback: (event: CustomEvent<any>) => void) => void;
}; };
let roomId: string | undefined; let roomId: string | undefined;
@ -31,7 +32,10 @@ const escapeHtml = (unsafe: string): string => {
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) {
return [];
}
const map = JSON.parse(data); const map = JSON.parse(data);
return map[roomId] || []; return map[roomId] || [];
}; };
@ -39,7 +43,10 @@ const getWebhookEventsFromStorage = (roomId: string): any[] => {
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] = [];
}
map[roomId].push(event); map[roomId].push(event);
localStorage.setItem('webhookEventsByRoom', JSON.stringify(map)); localStorage.setItem('webhookEventsByRoom', JSON.stringify(map));
}; };
@ -47,6 +54,7 @@ const saveWebhookEventToStorage = (roomId: string, event: any): void => {
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); const map = JSON.parse(data);
if (map[roomId]) { if (map[roomId]) {
map[roomId] = []; map[roomId] = [];
@ -57,6 +65,7 @@ const clearWebhookEventsByRoom = (roomId: string): void => {
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);
@ -86,6 +95,7 @@ const renderStoredWebhookEvents = (roomId: string) => {
webhookLogList.removeChild(webhookLogList.firstChild); webhookLogList.removeChild(webhookLogList.firstChild);
} }
} }
const events = getWebhookEventsFromStorage(roomId); const events = getWebhookEventsFromStorage(roomId);
events.forEach((event) => addWebhookEventElement(event)); events.forEach((event) => addWebhookEventElement(event));
}; };
@ -125,9 +135,9 @@ const addWebhookEventElement = (event: any) => {
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 // Format the header text with event name and timestamp
const date = new Date(event.creationDate); const date = new Date(event.creationDate);
const formattedDate = date.toLocaleString('es-ES', { const formattedDate = date.toLocaleString('es-ES', {
// year: 'numeric', // year: 'numeric',
// month: '2-digit', // month: '2-digit',
@ -135,7 +145,7 @@ const addWebhookEventElement = (event: any) => {
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit',
second: '2-digit', second: '2-digit',
hour12: false, hour12: false
}); });
button.innerHTML = `[${formattedDate}] <strong>${event.event}</strong>`; button.innerHTML = `[${formattedDate}] <strong>${event.event}</strong>`;
@ -152,9 +162,7 @@ const addWebhookEventElement = (event: any) => {
// 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);
@ -178,7 +186,6 @@ const addWebhookEventElement = (event: any) => {
}; };
// 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) {
@ -195,7 +202,6 @@ const listenWebComponentEvents = () => {
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));
@ -211,26 +217,25 @@ const setUpWebComponentCommands = () => {
} }
// 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'
) as HTMLInputElement;
meet = document.querySelector('openvidu-meet') as any; meet = document.querySelector('openvidu-meet') as any;
if (!roomId) { if (!roomId) {
@ -238,6 +243,7 @@ document.addEventListener('DOMContentLoaded', () => {
alert('Room ID not found in the DOM'); alert('Room ID not found in the DOM');
return; return;
} }
renderStoredWebhookEvents(roomId); renderStoredWebhookEvents(roomId);
listenWebhookServerEvents(); listenWebhookServerEvents();
listenWebComponentEvents(); listenWebComponentEvents();

View File

@ -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>

View File

@ -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>

View File

@ -1,20 +1,12 @@
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);
@ -64,6 +56,7 @@ export const deleteAllRoomsCtrl = async (_req: Request, res: Response) => {
res.render('index', { rooms: [] }); res.render('index', { rooms: [] });
return; return;
} }
const roomIds = allRooms.rooms.map((room) => room.roomId); const roomIds = allRooms.rooms.map((room) => room.roomId);
console.log(`Deleting ${roomIds.length} rooms`, roomIds); console.log(`Deleting ${roomIds.length} rooms`, roomIds);
await deleteAllRooms(roomIds); await deleteAllRooms(roomIds);
@ -77,15 +70,13 @@ export const deleteAllRoomsCtrl = async (_req: Request, res: Response) => {
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(),
getAllRooms(),
]);
if (recordings.length === 0) { if (recordings.length === 0) {
console.log('No recordings to delete'); console.log('No recordings to delete');
res.render('index', { rooms }); res.render('index', { rooms });
return; return;
} }
const recordingIds = recordings.map((recording) => recording.recordingId); const recordingIds = recordings.map((recording) => recording.recordingId);
await deleteAllRecordings(recordingIds); await deleteAllRecordings(recordingIds);
console.log(`Deleted ${recordingIds.length} recordings`); console.log(`Deleted ${recordingIds.length} recordings`);
@ -103,21 +94,18 @@ export const deleteAllRecordingsCtrl = async (_req: Request, res: Response) => {
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: { virtualBackgroundPreferences: {
enabled: enabled: body['preferences.virtualBackgroundPreferences.enabled'] === 'on'
body['preferences.virtualBackgroundPreferences.enabled'] === 'on', }
},
}; };
return preferences; return preferences;

View File

@ -9,6 +9,7 @@ interface JoinRoomRequest {
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) => {
@ -18,16 +19,18 @@ export const joinRoom = (req: Request, res: Response) => {
roomUrl, roomUrl,
roomId, roomId,
participantName = 'User', participantName = 'User',
showOnlyRecordings
} = req.body as JoinRoomRequest; } = req.body as JoinRoomRequest;
if (!roomUrl) { if (!roomUrl) {
throw new Error('Room URL is required.'); throw new Error('Room URL is required.');
} }
res.render('room', { res.render('room', {
roomUrl, roomUrl,
participantRole, isModerator: participantRole === 'moderator',
participantName, participantName,
roomId, roomId,
isModerator: participantRole === 'moderator', showOnlyRecordings: showOnlyRecordings || false
}); });
} catch (error) { } catch (error) {
console.error('Error joining room:', error); console.error('Error joining room:', error);
@ -36,11 +39,7 @@ export const joinRoom = (req: Request, res: Response) => {
} }
}; };
export const handleWebhook = async ( export const handleWebhook = async (req: Request, res: Response, io: IOServer): Promise<void> => {
req: Request,
res: Response,
io: IOServer
): Promise<void> => {
try { try {
const webhookEvent = req.body as MeetWebhookEvent; const webhookEvent = req.body as MeetWebhookEvent;
@ -54,7 +53,7 @@ export const handleWebhook = async (
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
}); });
} }
}; };

View File

@ -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,
postCreateRoom,
deleteRoomCtrl,
deleteAllRoomsCtrl,
deleteAllRecordingsCtrl, deleteAllRecordingsCtrl,
deleteAllRoomsCtrl,
deleteRoomCtrl,
getHome,
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,7 +31,6 @@ 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);
@ -45,13 +44,11 @@ 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}`);
}); });

View File

@ -1,4 +1,5 @@
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';
@ -12,7 +13,7 @@ export const getAllRecordings = async (): Promise<{
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) {
@ -21,21 +22,18 @@ export const getAllRecordings = async (): Promise<{
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> => {
const url = `${
configService.meetApiUrl
}/recordings?recordingIds=${recordingIds.join(',')}`;
await del<void>(url, { await del<void>(url, {
headers: { 'x-api-key': configService.meetApiKey }, headers: { 'x-api-key': configService.meetApiKey }
}); });
}; };

View File

@ -1,4 +1,4 @@
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';
@ -9,33 +9,37 @@ export async function getAllRooms(): Promise<{
}> { }> {
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();
} else {
// Ensure autoDeletionDate is a timestamp
roomData.autoDeletionDate = new Date(roomData.autoDeletionDate).getTime();
}
console.log('Creating room with options:', roomData); console.log('Creating room with options:', roomData);
return post<MeetRoom>(url, { return post<MeetRoom>(url, {
headers: { 'x-api-key': configService.meetApiKey }, headers: { 'x-api-key': configService.meetApiKey },
body: roomData, 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 }
}); });
} }

View File

@ -4,28 +4,20 @@ export interface RequestOptions {
body?: any; body?: any;
} }
async function buildUrl( async function buildUrl(url: string, queryParams?: Record<string, string>): Promise<string> {
url: string,
queryParams?: Record<string, string>
): Promise<string> {
if (!queryParams) return url; if (!queryParams) return url;
const params = new URLSearchParams( const params = new URLSearchParams(queryParams as Record<string, string>).toString();
queryParams as Record<string, string>
).toString();
return `${url}?${params}`; return `${url}?${params}`;
} }
async function request<T>( async function request<T>(method: string, url: string, options: RequestOptions = {}): Promise<T> {
method: string,
url: string,
options: RequestOptions = {}
): Promise<T> {
const fullUrl = await buildUrl(url, options.queryParams); const fullUrl = await buildUrl(url, options.queryParams);
const fetchOptions: RequestInit = { const fetchOptions: RequestInit = {
method, method,
headers: { 'Content-Type': 'application/json', ...(options.headers || {}) }, headers: { 'Content-Type': 'application/json', ...(options.headers || {}) },
body: options.body ? JSON.stringify(options.body) : undefined, body: options.body ? JSON.stringify(options.body) : undefined
}; };
const response = await fetch(fullUrl, fetchOptions); const response = await fetch(fullUrl, fetchOptions);
if (!response.ok) { if (!response.ok) {
const text = await response.text(); const text = await response.text();
@ -41,30 +33,18 @@ async function request<T>(
return JSON.parse(text) 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,
options?: Omit<RequestOptions, 'body'>
): Promise<T> {
return request<T>('GET', url, options || {}); 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,
options?: Omit<RequestOptions, 'body'> & { body: any }
): Promise<T> {
return request<T>('POST', url, options as RequestOptions); 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,
options?: Omit<RequestOptions, 'body'> & { body: any }
): Promise<T> {
return request<T>('PUT', url, options as RequestOptions); 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,
options?: Omit<RequestOptions, 'body'>
): Promise<T> {
return request<T>('DELETE', url, options || {}); return request<T>('DELETE', url, options || {});
} }