initial version
This commit is contained in:
1 parent
93dc6ccc07
commit
34df35d54c
1 file changed
+609
+609
@@ -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>
|
||||||
Reference in new issue
Block a user