From 9cd011e7bf03916de6e193ac24bf9f9be0c603ef Mon Sep 17 00:00:00 2001 From: fraxken Date: Thu, 27 Nov 2025 16:26:40 +0100 Subject: [PATCH] refactor: implement a new WebSocketClient class --- public/components/views/search/search.js | 6 +- public/core/search-nav.js | 4 +- public/main.js | 139 ++++++++++-------- public/websocket.js | 48 ++++++ .../server/src/websocket/commands/search.ts | 15 +- .../server/src/websocket/websocket.types.ts | 7 +- 6 files changed, 151 insertions(+), 68 deletions(-) create mode 100644 public/websocket.js diff --git a/public/components/views/search/search.js b/public/components/views/search/search.js index b03867e8..bc73c2ae 100644 --- a/public/components/views/search/search.js +++ b/public/components/views/search/search.js @@ -230,7 +230,7 @@ export class SearchView { const pkgSpanElement = document.createElement("span"); pkgSpanElement.innerHTML = `${name}@${version}${local ? " local" : ""}`; pkgSpanElement.addEventListener("click", () => { - window.socket.send(JSON.stringify({ action: "SEARCH", pkg })); + window.socket.commands.search(pkg); }, { once: true }); const removeButton = createDOMElement("button", { @@ -239,7 +239,7 @@ export class SearchView { }); removeButton.addEventListener("click", (event) => { event.stopPropagation(); - window.socket.send(JSON.stringify({ action: "REMOVE", pkg })); + window.socket.commands.remove(pkg); }, { once: true }); pkgElement.append(pkgSpanElement, removeButton); @@ -250,7 +250,7 @@ export class SearchView { fetchPackage(packageName, version) { const pkg = `${packageName}@${version}`; - window.socket.send(JSON.stringify({ action: "SEARCH", pkg })); + window.socket.commands.search(pkg); } async fetchPackageVersions(packageName) { diff --git a/public/core/search-nav.js b/public/core/search-nav.js index 3e90d24f..3db36515 100644 --- a/public/core/search-nav.js +++ b/public/core/search-nav.js @@ -77,7 +77,7 @@ function initPackagesNavigation(data) { } pkgElement.addEventListener("click", () => { if (window.activePackage !== pkg) { - window.socket.send(JSON.stringify({ pkg, action: "SEARCH" })); + window.socket.commands.search(pkg); } }); @@ -118,7 +118,7 @@ function renderPackageRemoveButton(packageName, options) { removeButton.addEventListener("click", (event) => { event.stopPropagation(); - window.socket.send(JSON.stringify({ action: "REMOVE", pkg: packageName })); + window.socket.commands.remove(packageName); if (hasExactly2Packages) { document diff --git a/public/main.js b/public/main.js index 9cefe48a..39b15f80 100644 --- a/public/main.js +++ b/public/main.js @@ -17,6 +17,7 @@ import { i18n } from "./core/i18n.js"; import { initSearchNav } from "./core/search-nav.js"; import * as utils from "./common/utils.js"; import { EVENTS } from "./core/events.js"; +import { WebSocketClient } from "./websocket.js"; let secureDataSet; let nsn; @@ -34,70 +35,85 @@ document.addEventListener("DOMContentLoaded", async() => { window.wiki = new Wiki(); await init(); - window.dispatchEvent(new CustomEvent(EVENTS.SETTINGS_SAVED, { detail: window.settings.config })); + window.dispatchEvent( + new CustomEvent(EVENTS.SETTINGS_SAVED, { + detail: window.settings.config + }) + ); onSettingsSaved(window.settings.config); - window.socket = new WebSocket(`ws://${window.location.hostname}:1338`); - window.socket.addEventListener("message", async(event) => { - const data = JSON.parse(event.data); - console.log(`[WEBSOCKET] data status = '${data.status || "NONE"}'`); - - if (data.rootDependencyName) { - // TODO: implement rootDependency as a whole spec in scanner - const rootDepVersion = Object.keys(data.dependencies[data.rootDependencyName].versions)[0]; - window.activePackage = data.rootDependencyName + "@" + rootDepVersion; - - await init({ navigateToNetworkView: true }); - initSearchNav(data, { - initFromZero: false, - searchOptions: { - nsn, secureDataSet - } - }); - } - else if (data.status === "INIT" || data.status === "RELOAD") { - window.scannedPackageCache = data.availables; - window.recentPackageCache = data.lru; - console.log( - "[INFO] Older packages are loaded!", - window.scannedPackageCache - ); - console.log( - "[INFO] Recent packages are loaded!", - window.recentPackageCache - ); - - initSearchNav(data, { - searchOptions: { - nsn, secureDataSet - } - }); - searchview.mount(); - searchview.initialize(); - const nsnActivePackage = secureDataSet.linker.get(0); - const nsnRootPackage = nsnActivePackage ? `${nsnActivePackage.name}@${nsnActivePackage.version}` : null; - if (data.status === "RELOAD" && nsnRootPackage !== null && nsnRootPackage !== window.activePackage) { - // it means we removed the previous active package, which is still active in network, so we need to re-init - await init(); - - // FIXME: initSearchNav is called twice, we need to fix this - initSearchNav(data, { - searchOptions: { - nsn, secureDataSet - } - }); - } - } - else if (data.status === "SCAN") { - searchview.onScan(data.pkg); + const socket = new WebSocketClient(`ws://${window.location.hostname}:1338`); + socket.addEventListener("PAYLOAD", onSocketPayload); + socket.addEventListener("INIT", onSocketInitOrReload); + socket.addEventListener("RELOAD", onSocketInitOrReload); + socket.addEventListener("SCAN", (event) => { + const data = event.detail; + + searchview.onScan(data.pkg); + }); +}); + +async function onSocketPayload(event) { + const data = event.detail; + const { payload } = data; + + // TODO: implement rootDependency as a whole spec in scanner + const rootDepVersion = Object.keys(payload.dependencies[payload.rootDependencyName].versions)[0]; + window.activePackage = payload.rootDependencyName + "@" + rootDepVersion; + + await init({ navigateToNetworkView: true }); + initSearchNav(payload, { + initFromZero: false, + searchOptions: { + nsn, + secureDataSet } }); +} - window.onbeforeunload = () => { - window.socket.onclose = () => void 0; - window.socket.close(); - }; -}); +async function onSocketInitOrReload(event) { + const data = event.detail; + + window.scannedPackageCache = data.availables; + window.recentPackageCache = data.lru; + console.log( + "[INFO] Older packages are loaded!", + window.scannedPackageCache + ); + console.log( + "[INFO] Recent packages are loaded!", + window.recentPackageCache + ); + + initSearchNav(data, { + searchOptions: { + nsn, + secureDataSet + } + }); + searchview.mount(); + searchview.initialize(); + + const nsnActivePackage = secureDataSet.linker.get(0); + const nsnRootPackage = nsnActivePackage ? + `${nsnActivePackage.name}@${nsnActivePackage.version}` : + null; + if ( + data.status === "RELOAD" && + nsnRootPackage !== null && + nsnRootPackage !== window.activePackage + ) { + // it means we removed the previous active package, which is still active in network, so we need to re-init + await init(); + + // FIXME: initSearchNav is called twice, we need to fix this + initSearchNav(data, { + searchOptions: { + nsn, secureDataSet + } + }); + } +} async function init(options = {}) { const { navigateToNetworkView = false } = options; @@ -246,6 +262,11 @@ function onSettingsSaved(defaultConfig = null) { secureDataSet.FLAGS, secureDataSet.theme ); + + if (!nsn) { + return; + } + const { nodes } = secureDataSet.build(); nsn.nodes.update(nodes.get()); const rootNode = secureDataSet.linker.get(0); diff --git a/public/websocket.js b/public/websocket.js new file mode 100644 index 00000000..259d78ac --- /dev/null +++ b/public/websocket.js @@ -0,0 +1,48 @@ + +export class WebSocketClient extends EventTarget { + /** @type {WebSocket} */ + client; + + constructor( + url + ) { + super(); + this.client = new WebSocket(url); + this.client.addEventListener("message", this.#messageHandler.bind(this)); + this.commands = { + search: (pkg) => this.send({ action: "SEARCH", pkg }), + remove: (pkg) => this.send({ action: "REMOVE", pkg }) + }; + + window.socket = this; + window.onbeforeunload = () => { + this.close(); + }; + } + + send(data) { + this.client.send(JSON.stringify(data)); + } + + #messageHandler(event) { + const data = JSON.parse(event.data); + if (!data.status) { + console.warn( + "[WEBSOCKET] Received data without status:", + data + ); + + return; + } + + console.log(`[WEBSOCKET] data status = '${data.status}'`); + this.dispatchEvent( + new CustomEvent(data.status, { detail: data }) + ); + } + + close() { + this.client.onclose = () => void 0; + this.client.close(); + } +} diff --git a/workspaces/server/src/websocket/commands/search.ts b/workspaces/server/src/websocket/commands/search.ts index 5e8b1e95..67247170 100644 --- a/workspaces/server/src/websocket/commands/search.ts +++ b/workspaces/server/src/websocket/commands/search.ts @@ -27,7 +27,10 @@ export async function* search( lastUsed: { ...cacheList.lastUsed, [pkg]: Date.now() } }; await cache.updatePayloadsList(updatedList); - yield cachedPayload; + yield { + status: "PAYLOAD" as const, + payload: cachedPayload + }; if (cache.startFromZero) { yield { @@ -51,7 +54,10 @@ export async function* search( }; await cache.updatePayloadsList(updatedList); - yield cachedPayload; + yield { + status: "PAYLOAD" as const, + payload: cachedPayload + }; yield { status: "RELOAD" as const, ...updatedList @@ -89,7 +95,10 @@ export async function* search( }; await cache.updatePayloadsList(updatedList); - yield payload; + yield { + status: "PAYLOAD" as const, + payload + }; yield { status: "RELOAD" as const, ...updatedList diff --git a/workspaces/server/src/websocket/websocket.types.ts b/workspaces/server/src/websocket/websocket.types.ts index 6897d873..b5a7dcef 100644 --- a/workspaces/server/src/websocket/websocket.types.ts +++ b/workspaces/server/src/websocket/websocket.types.ts @@ -6,6 +6,11 @@ import type { Payload } from "@nodesecure/scanner"; // Import Internal Dependencies import type { logger } from "../logger.js"; +type PayloadResponse = { + status: "PAYLOAD"; + payload: Payload; +}; + /** * A (NodeSecure) scan is in progress */ @@ -22,7 +27,7 @@ type CachedResponse = { } & PayloadsList; export type WebSocketResponse = - | Payload + | PayloadResponse | CachedResponse | ScanResponse;