// file: kb_app/frontend/ts/demo_ws.ts import * as bootstrap from "bootstrap"; import "simplebar"; import ResizeObserver from "resize-observer-polyfill"; import { invoke } from "@tauri-apps/api/core"; import { listen, type UnlistenFn } from "@tauri-apps/api/event"; import { debug, takeoverConsole } from "@fltsci/tauri-plugin-tracing"; (window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap; (window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver; interface DemoWsEndpointSummary { name: string; resolvedUrl: string; provider: string; enabled: boolean; roles: string[]; } interface DemoWsStatusPayload { connectionState: string; endpointName: string | null; endpointUrl: string | null; currentSubscriptionId: number | null; currentSubscribeMethod: string | null; currentUnsubscribeMethod: string | null; currentNotificationMethod: string | null; eventCountTotal: number; notificationCountTotal: number; uiLogCount: number; suppressedLogCount: number; lastEventKind: string | null; } interface DemoWsSubscribeRequest { method: string; mode: string; target: string | null; filterJson: string | null; configJson: string | null; } function shortenLine(line: string, maxChars = 3000): string { if (line.length <= maxChars) { return line; } return `${line.slice(0, maxChars)} …[truncated ${line.length - maxChars} chars]`; } function appendLogLine(textarea: HTMLTextAreaElement, line: string): void { const now = new Date(); const timestamp = now.toLocaleTimeString("fr-CH", { hour12: false }); const safeLine = shortenLine(line, 3000); const existingLines = textarea.value === "" ? [] : textarea.value.split("\n"); existingLines.push(`[${timestamp}] ${safeLine}`); const maxLines = 800; const trimmedLines = existingLines.length > maxLines ? existingLines.slice(existingLines.length - maxLines) : existingLines; textarea.value = trimmedLines.join("\n"); textarea.scrollTop = textarea.scrollHeight; } function setStatusBadge(badge: HTMLSpanElement, state: string): void { badge.textContent = state; if (state === "Connected") { badge.className = "badge text-bg-success"; return; } if (state === "Connecting" || state === "Disconnecting") { badge.className = "badge text-bg-warning"; return; } badge.className = "badge text-bg-secondary"; } function methodSupportsTypedMode(method: string): boolean { return ["account", "block", "logs", "program", "signature"].includes(method); } function methodNeedsTarget(method: string): boolean { return ["account", "program", "signature"].includes(method); } function methodNeedsFilter(method: string): boolean { return ["block", "logs"].includes(method); } function methodNeedsConfig(method: string): boolean { return ["account", "block", "logs", "program", "signature"].includes(method); } function targetLabelForMethod(method: string): string { if (method === "account") return "Account pubkey"; if (method === "program") return "Program id"; if (method === "signature") return "Signature"; return "Target"; } function methodPreset(method: string, mode: string): { target: string; filterJson: string; configJson: string; } { if (method === "account") { return { target: "11111111111111111111111111111111", filterJson: "", configJson: mode === "typed" ? `{"encoding":"base64","commitment":"confirmed"}` : `{"encoding":"base64","commitment":"confirmed"}`, }; } if (method === "block") { return { target: "", filterJson: `{"mentionsAccountOrProgram":"11111111111111111111111111111111"}`, configJson: mode === "typed" ? `{"commitment":"confirmed","encoding":"base64","transactionDetails":"signatures","showRewards":false,"maxSupportedTransactionVersion":0}` : `{"commitment":"confirmed","encoding":"base64","transactionDetails":"signatures","showRewards":false,"maxSupportedTransactionVersion":0}`, }; } if (method === "logs") { return { target: "", filterJson: `{"mentions":["11111111111111111111111111111111"]}`, configJson: `{"commitment":"confirmed"}`, }; } if (method === "program") { return { target: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", filterJson: "", configJson: mode === "typed" ? `{"encoding":"base64","commitment":"confirmed","filters":[]}` : `{"encoding":"base64","commitment":"confirmed","filters":[]}`, }; } if (method === "signature") { return { target: "2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b", filterJson: "", configJson: `{"commitment":"confirmed","enableReceivedNotification":true}`, }; } return { target: "", filterJson: "", configJson: "", }; } function updateFormVisibility( methodSelect: HTMLSelectElement, modeSelect: HTMLSelectElement, targetGroup: HTMLDivElement, targetLabel: HTMLLabelElement, targetInput: HTMLInputElement, filterGroup: HTMLDivElement, filterTextarea: HTMLTextAreaElement, configGroup: HTMLDivElement, configTextarea: HTMLTextAreaElement, ): void { const method = methodSelect.value; const supportsTypedMode = methodSupportsTypedMode(method); modeSelect.disabled = !supportsTypedMode; if (!supportsTypedMode) { modeSelect.value = "typed"; } targetGroup.style.display = methodNeedsTarget(method) ? "" : "none"; filterGroup.style.display = methodNeedsFilter(method) ? "" : "none"; configGroup.style.display = methodNeedsConfig(method) ? "" : "none"; targetLabel.textContent = targetLabelForMethod(method); const preset = methodPreset(method, modeSelect.value); targetInput.value = preset.target; filterTextarea.value = preset.filterJson; configTextarea.value = preset.configJson; } function applyStatusToUi( status: DemoWsStatusPayload, statusBadge: HTMLSpanElement, stateText: HTMLSpanElement, endpointText: HTMLSpanElement, subscriptionText: HTMLSpanElement, eventCountText: HTMLSpanElement, notificationCountText: HTMLSpanElement, uiLogCountText: HTMLSpanElement, suppressedLogCountText: HTMLSpanElement, lastEventKindText: HTMLSpanElement, connectButton: HTMLButtonElement, disconnectButton: HTMLButtonElement, subscribeButton: HTMLButtonElement, unsubscribeButton: HTMLButtonElement, ): void { setStatusBadge(statusBadge, status.connectionState); stateText.textContent = status.connectionState; endpointText.textContent = status.endpointName && status.endpointUrl ? `${status.endpointName} (${status.endpointUrl})` : "-"; if (status.currentSubscriptionId !== null) { subscriptionText.textContent = `${status.currentSubscribeMethod ?? "?"} / #${status.currentSubscriptionId} / ${status.currentNotificationMethod ?? "?"}`; } else { subscriptionText.textContent = "-"; } eventCountText.textContent = String(status.eventCountTotal); notificationCountText.textContent = String(status.notificationCountTotal); uiLogCountText.textContent = String(status.uiLogCount); suppressedLogCountText.textContent = String(status.suppressedLogCount); lastEventKindText.textContent = status.lastEventKind ?? "-"; const isConnected = status.connectionState === "Connected"; const isBusy = status.connectionState === "Connecting" || status.connectionState === "Disconnecting"; connectButton.disabled = isConnected || isBusy; disconnectButton.disabled = !isConnected && status.connectionState !== "Disconnecting"; subscribeButton.disabled = !isConnected || isBusy; unsubscribeButton.disabled = !isConnected || isBusy || status.currentSubscriptionId === null; } document.addEventListener("DOMContentLoaded", async () => { void takeoverConsole(); const sidebarToggle = document.querySelector('#sidebarToggle'); if (sidebarToggle) { // restaurer l’état depuis localStorage if (localStorage.getItem('sidebar-toggle') === 'true') { document.body.classList.add('sidenav-toggled'); } sidebarToggle.addEventListener('click', (event) => { event.preventDefault(); document.body.classList.toggle('sidenav-toggled'); localStorage.setItem('sidebar-toggle', document.body.classList.contains('sidenav-toggled') ? 'true' : 'false'); }); } const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]'); Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)); const toastElList = document.querySelectorAll('.toast'); Array.from(toastElList).map(toastEl => new bootstrap.Toast(toastEl)); const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]'); Array.from(popoverTriggerList).map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl)); const gobackto = location.pathname + location.search; document.querySelectorAll('a[data-setlang]').forEach((a) => { const href = a.getAttribute("href"); if (!href) return; // pas de href => on ignore const url = new URL(href, location.origin); url.searchParams.set("gobackto", gobackto); // conserve une URL relative (path + query) a.setAttribute("href", url.pathname + "?" + url.searchParams.toString()); }); const endpointSelect = document.querySelector("#demoWsEndpointSelect"); const methodSelect = document.querySelector("#demoWsMethodSelect"); const modeSelect = document.querySelector("#demoWsModeSelect"); const targetGroup = document.querySelector("#demoWsTargetGroup"); const targetLabel = document.querySelector("#demoWsTargetLabel"); const targetInput = document.querySelector("#demoWsTargetInput"); const filterGroup = document.querySelector("#demoWsFilterGroup"); const filterTextarea = document.querySelector("#demoWsFilterTextarea"); const configGroup = document.querySelector("#demoWsConfigGroup"); const configTextarea = document.querySelector("#demoWsConfigTextarea"); const statusBadge = document.querySelector("#demoWsStatusBadge"); const stateText = document.querySelector("#demoWsStateText"); const endpointText = document.querySelector("#demoWsEndpointText"); const subscriptionText = document.querySelector("#demoWsSubscriptionText"); const requestText = document.querySelector("#demoWsRequestText"); const eventCountText = document.querySelector("#demoWsEventCountText"); const notificationCountText = document.querySelector("#demoWsNotificationCountText"); const uiLogCountText = document.querySelector("#demoWsUiLogCountText"); const suppressedLogCountText = document.querySelector("#demoWsSuppressedLogCountText"); const lastEventKindText = document.querySelector("#demoWsLastEventKindText"); const connectButton = document.querySelector("#demoWsConnectButton"); const disconnectButton = document.querySelector("#demoWsDisconnectButton"); const subscribeButton = document.querySelector("#demoWsSubscribeButton"); const unsubscribeButton = document.querySelector("#demoWsUnsubscribeButton"); const clearLogButton = document.querySelector("#demoWsClearLogButton"); const logTextarea = document.querySelector("#demoWsLogTextarea"); if ( !eventCountText || !notificationCountText || !uiLogCountText || !suppressedLogCountText || !lastEventKindText || !endpointSelect || !methodSelect || !modeSelect || !targetGroup || !targetLabel || !targetInput || !filterGroup || !filterTextarea || !configGroup || !configTextarea || !statusBadge || !stateText || !endpointText || !subscriptionText || !requestText || !connectButton || !disconnectButton || !subscribeButton || !unsubscribeButton || !clearLogButton || !logTextarea ) { debug("demo_ws UI controls not found"); return; } let unlistenLogEvent: UnlistenFn | null = null; let unlistenStatusEvent: UnlistenFn | null = null; try { unlistenLogEvent = await listen("demo-ws-log", (event) => { appendLogLine(logTextarea, event.payload); }); unlistenStatusEvent = await listen("demo-ws-status", (event) => { applyStatusToUi( event.payload, statusBadge, stateText, endpointText, subscriptionText, eventCountText, notificationCountText, uiLogCountText, suppressedLogCountText, lastEventKindText, connectButton, disconnectButton, subscribeButton, unsubscribeButton, ); }); } catch (error) { appendLogLine(logTextarea, `[ui] event listen error: ${String(error)}`); } try { const endpoints = await invoke("demo_ws_list_endpoints"); endpointSelect.innerHTML = ""; for (const endpoint of endpoints) { const option = document.createElement("option"); option.value = endpoint.name; option.textContent = `${endpoint.name} — ${endpoint.provider} — ${endpoint.resolvedUrl}`; option.disabled = !endpoint.enabled; endpointSelect.appendChild(option); } } catch (error) { appendLogLine(logTextarea, `[ui] endpoint list error: ${String(error)}`); } updateFormVisibility( methodSelect, modeSelect, targetGroup, targetLabel, targetInput, filterGroup, filterTextarea, configGroup, configTextarea, ); methodSelect.addEventListener("change", () => { updateFormVisibility( methodSelect, modeSelect, targetGroup, targetLabel, targetInput, filterGroup, filterTextarea, configGroup, configTextarea, ); }); modeSelect.addEventListener("change", () => { updateFormVisibility( methodSelect, modeSelect, targetGroup, targetLabel, targetInput, filterGroup, filterTextarea, configGroup, configTextarea, ); }); try { const status = await invoke("demo_ws_get_status"); applyStatusToUi( status, statusBadge, stateText, endpointText, subscriptionText, eventCountText, notificationCountText, uiLogCountText, suppressedLogCountText, lastEventKindText, connectButton, disconnectButton, subscribeButton, unsubscribeButton, ); } catch (error) { appendLogLine(logTextarea, `[ui] initial status error: ${String(error)}`); } appendLogLine(logTextarea, "[ui] demo_ws window loaded"); connectButton.addEventListener("click", async () => { try { const status = await invoke("demo_ws_connect", { endpointName: endpointSelect.value, }); applyStatusToUi( status, statusBadge, stateText, endpointText, subscriptionText, eventCountText, notificationCountText, uiLogCountText, suppressedLogCountText, lastEventKindText, connectButton, disconnectButton, subscribeButton, unsubscribeButton, ); } catch (error) { appendLogLine(logTextarea, `[ui] connect error: ${String(error)}`); } }); disconnectButton.addEventListener("click", async () => { try { const status = await invoke("demo_ws_disconnect"); applyStatusToUi( status, statusBadge, stateText, endpointText, subscriptionText, eventCountText, notificationCountText, uiLogCountText, suppressedLogCountText, lastEventKindText, connectButton, disconnectButton, subscribeButton, unsubscribeButton, ); } catch (error) { appendLogLine(logTextarea, `[ui] disconnect error: ${String(error)}`); } }); subscribeButton.addEventListener("click", async () => { const request: DemoWsSubscribeRequest = { method: methodSelect.value, mode: modeSelect.value, target: targetInput.value.trim() === "" ? null : targetInput.value.trim(), filterJson: filterTextarea.value.trim() === "" ? null : filterTextarea.value.trim(), configJson: configTextarea.value.trim() === "" ? null : configTextarea.value.trim(), }; try { const requestId = await invoke("demo_ws_subscribe", { request }); requestText.textContent = `request_id=${requestId}`; appendLogLine(logTextarea, `[ui] subscribe request sent: request_id=${requestId}`); } catch (error) { appendLogLine(logTextarea, `[ui] subscribe error: ${String(error)}`); } }); unsubscribeButton.addEventListener("click", async () => { try { const requestId = await invoke("demo_ws_unsubscribe_current"); requestText.textContent = `unsubscribe_request_id=${requestId}`; appendLogLine(logTextarea, `[ui] unsubscribe request sent: request_id=${requestId}`); } catch (error) { appendLogLine(logTextarea, `[ui] unsubscribe error: ${String(error)}`); } }); clearLogButton.addEventListener("click", () => { logTextarea.value = ""; }); window.addEventListener("beforeunload", () => { if (unlistenLogEvent) { unlistenLogEvent(); } if (unlistenStatusEvent) { unlistenStatusEvent(); } }); debug("demo_ws window loaded"); });