import crypto from 'crypto'; import xml2js from 'xml2js'; const BBB_URL = process.env.BBB_URL || 'https://your-bbb-server.com/bigbluebutton/api/'; const BBB_SECRET = process.env.BBB_SECRET || ''; function getChecksum(apiCall, params) { const queryString = new URLSearchParams(params).toString(); const raw = apiCall + queryString + BBB_SECRET; return crypto.createHash('sha256').update(raw).digest('hex'); } function buildUrl(apiCall, params = {}) { const checksum = getChecksum(apiCall, params); const queryString = new URLSearchParams({ ...params, checksum }).toString(); return `${BBB_URL}${apiCall}?${queryString}`; } async function apiCall(apiCallName, params = {}) { const url = buildUrl(apiCallName, params); try { const response = await fetch(url); const xml = await response.text(); const result = await xml2js.parseStringPromise(xml, { explicitArray: false, trim: true, }); return result.response; } catch (error) { console.error(`BBB API error (${apiCallName}):`, error.message); throw error; } } // Generate deterministic passwords from room UID function getRoomPasswords(uid) { const modPw = crypto.createHash('sha256').update(uid + '_mod_' + BBB_SECRET).digest('hex').substring(0, 16); const attPw = crypto.createHash('sha256').update(uid + '_att_' + BBB_SECRET).digest('hex').substring(0, 16); return { moderatorPW: modPw, attendeePW: attPw }; } export async function createMeeting(room) { const { moderatorPW, attendeePW } = getRoomPasswords(room.uid); const params = { meetingID: room.uid, name: room.name, attendeePW, moderatorPW, welcome: room.welcome_message || 'Willkommen!', record: room.record_meeting ? 'true' : 'false', autoStartRecording: 'false', allowStartStopRecording: 'true', muteOnStart: room.mute_on_join ? 'true' : 'false', 'meta_bbb-origin': 'Redlight', 'meta_bbb-origin-server-name': 'Redlight', }; if (room.max_participants > 0) { params.maxParticipants = room.max_participants.toString(); } if (room.access_code) { params.lockSettingsLockOnJoin = 'true'; } return apiCall('create', params); } export async function joinMeeting(uid, name, isModerator = false) { const { moderatorPW, attendeePW } = getRoomPasswords(uid); const params = { meetingID: uid, fullName: name, password: isModerator ? moderatorPW : attendeePW, redirect: 'true', }; return buildUrl('join', params); } export async function endMeeting(uid) { const { moderatorPW } = getRoomPasswords(uid); return apiCall('end', { meetingID: uid, password: moderatorPW }); } export async function getMeetingInfo(uid) { return apiCall('getMeetingInfo', { meetingID: uid }); } export async function isMeetingRunning(uid) { const result = await apiCall('isMeetingRunning', { meetingID: uid }); return result.running === 'true'; } export async function getMeetings() { return apiCall('getMeetings', {}); } export async function getRecordings(meetingID) { const params = meetingID ? { meetingID } : {}; const result = await apiCall('getRecordings', params); if (result.returncode !== 'SUCCESS' || !result.recordings) { return []; } const recordings = result.recordings.recording; if (!recordings) return []; return Array.isArray(recordings) ? recordings : [recordings]; } export async function deleteRecording(recordID) { return apiCall('deleteRecordings', { recordID }); } export async function publishRecording(recordID, publish) { return apiCall('publishRecordings', { recordID, publish: publish ? 'true' : 'false' }); } export { getRoomPasswords };