initial version

This commit is contained in:
stefan.tollkuehn committed 2026-09-22 09:40:07 +02:00
1 parent 93dc6ccc07
commit 34df35d54c
1 file changed
+609
+609
View File
@@ -0,0 +1,609 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Jitsi Meeting öffnen</title>
<style>
:root {
color-scheme: light dark;
}
body {
margin: 0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #f5f5f5;
color: #222;
}
.card {
box-sizing: border-box;
width: min(600px, calc(100% - 32px));
padding: 32px;
background: white;
border-radius: 14px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.10);
text-align: center;
}
h1 {
margin-top: 0;
}
p {
color: #555;
}
button, .button {
display: inline-block;
border: 0;
border-radius: 8px;
padding: 12px 20px;
margin: 6px;
font-size: 16px;
font-family: inherit;
cursor: pointer;
text-decoration: none;
}
.primary {
background: #1f73e8;
color: white;
}
.secondary {
background: #e9e9e9;
color: #222;
}
.hidden {
display: none !important;
}
.status {
margin-top: 24px;
padding: 16px;
border-radius: 8px;
background: #f0f0f0;
}
.spinner {
display: inline-block;
width: 16px;
height: 16px;
margin-right: 8px;
border: 2px solid #ccc;
border-top-color: #333;
border-radius: 50%;
animation: spin 0.7s linear infinite;
vertical-align: -3px;
}
.download {
margin-top: 24px;
padding: 20px;
border-radius: 8px;
background: #f5f5f5;
}
.room {
font-family: monospace;
word-break: break-all;
}
.error {
padding: 16px;
border-radius: 8px;
background: #fff0f0;
color: #900;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@media (prefers-color-scheme: dark) {
body {
background: #181818;
color: #eee;
}
.card {
background: #242424;
}
p {
color: #bbb;
}
.secondary {
background: #444;
color: #eee;
}
.status, .download {
background: #303030;
}
.spinner {
border-color: #555;
border-top-color: #ddd;
}
}
</style>
</head>
<body>
<div class="card">
<!-- NORMALER LAUNCHER -->
<div id="launcher" class="hidden">
<h1>Jitsi Meeting</h1>
<p>
Das Meeting <span class="room"><strong id="roomName"></strong></span> wird geöffnet.
</p>
<button id="openButton" class="button primary" type="button">Meeting öffnen</button>
</div>
<!-- STARTVORGANG -->
<div id="starting" class="hidden">
<h1>Jitsi Meet wird gestartet …</h1>
<div class="status">
<span class="spinner"></span>
Der Jitsi Meet Desktop-Client wird geöffnet.
</div>
</div>
<!-- FALLBACK -->
<div id="fallback" class="hidden">
<h1>Jitsi Meet konnte nicht geöffnet werden</h1>
<p>
Der Jitsi Meet Desktop-Client wurde möglicherweise nicht gestartet.
</p>
<button id="retryButton" class="button primary" type="button">Erneut versuchen</button>
<!-- Windows -->
<div id="windowsDownload" class="download hidden">
<h2>Jitsi Meet für Windows</h2>
<p>
Installiere den Jitsi Meet Desktop-Client und versuche anschließend erneut, das Meeting zu öffnen.
</p>
<a id="windowsLink" class="button secondary" target="_blank" rel="noopener noreferrer">Jitsi für Windows herunterladen</a>
</div>
<!-- macOS -->
<div id="macosDownload" class="download hidden">
<h2>Jitsi Meet für macOS</h2>
<p>
Installiere den Jitsi Meet Desktop-Client und versuche anschließend erneut, das Meeting zu öffnen.
</p>
<a id="macosLink" class="button secondary" target="_blank" rel="noopener noreferrer">Jitsi für macOS herunterladen</a>
</div>
<!-- Linux / unbekannt -->
<div id="otherDownload" class="download hidden">
<h2>Jitsi Meet Desktop</h2>
<p>
Für dein Betriebssystem ist kein Desktop-Download hinterlegt.
</p>
</div>
<!-- Browser -->
<div class="download">
<h2>Im Browser teilnehmen</h2>
<p>
Du kannst dem Meeting auch direkt über deinen Browser beitreten.
</p>
<a id="browserLink" class="button secondary">Im Browser beitreten</a>
</div>
</div>
<!-- FEHLER -->
<div id="invalidRoom" class="hidden">
<h1>Kein Meeting angegeben</h1>
<div class="error">
Bitte gib ein Meeting über den Parameter <code>?room=...</code> an.
</div>
</div>
</div>
<script>
"use strict";
/* KONFIGURATION */
const CONFIG = {
/*
* Deine Jitsi-Domain.
*
* Absichtlich fest eingetragen und nicht aus
* window.location.host übernommen.
*/
jitsiHost: "meet.bnvb-ingenieure.de",
/*
* Timeout für den Versuch, den Desktop-Client
* zu starten.
*/
launchTimeout: 1750,
/* HIER DIE TATSÄCHLICHEN DOWNLOAD-URLS EINTRAGEN. */
downloads: {
windows: "https://github.com/jitsi/jitsi-meet-electron/releases",
macos: "https://github.com/jitsi/jitsi-meet-electron/releases"
}
};
/* DOM-ELEMENTE */
const launcher = document.getElementById("launcher");
const starting = document.getElementById("starting");
const fallback = document.getElementById("fallback");
const invalidRoom = document.getElementById("invalidRoom");
const roomName = document.getElementById("roomName");
const openButton = document.getElementById("openButton");
const retryButton = document.getElementById("retryButton");
const browserLink = document.getElementById("browserLink");
const windowsDownload = document.getElementById("windowsDownload");
const macosDownload = document.getElementById("macosDownload");
const otherDownload = document.getElementById("otherDownload");
const windowsLink = document.getElementById("windowsLink");
const macosLink = document.getElementById("macosLink");
/*
* ============================================================
* ROOM AUS QUERY-PARAMETER
* ============================================================
*
* Beispiel:
*
* https://meet.bnvb-ingenieure.de/launch.html?room=abc-defg-hij
*
* ergibt:
*
* abc-defg-hij
*
* ============================================================
*/
function getRoom() {
const params = new URLSearchParams(window.location.search);
const room = params.get("room");
if (!room) {
return null;
}
/*
* Leerzeichen etc. verhindern.
*/
const trimmed = room.trim();
if (!trimmed) {
return null;
}
return trimmed;
}
/*
* ============================================================
* ROOM VALIDIEREN
* ============================================================
*
* Jitsi-Roomnamen können je nach Konfiguration relativ
* unterschiedliche Zeichen enthalten.
*
* Wir verhindern hier lediglich Steuerzeichen und
* offensichtliche URL-Manipulation.
* ============================================================
*/
function isValidRoom(room) {
if (!room) {
return false;
}
/*
* Keine Steuerzeichen.
*/
if (/[\u0000-\u001F\u007F]/.test(room)) {
return false;
}
/*
* Keine Slash-Zeichen.
*
* Falls deine Jitsi-Konfiguration Räume mit Slashes
* unterstützt, diese Prüfung entsprechend entfernen.
*/
if (room.includes("/")) {
return false;
}
return true;
}
/* JITSI WEB URL */
function createBrowserUrl(room) {
return ("https://" + CONFIG.jitsiHost + "/" + encodeURIComponent(room));
}
/* JITSI DESKTOP PROTOCOL URL */
function createAppUrl(room) {
return ("jitsi-meet://" + CONFIG.jitsiHost + "/" + encodeURIComponent(room));
}
/* BETRIEBSSYSTEM ERKENNEN */
function detectOperatingSystem() {
const ua = navigator.userAgent;
/* iPhone / iPad / iPod */
if (
/iPhone|iPad|iPod/i.test(ua) ||
(
navigator.platform === "MacIntel" &&
navigator.maxTouchPoints > 1
)
) {
return "ios";
}
/* Android */
if (/Android/i.test(ua)) {
return "android";
}
/* Windows */
if (/Windows/i.test(ua)) {
return "windows";
}
/* macOS */
if (
/Macintosh|Mac OS X/i.test(ua)
) {
return "macos";
}
/* Linux */
if (/Linux/i.test(ua)) {
return "linux";
}
return "unknown";
}
/* BROWSER ERKENNEN */
function detectBrowser() {
const ua = navigator.userAgent;
if (/Edg\//i.test(ua)) {
return "edge";
}
if (/Firefox\//i.test(ua)) {
return "firefox";
}
if (
/Chrome\//i.test(ua) &&
!/Edg\//i.test(ua)
) {
return "chrome";
}
if (
/Safari\//i.test(ua) &&
!/Chrome\//i.test(ua) &&
!/Chromium\//i.test(ua)
) {
return "safari";
}
return "unknown";
}
/* DOWNLOADS */
function configureDownloads() {
const os = detectOperatingSystem();
windowsDownload.classList.add("hidden");
macosDownload.classList.add("hidden");
otherDownload.classList.add("hidden");
/* Windows */
if (os === "windows") {
windowsLink.href = CONFIG.downloads.windows;
windowsDownload.classList.remove("hidden");
return;
}
/* macOS */
if (os === "macos") {
macosLink.href = CONFIG.downloads.macos;
macosDownload.classList.remove("hidden");
return;
}
/* Alles andere */
otherDownload.classList.remove("hidden");
}
/* FALLBACK ANZEIGEN */
function showFallback() {
starting.classList.add("hidden");
launcher.classList.add("hidden");
fallback.classList.remove("hidden");
configureDownloads();
}
/* JITSI DESKTOP STARTEN */
function launchJitsi() {
const room = getRoom();
if (!isValidRoom(room)) {
return;
}
const protocolUrl = createAppUrl(room);
const browserUrl = createBrowserUrl(room);
/* Browser-Fallback vorbereiten. */
browserLink.href = browserUrl;
/* UI auf "Startvorgang" setzen. */
launcher.classList.add("hidden");
fallback.classList.add("hidden");
starting.classList.remove("hidden");
let completed = false;
let timeout = null;
/* Unsichtbarer iframe für den Protocol Handler. */
const iframe = document.createElement("iframe");
iframe.style.display = "none";
/* Aufräumen. */
function cleanup() {
if (completed) {
return;
}
completed = true;
if (timeout !== null) {
clearTimeout(timeout);
}
iframe.remove();
}
/*
* --------------------------------------------------------
* WICHTIG:
*
* Kein "blur" mehr als Erfolgskriterium verwenden.
*
* Der Browser kann beim Protocol-Dialog ebenfalls
* blur auslösen.
*
* Wenn der Benutzer den Dialog abbricht, bleibt die
* Seite sichtbar und unser Timeout läuft weiter.
* --------------------------------------------------------
*/
document.body.appendChild(iframe);
/* Protocol Handler starten. */
try {
iframe.src = protocolUrl;
} catch (error) {
console.error(
"Fehler beim Öffnen des Jitsi Protocol Handlers:",
error
);
cleanup();
showFallback();
return;
}
/*
* --------------------------------------------------------
* FALLBACK
* --------------------------------------------------------
*
* Wir warten unabhängig davon, ob der Browser kurz
* den Fokus verliert.
*
* Wenn Jitsi erfolgreich startet:
*
* -> Jitsi kommt in den Vordergrund
* -> Benutzer sieht diese Seite nicht mehr
*
* Wenn der Benutzer den Browser-Dialog abbricht:
*
* -> diese Seite bleibt sichtbar
* -> Timeout läuft ab
* -> Fallback wird angezeigt
* --------------------------------------------------------
*/
timeout = setTimeout(() => {
if (completed) {
return;
}
cleanup();
showFallback();
}, CONFIG.launchTimeout);
}
/* INITIALISIERUNG */
function initialize() {
const room = getRoom();
/* Kein room-Parameter */
if (!isValidRoom(room)) {
invalidRoom.classList.remove("hidden");
return;
}
/* Room anzeigen. */
roomName.textContent = room;
/* Browser-Fallback vorbereiten. */
browserLink.href = createBrowserUrl(room);
/* Launcher anzeigen. */
launcher.classList.remove("hidden");
/* Debug-Informationen. */
console.log(
"Jitsi Launcher",
{
room: room,
operatingSystem: detectOperatingSystem(),
browser: detectBrowser(),
protocol: createAppUrl(room),
browserUrl: createBrowserUrl(room)
}
);
}
/* EVENTS */
openButton.addEventListener("click", launchJitsi);
retryButton.addEventListener("click", launchJitsi);
/* Start */
initialize();
</script>
</body>
</html>