add avatar support for BBB
All checks were successful
Build & Push Docker Image / build (push) Successful in 1m10s

This commit is contained in:
2026-02-24 19:05:41 +01:00
parent cf74ed31af
commit 9001aea8cd
5 changed files with 51 additions and 4 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -66,7 +66,7 @@ export async function createMeeting(room, logoutURL) {
return apiCall('create', params);
}
export async function joinMeeting(uid, name, isModerator = false) {
export async function joinMeeting(uid, name, isModerator = false, avatarURL = null) {
const { moderatorPW, attendeePW } = getRoomPasswords(uid);
const params = {
meetingID: uid,
@@ -74,6 +74,9 @@ export async function joinMeeting(uid, name, isModerator = false) {
password: isModerator ? moderatorPW : attendeePW,
redirect: 'true',
};
if (avatarURL) {
params.avatarURL = avatarURL;
}
return buildUrl('join', params);
}

View File

@@ -203,6 +203,36 @@ router.delete('/avatar', authenticateToken, async (req, res) => {
}
});
// GET /api/auth/avatar/initials/:name - Generate SVG avatar from initials (public, BBB fetches this)
router.get('/avatar/initials/:name', (req, res) => {
const name = decodeURIComponent(req.params.name).trim();
const color = req.query.color || generateColorFromName(name);
const initials = name
.split(' ')
.map(n => n[0])
.join('')
.toUpperCase()
.slice(0, 2) || '?';
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<rect width="128" height="128" rx="64" fill="${color}"/>
<text x="64" y="64" dy=".35em" text-anchor="middle" fill="white" font-family="Arial, sans-serif" font-size="52" font-weight="bold">${initials}</text>
</svg>`;
res.setHeader('Content-Type', 'image/svg+xml');
res.setHeader('Cache-Control', 'public, max-age=3600');
res.send(svg);
});
function generateColorFromName(name) {
let hash = 0;
for (let i = 0; i < name.length; i++) {
hash = name.charCodeAt(i) + ((hash << 5) - hash);
}
const hue = Math.abs(hash) % 360;
return `hsl(${hue}, 55%, 45%)`;
}
// GET /api/auth/avatar/:filename - Serve avatar image
router.get('/avatar/:filename', (req, res) => {
const filepath = path.join(uploadsDir, req.params.filename);

View File

@@ -12,6 +12,16 @@ import {
const router = Router();
// Build avatar URL for a user (uploaded image or generated initials)
function getUserAvatarURL(req, user) {
const baseUrl = `${req.protocol}://${req.get('host')}`;
if (user.avatar_image) {
return `${baseUrl}/api/auth/avatar/${user.avatar_image}`;
}
const color = user.avatar_color ? `?color=${encodeURIComponent(user.avatar_color)}` : '';
return `${baseUrl}/api/auth/avatar/initials/${encodeURIComponent(user.name)}${color}`;
}
// GET /api/rooms - List user's rooms
router.get('/', authenticateToken, async (req, res) => {
try {
@@ -199,7 +209,8 @@ router.post('/:uid/start', authenticateToken, async (req, res) => {
}
await createMeeting(room, `${req.protocol}://${req.get('host')}`);
const joinUrl = await joinMeeting(room.uid, req.user.name, true);
const avatarURL = getUserAvatarURL(req, req.user);
const joinUrl = await joinMeeting(room.uid, req.user.name, true, avatarURL);
res.json({ joinUrl });
} catch (err) {
console.error('Start meeting error:', err);
@@ -229,7 +240,8 @@ router.post('/:uid/join', authenticateToken, async (req, res) => {
}
const isModerator = room.user_id === req.user.id || room.all_join_moderator;
const joinUrl = await joinMeeting(room.uid, req.user.name, isModerator);
const avatarURL = getUserAvatarURL(req, req.user);
const joinUrl = await joinMeeting(room.uid, req.user.name, isModerator, avatarURL);
res.json({ joinUrl });
} catch (err) {
console.error('Join meeting error:', err);
@@ -335,7 +347,9 @@ router.post('/:uid/guest-join', async (req, res) => {
isModerator = true;
}
const joinUrl = await joinMeeting(room.uid, name.trim(), isModerator);
const baseUrl = `${req.protocol}://${req.get('host')}`;
const guestAvatarURL = `${baseUrl}/api/auth/avatar/initials/${encodeURIComponent(name.trim())}`;
const joinUrl = await joinMeeting(room.uid, name.trim(), isModerator, guestAvatarURL);
res.json({ joinUrl });
} catch (err) {
console.error('Guest join error:', err);