UNPKG

digital-samba-mcp-server

Version:

Digital Samba MCP Server - Model Context Protocol server for Digital Samba's video conferencing API

1,452 lines (1,451 loc) 51.4 kB
/** * Digital Samba API Client Module * * This module provides a comprehensive client for interacting with the Digital Samba API. * It offers interfaces for all API entities and a client class that handles authentication, * request processing, and provides methods for all available API endpoints. * * Key features include: * - Authentication using either direct API key or the ApiKeyContext for session-based auth * - Comprehensive coverage of all Digital Samba API endpoints * - Type-safe interfaces for request and response data * - Error handling and logging * - Support for pagination, filtering, and other API parameters * * @module digital-samba-api * @author Digital Samba Team * @version 0.1.0 */ // Local modules import apiKeyContext from './auth.js'; import { ApiRequestError, ApiResponseError, AuthenticationError, ResourceNotFoundError, ValidationError } from './errors.js'; import logger from './logger.js'; /** * Digital Samba API Client * * This class provides a comprehensive interface to the Digital Samba API. It handles * authentication, request formation, response parsing, and error handling for all * available API endpoints. The client supports both direct API key authentication * and session-based authentication through the ApiKeyContext. * * @class DigitalSambaApiClient * @example * // Create a client with direct API key * const client = new DigitalSambaApiClient('your-api-key'); * * // Create a client that uses the ApiKeyContext * const sessionClient = new DigitalSambaApiClient(); * * // List all rooms * const rooms = await client.listRooms(); * * // Create a room * const room = await client.createRoom({ * name: 'New Meeting Room', * privacy: 'public' * }); */ export class DigitalSambaApiClient { /** * Creates an instance of the Digital Samba API Client * * @constructor * @param {string} [apiKey] - Optional API key for direct authentication. If not provided, * the client will use the ApiKeyContext for session-based authentication * @param {string} [apiBaseUrl='https://api.digitalsamba.com/api/v1'] - Base URL for the Digital Samba API * @example * // Create a client with the default API URL * const client = new DigitalSambaApiClient('your-api-key'); * * // Create a client with a custom API URL * const customClient = new DigitalSambaApiClient('your-api-key', 'https://custom-api.example.com/v1'); */ constructor(apiKey, apiBaseUrl = 'https://api.digitalsamba.com/api/v1', cache) { // Store the API key in ApiKeyContext if provided if (apiKey) { // For direct usage outside of MCP context this._apiKey = apiKey; } this.apiBaseUrl = apiBaseUrl; this.cache = cache; } /** * Get the API key from context or direct value * * This method retrieves the API key using a prioritized approach: * 1. First tries to get the API key from the ApiKeyContext (for session-based auth) * 2. If not found, falls back to using the direct API key if provided during construction * 3. If neither source provides an API key, throws an AuthenticationError * * @protected * @returns {string} The API key to use for authentication * @throws {AuthenticationError} If no API key is available from any source */ getApiKey() { // Try to get API key from context first const contextApiKey = apiKeyContext.getCurrentApiKey(); if (contextApiKey) { return contextApiKey; } // Fall back to direct API key if set if (this._apiKey) { return this._apiKey; } // No API key available throw new AuthenticationError('No API key found in context or provided directly. Please include an Authorization header with a Bearer token.'); } /** * Make an authenticated request to the Digital Samba API * * This method handles all API requests including authentication, error handling, and response parsing. * It automatically adds the Authorization header with the API key, logs request details (excluding sensitive * information), and processes the response. It also handles special cases like 204 No Content responses and * adds array-like properties to ApiResponse objects for easier consumption. * * @protected * @template T - The expected response type * @param {string} endpoint - The API endpoint path (without the base URL) * @param {RequestInit} [options={}] - Request options, including method, body, and additional headers * @returns {Promise<T>} A promise resolving to the parsed response data * @throws {AuthenticationError} If no API key is available for authentication * @throws {ApiRequestError} If a network error occurs during the request * @throws {ApiResponseError} If the API returns a non-2xx status code * @throws {ValidationError} If the API returns a 400 Bad Request with validation errors * @throws {ResourceNotFoundError} If the API returns a 404 Not Found response * @example * // Example internal usage * const rooms = await this.request<ApiResponse<Room>>('/rooms'); */ async request(endpoint, options = {}) { // Handle case where endpoint is already a full URL (starts with http:// or https://) const url = endpoint.startsWith('http') ? endpoint : `${this.apiBaseUrl}${endpoint}`; const method = options.method || 'GET'; const isCacheable = this.cache && method === 'GET'; // Generate a cache key based on endpoint and API key (to avoid cross-client leakage) const cacheNamespace = 'api'; const cacheKey = endpoint; // Check cache first for GET requests if (isCacheable) { const cachedResponse = this.cache.get(cacheNamespace, cacheKey); if (cachedResponse) { logger.debug(`Cache hit for ${endpoint}`); // Record cache hit in metrics if available try { const metricsRegistry = await import('./metrics.js').then(m => m.default); metricsRegistry.cacheHitsTotal.inc({ namespace: cacheNamespace }); } catch (error) { // Ignore metrics errors - they shouldn't affect normal operation } return cachedResponse.value; } else if (this.cache) { // Record cache miss in metrics if available try { const metricsRegistry = await import('./metrics.js').then(m => m.default); metricsRegistry.cacheMissesTotal.inc({ namespace: cacheNamespace }); } catch (error) { // Ignore metrics errors } } } // Start timer for metrics const startTime = Date.now(); let metricsLabels = { endpoint, method: method.toLowerCase() }; try { const apiKey = this.getApiKey(); const headers = { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', ...options.headers }; // Log the request details (excluding sensitive info) logger.debug(`Making API request to: ${url}`, { method, headers: { ...headers, Authorization: '[REDACTED]' }, cacheStatus: isCacheable ? 'miss' : 'disabled' }); // Track API request in metrics try { const metricsRegistry = await import('./metrics.js').then(m => m.default); metricsRegistry.apiRequestsTotal.inc(metricsLabels); } catch (error) { // Ignore metrics errors } let response; try { response = await fetch(url, { ...options, headers }); } catch (error) { // Handle network errors logger.error('Network error in API request', { url, method, error: error instanceof Error ? error.message : String(error) }); // Track API error in metrics try { const metricsRegistry = await import('./metrics.js').then(m => m.default); metricsRegistry.apiErrorsTotal.inc({ ...metricsLabels, error_type: 'network_error' }); } catch (metricsError) { // Ignore metrics errors } throw new ApiRequestError(`Network error while connecting to Digital Samba API: ${error instanceof Error ? error.message : String(error)}`, { cause: error instanceof Error ? error : undefined }); } // Log response details logger.debug(`Response status: ${response.status} ${response.statusText}`); // Update metrics labels with status code metricsLabels = { ...metricsLabels, status: response.status.toString() }; if (!response.ok) { const errorText = await response.text(); logger.error(`API Error Response: ${errorText}`, { status: response.status, statusText: response.statusText }); // Track API error in metrics try { const metricsRegistry = await import('./metrics.js').then(m => m.default); metricsRegistry.apiErrorsTotal.inc({ ...metricsLabels, error_type: `status_${response.status}` }); } catch (metricsError) { // Ignore metrics errors } // Parse error text as JSON if possible let errorData; try { errorData = JSON.parse(errorText); } catch { // Not JSON, use as plain text errorData = { message: errorText }; } // Handle specific error types based on status code if (response.status === 400) { // Bad Request - typically validation errors const validationErrors = errorData.errors || {}; throw new ValidationError(`Validation error: ${errorData.message || errorText}`, { validationErrors: validationErrors }); } else if (response.status === 401 || response.status === 403) { // Authentication or authorization error throw new AuthenticationError(`Authentication error: ${errorData.message || errorText}`); } else if (response.status === 404) { // Not Found error // Try to extract resource type and ID from the endpoint const matches = endpoint.match(/\/([^\/]+)\/([^\/]+)/); const resourceType = matches ? matches[1] : 'resource'; const resourceId = matches ? matches[2] : 'unknown'; throw new ResourceNotFoundError(`Resource not found: ${errorData.message || errorText}`, { resourceType, resourceId }); } else { // Generic API error throw new ApiResponseError(`Digital Samba API error (${response.status}): ${errorData.message || errorText}`, { statusCode: response.status, apiErrorMessage: errorData.message || errorText, apiErrorData: errorData }); } } // Return empty object for 204 No Content responses if (response.status === 204) { // Record request duration in metrics try { const duration = (Date.now() - startTime) / 1000; // Convert to seconds const metricsRegistry = await import('./metrics.js').then(m => m.default); metricsRegistry.apiRequestDuration.observe(metricsLabels, duration); } catch (error) { // Ignore metrics errors } return {}; } const responseData = await response.json(); // Add array-like properties to ApiResponse objects if (responseData && responseData.data && Array.isArray(responseData.data)) { // Add length property responseData.length = responseData.data.length; // Add map function that forwards to the data array responseData.map = function (callback) { return this.data.map(callback); }; } // Record request duration in metrics try { const duration = (Date.now() - startTime) / 1000; // Convert to seconds const metricsRegistry = await import('./metrics.js').then(m => m.default); metricsRegistry.apiRequestDuration.observe(metricsLabels, duration); } catch (error) { // Ignore metrics errors } // Store successful GET responses in cache if (isCacheable) { logger.debug(`Caching response for ${endpoint}`); this.cache.set(cacheNamespace, cacheKey, responseData); // Update cache metrics try { const metricsRegistry = await import('./metrics.js').then(m => m.default); metricsRegistry.cacheEntriesCount.inc({ namespace: cacheNamespace }); } catch (error) { // Ignore metrics errors } } return responseData; } catch (error) { // Record request duration in metrics even for errors try { const duration = (Date.now() - startTime) / 1000; // Convert to seconds const metricsRegistry = await import('./metrics.js').then(m => m.default); metricsRegistry.apiRequestDuration.observe(metricsLabels, duration); } catch (metricsError) { // Ignore metrics errors } // Catch and re-throw errors that aren't already one of our custom types if (!(error instanceof AuthenticationError) && !(error instanceof ApiRequestError) && !(error instanceof ApiResponseError) && !(error instanceof ValidationError) && !(error instanceof ResourceNotFoundError)) { logger.error('Unexpected error in API request', { url, method: options.method || 'GET', error: error instanceof Error ? error.message : String(error) }); // Track API error in metrics try { const metricsRegistry = await import('./metrics.js').then(m => m.default); metricsRegistry.apiErrorsTotal.inc({ ...metricsLabels, error_type: 'unexpected' }); } catch (metricsError) { // Ignore metrics errors } throw new ApiRequestError(`Unexpected error in Digital Samba API request: ${error instanceof Error ? error.message : String(error)}`, { cause: error instanceof Error ? error : undefined }); } // Re-throw custom error types throw error; } } // Default Room Settings /** * Get default room settings */ async getDefaultRoomSettings() { return this.request(''); } /** * Update default room settings */ async updateDefaultRoomSettings(settings) { return this.request('', { method: 'PATCH', body: JSON.stringify(settings) }); } // Rooms /** * List all rooms */ async listRooms(params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/rooms${query}`); } /** * Get details for a specific room */ async getRoom(roomId) { return this.request(`/rooms/${roomId}`); } /** * Create a new room */ async createRoom(settings) { // Make sure name is defined (it's required by the API) const roomSettings = { ...settings, name: settings.name || 'New Meeting Room' }; return this.request('/rooms', { method: 'POST', body: JSON.stringify(roomSettings) }); } /** * Update an existing room */ async updateRoom(roomId, settings) { return this.request(`/rooms/${roomId}`, { method: 'PATCH', body: JSON.stringify(settings) }); } /** * Delete a room */ async deleteRoom(roomId, options) { // Invalidate cache when deleting resources if (this.cache) { this.cache.invalidateNamespace('api'); } await this.request(`/rooms/${roomId}`, { method: 'DELETE', body: options ? JSON.stringify(options) : undefined }); } /** * Generate a token for joining a room */ async generateRoomToken(roomId, options) { return this.request(`/rooms/${roomId}/token`, { method: 'POST', body: JSON.stringify(options) }); } /** * Delete all resources for a room */ async deleteRoomResources(roomId) { await this.request(`/rooms/${roomId}/resources`, { method: 'DELETE' }); } // Live Participants /** * Get rooms with live participants count */ async getLiveRooms(params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/rooms/live${query}`); } /** * Get rooms with live participants data */ async getLiveRoomsWithParticipants(params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/rooms/live/participants${query}`); } /** * Get single room with live participants count */ async getRoomLiveParticipantsCount(roomId) { return this.request(`/rooms/${roomId}/live`); } /** * Get single room with live participants data */ async getRoomLiveParticipantsData(roomId) { return this.request(`/rooms/${roomId}/live/participants`); } // Participants /** * List all participants */ async listParticipants(params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/participants${query}`); } /** * Get details for a specific participant */ async getParticipant(participantId) { return this.request(`/participants/${participantId}`); } /** * List participants in a room */ async listRoomParticipants(roomId, params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/rooms/${roomId}/participants${query}`); } /** * List participants in a session */ async listSessionParticipants(sessionId, params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/sessions/${sessionId}/participants${query}`); } /** * Phone participants joined */ async phoneParticipantsJoined(roomId, participants) { await this.request(`/rooms/${roomId}/phone-participants/joined`, { method: 'POST', body: JSON.stringify(participants) }); } /** * Phone participants left */ async phoneParticipantsLeft(roomId, callIds) { await this.request(`/rooms/${roomId}/phone-participants/left`, { method: 'POST', body: JSON.stringify(callIds) }); } // Recordings /** * List all recordings */ async listRecordings(params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/recordings${query}`); } /** * List archived recordings */ async listArchivedRecordings(params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/recordings/archived${query}`); } /** * Get a specific recording */ async getRecording(recordingId) { return this.request(`/recordings/${recordingId}`); } /** * Delete a recording */ async deleteRecording(recordingId) { await this.request(`/recordings/${recordingId}`, { method: 'DELETE' }); } /** * Get a download link for a recording */ async getRecordingDownloadLink(recordingId, validForMinutes) { const queryParams = new URLSearchParams(); if (validForMinutes !== undefined) { queryParams.append('valid_for_minutes', String(validForMinutes)); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/recordings/${recordingId}/download${query}`); } /** * Archive a recording */ async archiveRecording(recordingId) { await this.request(`/recordings/${recordingId}/archive`, { method: 'POST' }); } /** * Unarchive a recording */ async unarchiveRecording(recordingId) { await this.request(`/recordings/${recordingId}/unarchive`, { method: 'POST' }); } /** * Start recording in a room */ async startRecording(roomId) { await this.request(`/rooms/${roomId}/recordings/start`, { method: 'POST' }); } /** * Stop recording in a room */ async stopRecording(roomId) { await this.request(`/rooms/${roomId}/recordings/stop`, { method: 'POST' }); } // Webhooks /** * List available event types for webhooks */ async listWebhookEvents() { return this.request('/events'); } /** * List all webhooks */ async listWebhooks(params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/webhooks${query}`); } /** * Create a new webhook */ async createWebhook(settings) { return this.request('/webhooks', { method: 'POST', body: JSON.stringify(settings) }); } /** * Get a specific webhook */ async getWebhook(webhookId) { return this.request(`/webhooks/${webhookId}`); } /** * Update a webhook */ async updateWebhook(webhookId, settings) { return this.request(`/webhooks/${webhookId}`, { method: 'PATCH', body: JSON.stringify(settings) }); } /** * Delete a webhook */ async deleteWebhook(webhookId) { await this.request(`/webhooks/${webhookId}`, { method: 'DELETE' }); } // Roles and Permissions /** * List all roles */ async listRoles(params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/roles${query}`); } /** * Create a new role */ async createRole(settings) { return this.request('/roles', { method: 'POST', body: JSON.stringify(settings) }); } /** * Get a specific role */ async getRole(roleId) { return this.request(`/roles/${roleId}`); } /** * Update a role */ async updateRole(roleId, settings) { return this.request(`/roles/${roleId}`, { method: 'PATCH', body: JSON.stringify(settings) }); } /** * Delete a role */ async deleteRole(roleId) { await this.request(`/roles/${roleId}`, { method: 'DELETE' }); } /** * List all available permissions */ async listPermissions() { return this.request('/permissions'); } // Sessions /** * List all sessions */ async listSessions(params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/sessions${query}`); } /** * List sessions for a specific room */ async listRoomSessions(roomId, params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/rooms/${roomId}/sessions${query}`); } /** * Get session statistics */ async getSessionStatistics(sessionId, metrics) { const queryParams = new URLSearchParams(); if (metrics) { queryParams.append('metrics', metrics); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/sessions/${sessionId}${query}`); } /** * End a live session */ async endSession(sessionId) { await this.request(`/sessions/${sessionId}/end`, { method: 'POST' }); } /** * Get session summary */ async getSessionSummary(sessionId) { return this.request(`/sessions/${sessionId}/summary`); } /** * Delete session data */ async deleteSessionData(sessionId, dataType) { await this.request(`/sessions/${sessionId}/${dataType}`, { method: 'DELETE' }); } // Chat and Q&A /** * Get chat messages */ async getChatMessages(roomId, params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/rooms/${roomId}/chat${query}`); } /** * Delete chat messages */ async deleteChatMessages(roomId) { await this.request(`/rooms/${roomId}/chat`, { method: 'DELETE' }); } /** * Export chat messages */ async exportChatMessages(roomId, options) { const queryParams = new URLSearchParams(); if (options) { Object.entries(options).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; const apiKey = this.getApiKey(); const response = await fetch(`${this.apiBaseUrl}/rooms/${roomId}/chat/export${query}`, { headers: { 'Authorization': `Bearer ${apiKey}` } }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Digital Samba API error (${response.status}): ${errorText}`); } return response.text(); } /** * Get Q&A */ async getQuestionsAndAnswers(roomId, params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/rooms/${roomId}/questions${query}`); } /** * Delete Q&A */ async deleteQA(roomId) { await this.request(`/rooms/${roomId}/questions`, { method: 'DELETE' }); } /** * Export Q&A */ async exportQA(roomId, options) { const queryParams = new URLSearchParams(); if (options) { Object.entries(options).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; const apiKey = this.getApiKey(); const response = await fetch(`${this.apiBaseUrl}/rooms/${roomId}/questions/export${query}`, { headers: { 'Authorization': `Bearer ${apiKey}` } }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Digital Samba API error (${response.status}): ${errorText}`); } return response.text(); } // Polls /** * Get polls */ async getPolls(roomId, params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/rooms/${roomId}/polls${query}`); } /** * Create a poll */ async createPoll(roomId, settings) { return this.request(`/rooms/${roomId}/polls`, { method: 'POST', body: JSON.stringify(settings) }); } /** * Get a specific poll */ async getPoll(roomId, pollId) { return this.request(`/rooms/${roomId}/polls/${pollId}`); } /** * Update a poll */ async updatePoll(roomId, pollId, settings) { return this.request(`/rooms/${roomId}/polls/${pollId}`, { method: 'PATCH', body: JSON.stringify(settings) }); } /** * Delete a poll */ async deletePoll(roomId, pollId) { await this.request(`/rooms/${roomId}/polls/${pollId}`, { method: 'DELETE' }); } /** * Get poll results */ async getPollResults(roomId, pollId, sessionId) { const queryParams = new URLSearchParams(); if (sessionId) { queryParams.append('session_id', sessionId); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/rooms/${roomId}/polls/${pollId}/results${query}`); } /** * Export polls */ async exportPolls(roomId, options) { const queryParams = new URLSearchParams(); if (options) { Object.entries(options).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; const apiKey = this.getApiKey(); const response = await fetch(`${this.apiBaseUrl}/rooms/${roomId}/polls/export${query}`, { headers: { 'Authorization': `Bearer ${apiKey}` } }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Digital Samba API error (${response.status}): ${errorText}`); } return response.text(); } // Libraries /** * List all libraries */ async listLibraries(params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/libraries${query}`); } /** * Create a new library */ async createLibrary(settings) { return this.request('/libraries', { method: 'POST', body: JSON.stringify(settings) }); } /** * Get a specific library */ async getLibrary(libraryId) { return this.request(`/libraries/${libraryId}`); } /** * Update a library */ async updateLibrary(libraryId, settings) { return this.request(`/libraries/${libraryId}`, { method: 'PATCH', body: JSON.stringify(settings) }); } /** * Delete a library */ async deleteLibrary(libraryId) { await this.request(`/libraries/${libraryId}`, { method: 'DELETE' }); } /** * Get library hierarchy */ async getLibraryHierarchy(libraryId) { return this.request(`/libraries/${libraryId}/hierarchy`); } /** * List library folders */ async listLibraryFolders(libraryId, params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/libraries/${libraryId}/folders${query}`); } /** * Create a library folder */ async createLibraryFolder(libraryId, settings) { return this.request(`/libraries/${libraryId}/folders`, { method: 'POST', body: JSON.stringify(settings) }); } /** * Get a specific library folder */ async getLibraryFolder(libraryId, folderId) { return this.request(`/libraries/${libraryId}/folders/${folderId}`); } /** * Update a library folder */ async updateLibraryFolder(libraryId, folderId, settings) { return this.request(`/libraries/${libraryId}/folders/${folderId}`, { method: 'PATCH', body: JSON.stringify(settings) }); } /** * Delete a library folder */ async deleteLibraryFolder(libraryId, folderId) { await this.request(`/libraries/${libraryId}/folders/${folderId}`, { method: 'DELETE' }); } /** * List library files */ async listLibraryFiles(libraryId, params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/libraries/${libraryId}/files${query}`); } /** * Create a new library file (get upload URL and token) */ async createLibraryFile(libraryId, settings) { return this.request(`/libraries/${libraryId}/files`, { method: 'POST', body: JSON.stringify(settings) }); } /** * Get a specific library file */ async getLibraryFile(libraryId, fileId) { return this.request(`/libraries/${libraryId}/files/${fileId}`); } /** * Update a library file */ async updateLibraryFile(libraryId, fileId, settings) { return this.request(`/libraries/${libraryId}/files/${fileId}`, { method: 'PATCH', body: JSON.stringify(settings) }); } /** * Delete a library file */ async deleteLibraryFile(libraryId, fileId) { await this.request(`/libraries/${libraryId}/files/${fileId}`, { method: 'DELETE' }); } /** * Get file links */ async getFileLinks(libraryId, fileId) { return this.request(`/libraries/${libraryId}/files/${fileId}/links`); } // Moderation /** * Remove a participant from a room */ async removeParticipant(roomId, participantId) { await this.request(`/rooms/${roomId}/participants/${participantId}/remove`, { method: 'POST' }); } /** * Set participant mute status */ async setParticipantMute(roomId, participantId, options) { const { mute, type = 'all' } = options; await this.request(`/rooms/${roomId}/participants/${participantId}/mute`, { method: 'POST', body: JSON.stringify({ mute, type }) }); } /** * Set participant role */ async setParticipantRole(roomId, participantId, role) { await this.request(`/rooms/${roomId}/participants/${participantId}/role`, { method: 'POST', body: JSON.stringify({ role }) }); } /** * Ban a participant from a room */ async banParticipant(roomId, participantId) { await this.request(`/rooms/${roomId}/participants/${participantId}/ban`, { method: 'POST' }); } /** * Unban a participant from a room */ async unbanParticipant(roomId, participantId) { await this.request(`/rooms/${roomId}/participants/${participantId}/unban`, { method: 'POST' }); } /** * Get banned participants */ async getBannedParticipants(roomId) { return this.request(`/rooms/${roomId}/banned-participants`); } // Statistics /** * Get team global statistics by period */ async getTeamStatistics(params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/statistics${query}`); } /** * Get team global statistics by current period */ async getTeamCurrentStatistics(metrics) { const queryParams = new URLSearchParams(); if (metrics) { queryParams.append('metrics', metrics); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/statistics/team/current${query}`); } /** * Get team statistics for current period (simplified) */ async getSimplifiedTeamCurrentStatistics() { return this.request('/statistics/current'); } /** * Get room statistics by period */ async getRoomStatistics(roomId, params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/rooms/${roomId}/statistics${query}`); } /** * Get room statistics for current period */ async getRoomCurrentStatistics(roomId, metrics) { const queryParams = new URLSearchParams(); if (metrics) { queryParams.append('metrics', metrics); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/rooms/${roomId}/statistics/current${query}`); } /** * Get participant statistics */ async getParticipantStatistics(participantId) { return this.request(`/participants/${participantId}/statistics`); } // Breakout Rooms /** * List breakout rooms for a parent room */ async listBreakoutRooms(roomId, params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/rooms/${roomId}/breakout-rooms${query}`); } /** * Get a specific breakout room */ async getBreakoutRoom(roomId, breakoutRoomId) { return this.request(`/rooms/${roomId}/breakout-rooms/${breakoutRoomId}`); } /** * Create breakout rooms */ async createBreakoutRooms(roomId, settings) { return this.request(`/rooms/${roomId}/breakout-rooms`, { method: 'POST', body: JSON.stringify(settings) }); } /** * Delete a breakout room */ async deleteBreakoutRoom(roomId, breakoutRoomId) { // Invalidate cache when deleting resources if (this.cache) { this.cache.invalidateNamespace('api'); } await this.request(`/rooms/${roomId}/breakout-rooms/${breakoutRoomId}`, { method: 'DELETE' }); } /** * Delete all breakout rooms */ async deleteAllBreakoutRooms(roomId) { // Invalidate cache when deleting resources if (this.cache) { this.cache.invalidateNamespace('api'); } await this.request(`/rooms/${roomId}/breakout-rooms`, { method: 'DELETE' }); } /** * List participants in a breakout room */ async listBreakoutRoomParticipants(roomId, breakoutRoomId, params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/rooms/${roomId}/breakout-rooms/${breakoutRoomId}/participants${query}`); } /** * Assign participants to breakout rooms */ async assignParticipantsToBreakoutRooms(roomId, assignments) { await this.request(`/rooms/${roomId}/breakout-rooms/assignments`, { method: 'POST', body: JSON.stringify(assignments) }); } /** * Return all participants to the main room */ async returnAllParticipantsToMainRoom(roomId) { await this.request(`/rooms/${roomId}/breakout-rooms/return-all`, { method: 'POST' }); } /** * Broadcast message to all breakout rooms */ async broadcastToBreakoutRooms(roomId, options) { await this.request(`/rooms/${roomId}/breakout-rooms/broadcast`, { method: 'POST', body: JSON.stringify(options) }); } /** * Open breakout rooms (start breakout sessions) */ async openBreakoutRooms(roomId) { await this.request(`/rooms/${roomId}/breakout-rooms/open`, { method: 'POST' }); } /** * Close breakout rooms */ async closeBreakoutRooms(roomId) { await this.request(`/rooms/${roomId}/breakout-rooms/close`, { method: 'POST' }); } // Meeting Scheduling /** * List all scheduled meetings */ async listScheduledMeetings(params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/meetings${query}`); } /** * Get a specific scheduled meeting */ async getScheduledMeeting(meetingId) { return this.request(`/meetings/${meetingId}`); } /** * Create a new scheduled meeting */ async createScheduledMeeting(settings) { return this.request('/meetings', { method: 'POST', body: JSON.stringify(settings) }); } /** * Update a scheduled meeting */ async updateScheduledMeeting(meetingId, settings) { return this.request(`/meetings/${meetingId}`, { method: 'PATCH', body: JSON.stringify(settings) }); } /** * Cancel a scheduled meeting */ async cancelScheduledMeeting(meetingId, options) { await this.request(`/meetings/${meetingId}/cancel`, { method: 'POST', body: options ? JSON.stringify(options) : undefined }); } /** * Delete a scheduled meeting */ async deleteScheduledMeeting(meetingId) { await this.request(`/meetings/${meetingId}`, { method: 'DELETE' }); } /** * List upcoming meetings */ async listUpcomingMeetings(params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { queryParams.append(key, String(value)); } }); } const query = queryParams.toString() ? `?${queryParams.toString()}` : ''; return this.request(`/meetings/upcoming${query}`); } /** * List meetings for a specific room */ async listRoomMeetings(roomId, params) { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined) {