feat(jitsi): video meetings — /api/jitsi/token route + jitsi-meet plugin + feature gate
Publish Docker Image / prepare (push) Successful in 2s
Publish Docker Image / build (linux/amd64, ubuntu-latest) (push) Failing after 8s
Publish Docker Image / build (linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
Publish Docker Image / merge (push) Canceled after 0s

This commit is contained in:
Bernd Rodler
2026-08-25 23:17:29 +02:00
parent a457c1770e
commit 2116798281
7 changed files with 693 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
/**
* Video meetings — SRC Advisory webmail plugin (VNCtalk / Jitsi).
*
* Adds a "Video meeting" entry to Settings. "Start a meeting" generates a room
* name, asks the webmail server for a signed JWT (the server holds the shared
* JITSI_APP_SECRET — it never reaches the browser), then opens the room in
* meet.src-advisory.com with the token as a query param.
*/
const host = require('@plugin-host');
const React = require('react');
const h = React.createElement;
const { useState } = React;
const card = {
border: '1px solid var(--color-border, #e2e8f0)',
borderRadius: '8px',
padding: '16px',
background: 'var(--color-card, #fff)',
color: 'var(--color-foreground, #0f172a)',
maxWidth: '720px',
};
const btnPrimary = {
font: 'inherit',
padding: '8px 14px',
borderRadius: '6px',
border: '1px solid var(--color-primary, #2563eb)',
background: 'var(--color-primary, #2563eb)',
color: '#fff',
cursor: 'pointer',
};
const input = {
font: 'inherit',
padding: '6px 8px',
borderRadius: '6px',
border: '1px solid var(--color-input, #cbd5e1)',
background: 'var(--color-background, #fff)',
color: 'var(--color-foreground, #0f172a)',
marginRight: '8px',
};
function generateRoomName() {
const suffix = Math.random().toString(36).slice(2, 10);
return `meeting-${suffix}`;
}
function SettingsSection() {
const [busy, setBusy] = useState(false);
const [joinRoom, setJoinRoom] = useState('');
async function openRoom(room) {
setBusy(true);
try {
const resp = await fetch('/api/jitsi/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ room }),
});
const data = await resp.json();
if (!resp.ok || !data.token) {
throw new Error(data.error || 'could not obtain meeting token');
}
const url = `${data.url}?jwt=${encodeURIComponent(data.token)}`;
try { host.ui.openExternalUrl(url); }
catch { window.open(url, '_blank', 'noopener,noreferrer'); }
} catch (err) {
const msg = err && err.message ? err.message : String(err);
try { host.toast.error(`Could not start meeting: ${msg}`); } catch { /* ignore */ }
} finally {
setBusy(false);
}
}
return h('div', { style: card },
h('div', { style: { fontWeight: 600, marginBottom: '6px' } }, 'Video meeting'),
h('div', { style: { fontSize: '13px', lineHeight: 1.5, marginBottom: '14px', color: 'var(--color-muted-foreground, #64748b)' } },
'Start or join a SRC video meeting. The room is opened in meet.src-advisory.com.'),
h('div', { style: { display: 'flex', alignItems: 'center', marginBottom: '10px' } },
h('button', { type: 'button', style: btnPrimary, disabled: busy, onClick: () => openRoom(generateRoomName()) },
busy ? 'Starting…' : 'Start a meeting →'),
),
h('div', { style: { display: 'flex', alignItems: 'center' } },
h('input', {
type: 'text',
style: input,
placeholder: 'Room name',
value: joinRoom,
onChange: (e) => setJoinRoom(e.target.value),
}),
h('button', { type: 'button', style: btnPrimary, disabled: busy || !joinRoom.trim(), onClick: () => openRoom(joinRoom.trim()) },
'Join'),
),
);
}
export const slots = {
'settings-section': { component: SettingsSection, order: 90 },
};
export async function activate(api) {
api.log.info('jitsi-meet plugin activated');
}