140 lines
3.5 KiB
TypeScript
140 lines
3.5 KiB
TypeScript
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 };
|