Adressdaten-Bereich, Mock hinter einem Profil, Webhook
Drei Stränge, die sich über dieselben Dateien ziehen (HomePage, index.css, README, application.properties) und deshalb nicht getrennt committet werden können, ohne einen nicht übersetzbaren Zwischenstand zu hinterlassen: Adressdaten: Der Bereich zeigt den gesamten Bestand des globalen Telefon- buchs - zuerst aus dem Adress-Cache der App (Protokoll 8), sonst über das Kommando "addresses" und ersatzweise aus rund 110 Einzelabfragen hinter einem Wartedialog. Der Mock gibt denselben Bestand heraus und lässt sich über /api/mock/address-cache leeren, um die Rückfallebene zu prüfen. Mock hinter dem Profil "mock": Seine Bohnen (/ws und /api/mock/**) hängen jetzt an @Profile, sind ohne das Profil also nicht vorhanden. Damit kann der Mock nicht versehentlich in einer Produktivumgebung mitlaufen; das Container-Image setzt das Profil nicht. Je ein Test hält beide Richtungen fest. Webhook: POST /api/webhook nimmt beliebiges JSON eines fremden Systems an und reicht es über Server-Sent Events an die offenen Browser weiter, wo es der neue Bereich "Webhook" unverändert anzeigt. Der WebSocket kam dafür nicht in Frage - er gehört der SwyxTray-App. Das Backend hält die letzten 50 Nachrichten vor und liefert sie beim Wiederverbinden anhand der Last-Event-ID nach; ein Heartbeat und X-Accel-Buffering: no halten die Verbindung durch Reverse Proxys hindurch offen. Ein Token (app.webhook.token) ist vorgesehen, aber nicht voreingestellt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
import {
|
||||
COMMANDS,
|
||||
CONTACT_QUERY_MAX_LENGTH,
|
||||
parseAddresses,
|
||||
parseHello,
|
||||
parseResult,
|
||||
parseSnapshot,
|
||||
type Contact,
|
||||
type HelloMessage,
|
||||
type ResultMessage,
|
||||
type SnapshotMessage,
|
||||
@@ -20,6 +23,8 @@ interface Listeners {
|
||||
status: (status: ConnectionStatus) => void
|
||||
hello: (hello: HelloMessage) => void
|
||||
snapshot: (snapshot: SnapshotMessage) => void
|
||||
/** Unaufgeforderter Adress-Cache der App; auch eine leere Liste zählt. */
|
||||
addresses: (contacts: Contact[]) => void
|
||||
raw: (message: RawMessage) => void
|
||||
error: (message: string) => void
|
||||
}
|
||||
@@ -50,6 +55,7 @@ export class SwyxTrayClient {
|
||||
status: new Set(),
|
||||
hello: new Set(),
|
||||
snapshot: new Set(),
|
||||
addresses: new Set(),
|
||||
raw: new Set(),
|
||||
error: new Set(),
|
||||
}
|
||||
@@ -179,6 +185,35 @@ export class SwyxTrayClient {
|
||||
return this.send(COMMANDS.closeTab, { tabId }, TAB_TIMEOUT_MS)
|
||||
}
|
||||
|
||||
/**
|
||||
* Durchsucht die Adressdaten des Swyx-Clients. Gesucht wird als
|
||||
* Teilzeichenkette in Name und Rufnummer; die App liefert höchstens 100
|
||||
* Treffer und kürzt ohne Hinweis. Ein leerer
|
||||
* Suchbegriff wäre ein Fehler der App – er wird hier gar nicht erst gesendet.
|
||||
*/
|
||||
searchContacts(query: string): Promise<ResultMessage> {
|
||||
const wanted = query.trim()
|
||||
if (!wanted) {
|
||||
return Promise.reject(new Error('Bitte einen Suchbegriff angeben.'))
|
||||
}
|
||||
if (wanted.length > CONTACT_QUERY_MAX_LENGTH) {
|
||||
return Promise.reject(
|
||||
new Error(`Der Suchbegriff darf höchstens ${CONTACT_QUERY_MAX_LENGTH} Zeichen lang sein.`),
|
||||
)
|
||||
}
|
||||
return this.send(COMMANDS.contacts, { query: wanted })
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt den Adress-Cache der App am Stück – ohne Suchbegriff und ohne Deckel.
|
||||
* Ältere App-Versionen kennen das Kommando nicht und antworten mit
|
||||
* `"Unbekanntes Kommando 'addresses'."`; die Startseite weicht dann auf die
|
||||
* Einzelabfragen aus.
|
||||
*/
|
||||
listAddresses(): Promise<ResultMessage> {
|
||||
return this.send(COMMANDS.addresses)
|
||||
}
|
||||
|
||||
ping(): Promise<ResultMessage> {
|
||||
return this.send(COMMANDS.ping)
|
||||
}
|
||||
@@ -270,14 +305,22 @@ export class SwyxTrayClient {
|
||||
return
|
||||
}
|
||||
|
||||
// Die App schickt keine Ereignisse: Jede Änderung – auch ein eingehender
|
||||
// Anruf – kommt als vollständiger Snapshot.
|
||||
// Die App schickt keine Anruf-Ereignisse: Jede Änderung – auch ein
|
||||
// eingehender Anruf – kommt als vollständiger Snapshot.
|
||||
const snapshot = parseSnapshot(parsed)
|
||||
if (snapshot) {
|
||||
this.emit('snapshot', snapshot)
|
||||
return
|
||||
}
|
||||
|
||||
// Der Adress-Cache dagegen kommt unaufgefordert – beim Verbinden und wenn
|
||||
// die App ihn erneuert.
|
||||
const addresses = parseAddresses(parsed)
|
||||
if (addresses) {
|
||||
this.emit('addresses', addresses)
|
||||
return
|
||||
}
|
||||
|
||||
const hello = parseHello(parsed)
|
||||
if (hello) {
|
||||
this.emit('hello', hello)
|
||||
|
||||
Reference in New Issue
Block a user