diff --git a/public/components/views/search/search.js b/public/components/views/search/search.js index bc73c2ae..9e35cec8 100644 --- a/public/components/views/search/search.js +++ b/public/components/views/search/search.js @@ -248,9 +248,8 @@ export class SearchView { } fetchPackage(packageName, version) { - const pkg = `${packageName}@${version}`; - - window.socket.commands.search(pkg); + const spec = `${packageName}@${version}`; + window.socket.commands.search(spec); } async fetchPackageVersions(packageName) { @@ -259,7 +258,7 @@ export class SearchView { return versions.reverse(); } - onScan(pkg) { + onScan(spec) { const searchViewForm = document.querySelector("#search--view form"); searchViewForm?.remove(); const containerResult = document.querySelector("#search--view .result-container"); @@ -268,7 +267,7 @@ export class SearchView { const searchViewContainer = document.querySelector("#search--view .container"); const scanInfo = document.createElement("div"); scanInfo.classList.add("scan-info"); - scanInfo.textContent = `Scanning ${pkg}.`; + scanInfo.textContent = `Scanning ${spec}`; const spinner = document.createElement("div"); spinner.classList.add("spinner"); searchViewContainer.appendChild(scanInfo); diff --git a/public/main.js b/public/main.js index 39b15f80..b7addfc3 100644 --- a/public/main.js +++ b/public/main.js @@ -49,7 +49,7 @@ document.addEventListener("DOMContentLoaded", async() => { socket.addEventListener("SCAN", (event) => { const data = event.detail; - searchview.onScan(data.pkg); + searchview.onScan(data.spec); }); }); @@ -73,9 +73,10 @@ async function onSocketPayload(event) { async function onSocketInitOrReload(event) { const data = event.detail; + const { cache } = data; - window.scannedPackageCache = data.availables; - window.recentPackageCache = data.lru; + window.scannedPackageCache = cache.availables; + window.recentPackageCache = cache.lru; console.log( "[INFO] Older packages are loaded!", window.scannedPackageCache @@ -85,7 +86,7 @@ async function onSocketInitOrReload(event) { window.recentPackageCache ); - initSearchNav(data, { + initSearchNav(cache, { searchOptions: { nsn, secureDataSet @@ -107,9 +108,10 @@ async function onSocketInitOrReload(event) { await init(); // FIXME: initSearchNav is called twice, we need to fix this - initSearchNav(data, { + initSearchNav(cache, { searchOptions: { - nsn, secureDataSet + nsn, + secureDataSet } }); } diff --git a/public/websocket.js b/public/websocket.js index 259d78ac..bdf382d8 100644 --- a/public/websocket.js +++ b/public/websocket.js @@ -10,8 +10,8 @@ export class WebSocketClient extends EventTarget { 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 }) + search: (spec) => this.send({ commandName: "SEARCH", spec }), + remove: (spec) => this.send({ commandName: "REMOVE", spec }) }; window.socket = this; diff --git a/workspaces/server/README.md b/workspaces/server/README.md index b19803d1..40370f9e 100644 --- a/workspaces/server/README.md +++ b/workspaces/server/README.md @@ -173,7 +173,7 @@ All static files (UI, assets, etc.) are served from the project root directory. > [!NOTE] > For more details on each endpoint, see the corresponding files in /src/endpoints. -## Websocket actions +## Websocket commands The `WebSocketServerInstanciator` class sets up and manages a WebSocket server for real-time communication with NodeSecure clients. It provides live updates and cache management features for package analysis. @@ -183,13 +183,13 @@ new WebSocketServerInstanciator(); - Initializes a WebSocket server on port 1338. - Listens for client connections and incoming messages. -- `SEARCH`: +### SEARCH **Request**: ```json { - "action": "SEARCH", - "pkg": "" + "commandName": "SEARCH", + "spec": "@" } ``` @@ -197,13 +197,13 @@ new WebSocketServerInstanciator(); Streams scan progress, payload data, and cache state updates. -- `REMOVE`: +### REMOVE **Request**: ```json { - "action": "REMOVE", - "pkg": "" + "commandName": "REMOVE", + "spec": "@" } ``` diff --git a/workspaces/server/src/websocket/commands/remove.ts b/workspaces/server/src/websocket/commands/remove.ts index 846ecb13..28eb7a80 100644 --- a/workspaces/server/src/websocket/commands/remove.ts +++ b/workspaces/server/src/websocket/commands/remove.ts @@ -8,111 +8,101 @@ import type { } from "../websocket.types.js"; export async function* remove( - pkg: string, + spec: string, context: WebSocketContext ): AsyncGenerator { const { cache, logger } = context; - logger.info(`[ws|remove](pkg: ${pkg})`); - - try { - const { mru, lru, current, lastUsed, root, availables } = await cache.payloadsList(); - delete lastUsed[pkg]; - if (availables.includes(pkg)) { - logger.info("[ws|remove] remove from availables"); - cache.removePayload(pkg); - const updatedList: PayloadsList = { - mru, - current, - lru, - lastUsed: { - ...lastUsed - }, - root, - availables: availables.filter((pkgName) => pkgName !== pkg) - }; - await cache.updatePayloadsList(updatedList); - - yield { - status: "RELOAD", - ...updatedList - }; - - return; - } - - logger.debug(`[ws|remove](lru: ${lru}|current: ${current})`); - - if (mru.length === 1 && lru.length === 0) { - throw new Error("Cannot remove the last package."); - } - - const mruIndex = mru.findIndex((pkgName) => pkgName === pkg); - const lruIndex = lru.findIndex((pkgName) => pkgName === pkg); - - if (mruIndex === -1 && lruIndex === -1) { - throw new Error("Package not found in cache."); - } + const { mru, lru, current, lastUsed, root, availables } = await cache.payloadsList(); + delete lastUsed[spec]; + if (availables.includes(spec)) { + logger.info("[ws|command.remove] remove from availables"); + cache.removePayload(spec); + const updatedList: PayloadsList = { + mru, + current, + lru, + lastUsed: { + ...lastUsed + }, + root, + availables: availables.filter((iterSpec) => iterSpec !== spec) + }; + await cache.updatePayloadsList(updatedList); + + yield { + status: "RELOAD", + cache: updatedList + }; + + return; + } - if (mruIndex > -1) { - logger.info("[ws|remove](remove from lru)"); - const updatedMru = mru.filter((pkgName) => pkgName !== pkg); - if (lru.length > 0) { - // We need to move the first lru package to the mru list - const olderLruPkg = lru.sort((a, b) => { - const aDate = lastUsed[a]; - const bDate = lastUsed[b]; + logger.debug(`[ws|command.remove](lru: ${lru}|current: ${current})`); - return aDate - bDate; - }); - updatedMru.push(olderLruPkg[0]); - lru.splice(lru.indexOf(olderLruPkg[0]), 1); - } + if (mru.length === 1 && lru.length === 0) { + throw new Error("Cannot remove the last package."); + } - const updatedList: PayloadsList = { - mru: updatedMru, - lru, - lastUsed: { - ...lastUsed - }, - current: current === pkg ? updatedMru[0] : current, - root, - availables - }; - await cache.updatePayloadsList(updatedList); + const mruIndex = mru.findIndex((iterSpec) => iterSpec === spec); + const lruIndex = lru.findIndex((iterSpec) => iterSpec === spec); - yield { - status: "RELOAD", - ...updatedList - }; - } - else { - logger.info("[ws|remove](remove from lru)"); - const updatedLru = lru.filter((pkgName) => pkgName !== pkg); - const updatedList: PayloadsList = { - mru, - lru: updatedLru, - availables, - lastUsed: { - ...lastUsed - }, - current, - root - }; - await cache.updatePayloadsList(updatedList); + if (mruIndex === -1 && lruIndex === -1) { + throw new Error("Package not found in cache."); + } - yield { - status: "RELOAD", - ...updatedList - }; + if (mruIndex > -1) { + logger.info("[ws|command.remove] removed from mru"); + const updatedMru = mru.filter((iterSpec) => iterSpec !== spec); + if (lru.length > 0) { + // We need to move the first lru package to the mru list + const olderLruPkg = lru.sort((a, b) => { + const aDate = lastUsed[a]; + const bDate = lastUsed[b]; + + return aDate - bDate; + }); + updatedMru.push(olderLruPkg[0]); + lru.splice(lru.indexOf(olderLruPkg[0]), 1); } - cache.removePayload(pkg); + const updatedList: PayloadsList = { + mru: updatedMru, + lru, + lastUsed: { + ...lastUsed + }, + current: current === spec ? updatedMru[0] : current, + root, + availables + }; + await cache.updatePayloadsList(updatedList); + + yield { + status: "RELOAD", + cache: updatedList + }; } - catch (error: any) { - logger.error(`[ws|remove](error: ${error.message})`); - logger.debug(error); - - throw error; + else { + logger.info("[ws|command.remove] removed from lru"); + const updatedLru = lru.filter((iterSpec) => iterSpec !== spec); + const updatedList: PayloadsList = { + mru, + lru: updatedLru, + availables, + lastUsed: { + ...lastUsed + }, + current, + root + }; + await cache.updatePayloadsList(updatedList); + + yield { + status: "RELOAD", + cache: updatedList + }; } + + cache.removePayload(spec); } diff --git a/workspaces/server/src/websocket/commands/search.ts b/workspaces/server/src/websocket/commands/search.ts index 67247170..8dbec753 100644 --- a/workspaces/server/src/websocket/commands/search.ts +++ b/workspaces/server/src/websocket/commands/search.ts @@ -9,22 +9,21 @@ import type { } from "../websocket.types.js"; export async function* search( - pkg: string, + spec: string, context: WebSocketContext ): AsyncGenerator { const { logger, cache } = context; - logger.info(`[ws|search](pkg: ${pkg})`); - const cachedPayload = cache.getPayloadOrNull(pkg); + const cachedPayload = cache.getPayloadOrNull(spec); if (cachedPayload) { - logger.info(`[ws|search](payload: ${pkg} found in cache)`); + logger.info("[ws|command.search] one entry found in cache"); const cacheList = await cache.payloadsList(); - if (cacheList.mru.includes(pkg)) { - logger.info(`[ws|search](payload: ${pkg} is already in the MRU)`); + if (cacheList.mru.includes(spec)) { + logger.info("[ws|command.search] payload is already in the MRU"); const updatedList: PayloadsList = { ...cacheList, - current: pkg, - lastUsed: { ...cacheList.lastUsed, [pkg]: Date.now() } + current: spec, + lastUsed: { ...cacheList.lastUsed, [spec]: Date.now() } }; await cache.updatePayloadsList(updatedList); yield { @@ -35,7 +34,7 @@ export async function* search( if (cache.startFromZero) { yield { status: "RELOAD" as const, - ...updatedList + cache: updatedList }; cache.startFromZero = false; } @@ -46,11 +45,11 @@ export async function* search( const { mru, lru, availables, lastUsed, ...updatedCache } = await cache.removeLastMRU(); const updatedList: PayloadsList = { ...updatedCache, - mru: [...new Set([...mru, pkg])], - current: pkg, - lru: lru.filter((pckg) => pckg !== pkg), - availables: availables.filter((pckg) => pckg !== pkg), - lastUsed: { ...lastUsed, [pkg]: Date.now() } + mru: [...new Set([...mru, spec])], + current: spec, + lru: lru.filter((pckg) => pckg !== spec), + availables: availables.filter((pckg) => pckg !== spec), + lastUsed: { ...lastUsed, [spec]: Date.now() } }; await cache.updatePayloadsList(updatedList); @@ -60,7 +59,7 @@ export async function* search( }; yield { status: "RELOAD" as const, - ...updatedList + cache: updatedList }; cache.startFromZero = false; @@ -69,29 +68,29 @@ export async function* search( } // at this point we don't have the payload in cache so we have to scan it. - logger.info(`[ws|search](scan ${pkg} in progress)`); - yield { status: "SCAN" as const, pkg }; + logger.info(`[ws|command.search](scan ${spec} in progress)`); + yield { status: "SCAN" as const, spec }; - const payload = await scanner.from(pkg, { maxDepth: 4 }); + const payload = await scanner.from(spec, { maxDepth: 4 }); const name = payload.rootDependencyName; const version = Object.keys(payload.dependencies[name].versions)[0]; { // save the payload in cache - const pkg = `${name}@${version}`; - logger.info(`[ws|search](scan ${pkg} done|cache: updated)`); + const inScanPackageSpec = `${name}@${version}`; + logger.info(`[ws|command.search](scan ${inScanPackageSpec} done|cache: updated)`); // update the payloads list const { mru, lru, availables, lastUsed, ...appCache } = await cache.removeLastMRU(); - mru.push(pkg); - cache.updatePayload(pkg, payload); + mru.push(inScanPackageSpec); + cache.updatePayload(inScanPackageSpec, payload); const updatedList: PayloadsList = { ...appCache, mru: [...new Set(mru)], lru, availables, - lastUsed: { ...lastUsed, [pkg]: Date.now() }, - current: pkg + lastUsed: { ...lastUsed, [inScanPackageSpec]: Date.now() }, + current: inScanPackageSpec }; await cache.updatePayloadsList(updatedList); @@ -101,11 +100,11 @@ export async function* search( }; yield { status: "RELOAD" as const, - ...updatedList + cache: updatedList }; cache.startFromZero = false; - logger.info("[ws|search](data sent to client|cache: updated)"); + logger.info("[ws|command.search](data sent to client|cache: updated)"); } } diff --git a/workspaces/server/src/websocket/index.ts b/workspaces/server/src/websocket/index.ts index e79ab633..b45b2e7c 100644 --- a/workspaces/server/src/websocket/index.ts +++ b/workspaces/server/src/websocket/index.ts @@ -23,17 +23,14 @@ export class WebSocketServerInstanciator { async onConnectionHandler(socket: WebSocket) { socket.on("message", (rawData: string) => { - logger.info(`[ws](message: ${rawData})`); - - this.onMessageHandler(socket, JSON.parse(rawData)) - .catch(console.error); + this.#onMessageHandler(socket, JSON.parse(rawData)); }); const data = await this.initializeServer(); sendSocketResponse(socket, data); } - async onMessageHandler( + async #onMessageHandler( socket: WebSocket, message: WebSocketMessage ) { @@ -43,13 +40,24 @@ export class WebSocketServerInstanciator { logger }; - const socketMessages = match(message.action) - .with("SEARCH", () => search(message.pkg, ctx)) - .with("REMOVE", () => remove(message.pkg, ctx)) - .exhaustive(); + const commandName = message.commandName; + logger.info(`[ws|command.${commandName.toLowerCase()}] ${message.spec}`); + + try { + const socketMessages = match(message) + .with({ commandName: "SEARCH" }, (command) => search(command.spec, ctx)) + .with({ commandName: "REMOVE" }, (command) => remove(command.spec, ctx)) + .exhaustive(); - for await (const message of socketMessages) { - sendSocketResponse(socket, message); + for await (const message of socketMessages) { + sendSocketResponse(socket, message); + } + } + catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + + logger.error(`[ws|command.${commandName}](error: ${errorMessage})`); + logger.debug(error); } } @@ -57,26 +65,20 @@ export class WebSocketServerInstanciator { stopInitializationOnError = false ): Promise { try { - const { - current, mru, lru, availables, root, lastUsed - } = await appCache.payloadsList(); - logger.info(`[ws|init](mru: ${mru}|lru: ${lru}|availables: ${availables}|current: ${current}|root: ${root})`); - + const cache = await appCache.payloadsList(); if ( - mru === void 0 || - current === void 0 + cache.mru === void 0 || + cache.current === void 0 ) { throw new Error("Payloads list not found in cache."); } + logger.info( + `[ws|init](current: ${cache.current}|root: ${cache.root})` + ); return { status: "INIT", - current, - mru, - lru, - availables, - root, - lastUsed + cache }; } catch { @@ -84,7 +86,7 @@ export class WebSocketServerInstanciator { return null; } - logger.error("[ws|init](No cache yet. Creating one...)"); + logger.error("[ws|init] creating new payloads list in cache"); await appCache.initPayloadsList(); return this.initializeServer(true); diff --git a/workspaces/server/src/websocket/websocket.types.ts b/workspaces/server/src/websocket/websocket.types.ts index b5a7dcef..c7612fcd 100644 --- a/workspaces/server/src/websocket/websocket.types.ts +++ b/workspaces/server/src/websocket/websocket.types.ts @@ -16,7 +16,7 @@ type PayloadResponse = { */ type ScanResponse = { status: "SCAN"; - pkg: string; + spec: string; }; /** @@ -24,7 +24,8 @@ type ScanResponse = { */ type CachedResponse = { status: "INIT" | "RELOAD"; -} & PayloadsList; + cache: PayloadsList; +}; export type WebSocketResponse = | PayloadResponse @@ -32,9 +33,8 @@ export type WebSocketResponse = | ScanResponse; export type WebSocketMessage = { - action: "SEARCH" | "REMOVE"; - pkg: string; - [key: string]: any; + commandName: "SEARCH" | "REMOVE"; + spec: string; }; export interface WebSocketContext {