import jwt from 'jsonwebtoken'; export interface JitsiUser { id: string; name: string; email?: string; avatar?: string; role: 'INSTRUCTOR' | 'CLIENT' | 'SUPER_ADMIN'; } export interface JitsiRoomOptions { tenantSlug: string; classSessionId: string; timestamp?: number; } export interface JitsiTokenOptions { roomName: string; tenantSlug: string; user: JitsiUser; expiresInSeconds?: number; } const JITSI_DOMAIN = process.env.JITSI_DOMAIN || 'jitsi.my127001.ca'; const JITSI_APP_ID = process.env.JITSI_APP_ID || 'fitness-app'; const JITSI_PUBLIC_URL = process.env.JITSI_PUBLIC_URL || `https://${JITSI_DOMAIN}`; /** * 1. Generates deterministic, tenant-isolated Jitsi room name: * Format: ${tenantSlug}_${classSessionId}_${timestamp} */ export function generateJitsiRoomName( tenantSlug: string, classSessionId: string, timestamp: number = Date.now() ): string { const cleanSlug = tenantSlug.toLowerCase().replace(/[^a-z0-9_-]/g, '-'); const cleanSessionId = classSessionId.replace(/[^a-z0-9_-]/g, '-'); return `${cleanSlug}_${cleanSessionId}_${timestamp}`; } /** * 2. Generates a signed Jitsi JWT token for host (INSTRUCTOR) or participant (CLIENT) */ export function generateJitsiToken({ roomName, tenantSlug, user, expiresInSeconds = 7200, // 2 hours }: JitsiTokenOptions): string { const secret = process.env.JITSI_APP_SECRET; if (!secret) { throw new Error('JITSI_APP_SECRET is required to sign classroom access tokens'); } const isHost = user.role === 'INSTRUCTOR' || user.role === 'SUPER_ADMIN'; const nowInSeconds = Math.floor(Date.now() / 1000); const payload = { aud: 'jitsi', iss: JITSI_APP_ID, sub: JITSI_DOMAIN, room: roomName, iat: nowInSeconds, nbf: nowInSeconds - 5, exp: nowInSeconds + expiresInSeconds, context: { user: { id: user.id, name: user.name, email: user.email || `${user.id}@${tenantSlug}.local`, avatar: user.avatar, moderator: isHost, }, features: { recording: isHost, livestreaming: isHost, screenSharing: true, lobby: isHost, }, tenant: tenantSlug, }, }; return jwt.sign(payload, secret, { algorithm: 'HS256', header: { typ: 'JWT', alg: 'HS256', }, }); } /** * 3. Constructs the meeting room URL with unbranded options */ export function generateJitsiMeetingUrl(roomName: string, token?: string): string { const base = `${JITSI_PUBLIC_URL}/${encodeURIComponent(roomName)}`; if (!token) return base; return `${base}?jwt=${encodeURIComponent(token)}`; } /** * Pre-set feature flags and config overrides for unbranded classrooms */ export const DEFAULT_JITSI_CONFIG_OVERRIDES = { disableThirdPartyRequests: true, defaultLanguage: 'en', hideLobbyButton: false, toolbarButtons: [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'tileview', 'hangup', ], disableDeepLinking: true, prejoinPageEnabled: false, startWithAudioMuted: false, startWithVideoMuted: false, enableWelcomePage: false, enableClosePage: false, disableInviteFunctions: true, }; export const DEFAULT_JITSI_INTERFACE_OVERRIDES = { SHOW_JITSI_WATERMARK: false, SHOW_WATERMARK_FOR_GUESTS: false, SHOW_BRAND_WATERMARK: false, DEFAULT_BACKGROUND: '#121212', SHOW_PROMOTIONAL_CLOSE_PAGE: false, MOBILE_APP_PROMO: false, HIDE_DEEP_LINKING_LOGO: true, TOOLBAR_ALWAYS_VISIBLE: false, }; export { JITSI_DOMAIN, JITSI_PUBLIC_URL, JITSI_APP_ID };