Windows-Tray-Anwendung (CLMgr-Anbindung an SwyxIt!) samt WebSocket-Zugang, Firefox-Erweiterung und Browser-Beispielclient.
185 lines
6.4 KiB
HTML
185 lines
6.4 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>SwyxTray — WebSocket-Beispiel</title>
|
|
<style>
|
|
body { font: 15px/1.5 system-ui, sans-serif; max-width: 46rem; margin: 2rem auto; padding: 0 1rem; }
|
|
fieldset { border: 1px solid #ccc; border-radius: 6px; margin-bottom: 1rem; }
|
|
input, button { font: inherit; padding: .35rem .6rem; }
|
|
input[type=text] { width: 22rem; }
|
|
#zustand { font-weight: 600; }
|
|
#protokoll { background: #f6f6f6; padding: .75rem; height: 16rem; overflow: auto;
|
|
white-space: pre-wrap; font: 13px/1.45 ui-monospace, monospace; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<h1>SwyxTray — WebSocket-Beispiel</h1>
|
|
|
|
<p>
|
|
<strong>Zur Zeit sind alle Prüfungen abgeschaltet</strong> — es genügt
|
|
<code>ws://127.0.0.1:17654/</code> ohne Token, auch beim Öffnen dieser Seite
|
|
direkt aus dem Dateisystem.
|
|
</p>
|
|
<p>
|
|
Von einem <strong>anderen Rechner</strong> aus: in
|
|
<code>websocket.json</code> <code>"AllowRemoteAccess": true</code> setzen, App
|
|
neu starten und die LAN-Adresse verwenden
|
|
(<code>ws://192.168.x.y:17654/…</code>). Diese Seite muss dann über
|
|
<code>http://</code> ausgeliefert werden — aus einer HTTPS-Seite heraus
|
|
blockiert der Browser <code>ws://</code> auf eine entfernte Adresse als Mixed
|
|
Content. Für Loopback gilt diese Ausnahme, für die LAN-Adresse nicht.
|
|
</p>
|
|
<p>
|
|
Sind die Prüfungen wieder eingeschaltet, muss die Seite von einem Origin
|
|
geladen werden, der in <code>%LOCALAPPDATA%\SwyxTray\websocket.json</code>
|
|
unter <code>AllowedOrigins</code> steht, und die Adresse braucht das Token.
|
|
Über <code>file://</code> sendet der Browser den Origin <code>null</code> —
|
|
dann muss dort <code>"null"</code> eingetragen sein.
|
|
</p>
|
|
|
|
<fieldset>
|
|
<legend>Verbindung</legend>
|
|
<input type="text" id="url" placeholder="ws://127.0.0.1:17654/?token=…">
|
|
<button id="verbinden">Verbinden</button>
|
|
<p>Status: <span id="zustand">nicht verbunden</span></p>
|
|
</fieldset>
|
|
|
|
<fieldset>
|
|
<legend>Steuerung</legend>
|
|
<input type="text" id="nummer" placeholder="+49 30 1234567">
|
|
<button data-cmd="call">Anrufen</button>
|
|
<button data-cmd="answer">Annehmen</button>
|
|
<button data-cmd="hangup">Auflegen</button>
|
|
<button data-cmd="hold">Halten</button>
|
|
<button data-cmd="status">Status</button>
|
|
<p>
|
|
<input type="text" id="fenstertitel" placeholder="Tab-Titel (optional)">
|
|
<input type="text" id="fokusurl" placeholder="https://… (Pflicht)">
|
|
<button data-cmd="focus">Fokussieren</button>
|
|
</p>
|
|
</fieldset>
|
|
|
|
<fieldset>
|
|
<legend>Tabs (Firefox-Plugin)</legend>
|
|
<p>
|
|
<button data-cmd="tabs">Tabs anzeigen</button>
|
|
<input type="text" id="taburl" placeholder="https://…">
|
|
<button data-cmd="opentab">Tab öffnen</button>
|
|
</p>
|
|
<ul id="tabliste"></ul>
|
|
</fieldset>
|
|
|
|
<div id="protokoll"></div>
|
|
|
|
<script>
|
|
let ws = null;
|
|
let naechsteId = 1;
|
|
let absichtlichGetrennt = false;
|
|
const gesendet = new Map(); // id → cmd, um Antworten dem Kommando zuzuordnen
|
|
|
|
const $ = id => document.getElementById(id);
|
|
const log = text => {
|
|
$("protokoll").textContent += text + "\n";
|
|
$("protokoll").scrollTop = $("protokoll").scrollHeight;
|
|
};
|
|
|
|
function verbinden() {
|
|
absichtlichGetrennt = false;
|
|
ws = new WebSocket($("url").value.trim(), ["swyxtray.v1"]);
|
|
|
|
ws.onopen = () => { $("zustand").textContent = "verbunden"; log("→ verbunden"); };
|
|
|
|
ws.onmessage = e => {
|
|
const m = JSON.parse(e.data);
|
|
log("← " + e.data);
|
|
|
|
// Der Zustand kommt unaufgefordert bei jeder Änderung — die Seite muss
|
|
// ihn also nie selbst nachhalten.
|
|
if (m.type === "snapshot") {
|
|
const belegt = m.lines.filter(l => l.busy);
|
|
$("zustand").textContent = m.statusText +
|
|
(belegt.length ? " — " + belegt.map(l => `L${l.line}: ${l.stateText} ${l.peer}`).join(", ") : "");
|
|
}
|
|
|
|
// Anruf-Ereignisse kommen zusätzlich zum snapshot — hier reagiert eine
|
|
// echte Anwendung, z. B. mit dem Öffnen der Kundenakte zum Anrufer.
|
|
if (m.type === "call") {
|
|
const text = { incoming: "Eingehender Ruf", outgoing: "Ausgehender Ruf",
|
|
connected: "Gespräch verbunden", ended: "Gespräch beendet" }[m.event] ?? m.event;
|
|
log(`☎ ${text} — Leitung ${m.line}: ${m.peer}`);
|
|
}
|
|
|
|
if (m.type === "result") {
|
|
const cmd = gesendet.get(m.id);
|
|
gesendet.delete(m.id);
|
|
if (cmd === "tabs" && m.ok) zeigeTabs(m.tabs ?? []);
|
|
// Nach Öffnen oder Schließen die Liste auffrischen, sofern sie schon
|
|
// einmal angezeigt wurde — der Zustand kommt hier nicht von selbst.
|
|
if ((cmd === "opentab" || cmd === "closetab") && m.ok
|
|
&& $("tabliste").childElementCount > 0) senden("tabs");
|
|
}
|
|
};
|
|
|
|
ws.onerror = () => log("→ Fehler (Token? Origin? Läuft SwyxTray?)");
|
|
|
|
// WebSockets verbinden sich nicht von selbst neu — anders als EventSource.
|
|
ws.onclose = e => {
|
|
$("zustand").textContent = "getrennt";
|
|
log(`→ getrennt (Code ${e.code})`);
|
|
if (!absichtlichGetrennt) setTimeout(verbinden, 2000);
|
|
};
|
|
}
|
|
|
|
function senden(cmd, felder = {}) {
|
|
if (ws?.readyState !== WebSocket.OPEN) { log("→ nicht verbunden"); return; }
|
|
const nachricht = { id: naechsteId++, cmd, ...felder };
|
|
if (cmd === "call") nachricht.number = $("nummer").value.trim();
|
|
if (cmd === "focus") {
|
|
nachricht.title = $("fenstertitel").value.trim();
|
|
const url = $("fokusurl").value.trim();
|
|
if (url) nachricht.url = url;
|
|
}
|
|
if (cmd === "opentab") {
|
|
const url = $("taburl").value.trim();
|
|
if (url) nachricht.url = url;
|
|
}
|
|
gesendet.set(nachricht.id, cmd);
|
|
log("→ " + JSON.stringify(nachricht));
|
|
ws.send(JSON.stringify(nachricht));
|
|
}
|
|
|
|
// Baut die Liste per createElement auf — Titel und Adressen stammen aus dem
|
|
// Browser und gehören nicht ungeprüft in innerHTML.
|
|
function zeigeTabs(tabs) {
|
|
const liste = $("tabliste");
|
|
liste.replaceChildren();
|
|
for (const tab of tabs) {
|
|
const li = document.createElement("li");
|
|
const knopf = document.createElement("button");
|
|
knopf.textContent = "Schließen";
|
|
knopf.onclick = () => senden("closetab", { tabId: tab.id });
|
|
li.append(knopf, ` ${tab.active ? "▶ " : ""}${tab.title ?? ""} — ${tab.url ?? ""}`);
|
|
liste.append(li);
|
|
}
|
|
if (!tabs.length) {
|
|
const li = document.createElement("li");
|
|
li.textContent = "(keine Tabs)";
|
|
liste.append(li);
|
|
}
|
|
}
|
|
|
|
$("verbinden").onclick = () => {
|
|
absichtlichGetrennt = true;
|
|
ws?.close();
|
|
verbinden();
|
|
};
|
|
|
|
document.querySelectorAll("button[data-cmd]").forEach(
|
|
b => b.onclick = () => senden(b.dataset.cmd));
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|