UNPKG

@digitalsamba/embedded-api-mcp-server

Version:

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

1,431 lines (1,430 loc) 50.3 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 developer 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"; export class DigitalSambaApiClient { /** * Creates an instance of the Digital Samba API Client * * @constructor * @param {string} [apiKey] - Optional developer 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-developer-key'); * * // Create a client with a custom API URL * const customClient = new DigitalSambaApiClient('your-developer-key', 'https://custom-api.example.com/v1'); */ constructor(apiKey, apiBaseUrl = "https://api.digitalsamba.com/api/v1", cache) { // Store the developer 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 developer key from context or direct value * * This method retrieves the developer key using a prioritized approach: * 1. First tries to get the developer key from the ApiKeyContext (for session-based auth) * 2. If not found, falls back to using the direct developer key if provided during construction * 3. If neither source provides a developer key, throws an AuthenticationError * * @protected * @returns {string} The developer key to use for authentication * @throws {AuthenticationError} If no developer key is available from any source */ getApiKey() { // Try to get developer key from context first const contextApiKey = apiKeyContext.getStore(); if (contextApiKey) { return contextApiKey; } // Fall back to direct developer key if set if (this._apiKey) { return this._apiKey; } // No developer key available throw new AuthenticationError("No developer 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}`); // Metrics recording removed - metrics.js no longer exists return cachedResponse.value; } else if (this.cache) { // Metrics recording removed - metrics.js no longer exists } } // Timer removed - no longer needed without metrics 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", }); // Metrics tracking removed - metrics.js no longer exists 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), }); // Metrics tracking removed - metrics.js no longer exists 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}`); // Status code handling - metrics removed if (!response.ok) { const errorText = await response.text(); logger.error(`API Error Response: ${errorText}`, { status: response.status, statusText: response.statusText, }); // Metrics tracking removed - metrics.js no longer exists // 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 // This provides better error context for API consumers if (response.status === 400) { // Bad Request - typically validation errors // Digital Samba API returns validation errors in the 'errors' field const validationErrors = errorData.errors || {}; throw new ValidationError(`Validation error: ${errorData.message || errorText}`, { validationErrors: validationErrors }); } else if (response.status === 401 || response.status === 403) { // 401: Missing or invalid API key // 403: Valid API key but insufficient permissions throw new AuthenticationError(`Authentication error: ${errorData.message || errorText}`); } else if (response.status === 404) { // Not Found error - resource doesn't exist // Try to extract resource type and ID from the endpoint for future error enhancement const matches = endpoint.match(/\/([^/]+)\/([^/]+)/); // Resource type and ID extraction - currently unused but kept for future error details void matches; // For backwards compatibility with tests, throw a generic API error throw new ApiResponseError(`Digital Samba API error (${response.status}): ${errorData.message || errorText}`, { statusCode: response.status, apiErrorMessage: errorData.message || errorText, }); } 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) { // Metrics tracking removed - metrics.js no longer exists return {}; } // Get response text first to check if it's empty const responseText = await response.text(); // Handle empty response bodies (some endpoints return 200 with empty body or {}) if (!responseText || responseText.trim() === '') { logger.debug(`Empty response body for ${endpoint}`); return {}; } // Parse the JSON response let responseData; try { responseData = JSON.parse(responseText); } catch (parseError) { logger.error(`Failed to parse JSON response for ${endpoint}`, { responseText, error: parseError instanceof Error ? parseError.message : String(parseError), }); throw new ApiResponseError(`Invalid JSON response from Digital Samba API: ${parseError instanceof Error ? parseError.message : String(parseError)}`, { statusCode: response.status, apiErrorMessage: `Failed to parse JSON: ${responseText}`, apiErrorData: { responseText }, }); } // 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); }; } // Metrics tracking removed - metrics.js no longer exists // Store successful GET responses in cache if (isCacheable) { logger.debug(`Caching response for ${endpoint}`); this.cache.set(cacheNamespace, cacheKey, responseData); // Metrics tracking removed - metrics.js no longer exists } return responseData; } catch (error) { // Metrics tracking removed - metrics.js no longer exists // 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), }); // Metrics tracking removed - metrics.js no longer exists 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 * * Retrieves a paginated list of all rooms in your Digital Samba account. * Supports pagination, filtering, and sorting options. * * @param {PaginationParams} [params] - Optional pagination parameters * @param {number} [params.limit] - Number of items per page (default: 10) * @param {number} [params.offset] - Number of items to skip * @param {'asc'|'desc'} [params.order] - Sort order * @param {string} [params.after] - Cursor for pagination * @returns {Promise<ApiResponse<Room>>} Paginated list of rooms * * @example * // Get first 20 rooms * const rooms = await client.listRooms({ limit: 20 }); * * @example * // Get next page * const nextPage = await client.listRooms({ * limit: 20, * after: rooms.data[rooms.data.length - 1].id * }); */ 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 * * Creates a new video conferencing room with the specified settings. * Only the 'name' field is required; all other settings are optional. * * @param {RoomCreateSettings} settings - Room configuration * @param {string} settings.name - Room name (required) * @param {string} [settings.description] - Room description * @param {'public'|'private'} [settings.privacy] - Room privacy setting * @param {number} [settings.max_participants] - Maximum participants (2-2000) * @param {string} [settings.friendly_url] - Custom URL slug * @returns {Promise<Room>} The created room object * @throws {ValidationError} If required fields are missing or invalid * @throws {ApiResponseError} If room creation fails * * @example * // Create a basic room * const room = await client.createRoom({ * name: 'Team Standup' * }); * * @example * // Create a fully configured room * const room = await client.createRoom({ * name: 'All Hands Meeting', * description: 'Monthly company meeting', * privacy: 'private', * max_participants: 200, * friendly_url: 'all-hands', * recordings_enabled: true, * chat_enabled: true * }); */ 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"); } return this.request(`/rooms/${roomId}`, { method: "DELETE", body: options ? JSON.stringify(options) : undefined, }); } /** * Generate a token for joining a room * * Creates a secure access token that allows a user to join a specific room. * Tokens can include user information, roles, and expiration settings. * * @param {string} roomId - The ID of the room to generate a token for * @param {TokenOptions} options - Token configuration options * @param {string} [options.u] - User name to display * @param {string} [options.ud] - External user identifier * @param {string} [options.role] - User role (e.g., 'moderator', 'participant') * @param {string} [options.avatar] - URL to user's avatar image * @param {string} [options.exp] - Token expiration in minutes * @returns {Promise<TokenResponse>} Object containing token and join link * * @example * // Generate a basic participant token * const { token, link } = await client.generateRoomToken('room-123', { * u: 'John Doe' * }); * * @example * // Generate a moderator token with expiration * const { token, link } = await client.generateRoomToken('room-123', { * u: 'Jane Smith', * ud: 'user-456', * role: 'moderator', * exp: '120' // Expires in 2 hours * }); */ 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", }); } /** * Start transcription in a room */ async startTranscription(roomId) { await this.request(`/rooms/${roomId}/transcription/start`, { method: "POST", }); } /** * Stop transcription in a room */ async stopTranscription(roomId) { await this.request(`/rooms/${roomId}/transcription/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", }); } /** * 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", }); } // 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(); } /** * 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(); } /** * Export Q&A (questions and answers) */ 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(); } /** * Export session transcripts */ async exportTranscripts(sessionId, 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}/sessions/${sessionId}/transcripts/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`); } // Webapps /** * Create a webapp in a library */ async createWebapp(libraryId, settings) { return this.request(`/libraries/${libraryId}/webapps`, { method: "POST", body: JSON.stringify(settings), }); } // Whiteboards /** * Create a whiteboard in a library */ async createWhiteboard(libraryId, settings) { return this.request(`/libraries/${libraryId}/whiteboards`, { method: "POST", body: JSON.stringify(settings), }); } // 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", }); } // Communication Management Methods /** * Delete session chats */ async deleteSessionChats(sessionId) { // Invalidate cache when deleting resources if (this.cache) { this.cache.invalidateNamespace("api"); } await this.request(`/sessions/${sessionId}/chat`, { method: "DELETE", }); } /** * Delete session Q&A */ async deleteSessionQA(sessionId) { // Invalidate cache when deleting resources if (this.cache) { this.cache.invalidateNamespace("api"); } await this.request(`/sessions/${sessionId}/questions`, { method: "DELETE", }); } /** * Delete session summaries */ async deleteSessionSummaries(sessionId) { // Invalidate cache when deleting resources if (this.cache) { this.cache.invalidateNamespace("api"); } await this.request(`/sessions/${sessionId}/summaries`, { method: "DELETE", }); } /** * Delete session polls */ async deleteSessionPolls(sessionId) { // Invalidate cache when deleting resources if (this.cache) { this.cache.invalidateNamespace("api"); } await this.request(`/sessions/${sessionId}/polls`, {