add avatar support for BBB
All checks were successful
Build & Push Docker Image / build (push) Successful in 1m10s
All checks were successful
Build & Push Docker Image / build (push) Successful in 1m10s
This commit is contained in:
BIN
redlight.db-shm
BIN
redlight.db-shm
Binary file not shown.
BIN
redlight.db-wal
BIN
redlight.db-wal
Binary file not shown.
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user