From df59e6dd010aa17543b08f0c81a0f3259e8c2eff Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Wed, 5 Aug 2026 16:56:12 +0200 Subject: [PATCH 01/13] save wip [skip ci] --- app/stores/viewer.js | 30 +------ .../data_style/mesh/points/visibility.js | 4 +- internal/utils/api_fetch.js | 2 +- internal/utils/viewer_call.js | 90 ++++++++----------- shared/utils/call_raw.js | 43 +++++++++ shared/utils/call_schema.js | 45 ++++++++++ shared/utils/fetch_schema.js | 11 ++- shared/utils/validate_schema.js | 4 +- shared/utils/ws_client.js | 29 ++++++ tests/unit/utils/validate_schema.nuxt.test.js | 6 +- 10 files changed, 173 insertions(+), 91 deletions(-) create mode 100644 shared/utils/call_raw.js create mode 100644 shared/utils/call_schema.js create mode 100644 shared/utils/ws_client.js diff --git a/app/stores/viewer.js b/app/stores/viewer.js index f647533a7..1d3d0350d 100644 --- a/app/stores/viewer.js +++ b/app/stores/viewer.js @@ -4,6 +4,7 @@ import _ from "lodash"; // oxlint-disable-next-line no-unassigned-import import "@kitware/vtk.js/Rendering/OpenGL/Profiles/Geometry"; import SmartConnect from "wslink/src/SmartConnect"; +import { initWebSocketClient } from "@ogw_shared/utils/ws_client"; import { connectImageStream } from "@kitware/vtk.js/Rendering/Misc/RemoteView"; import schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json"; @@ -35,7 +36,6 @@ export const useViewerStore = defineStore( const request_counter = ref(0); const status = ref(Status.NOT_CONNECTED); const version = ref("0.0.0"); - const busy = ref(0); const protocol = computed(() => getWebsocketApiProtocol()); @@ -75,35 +75,11 @@ export const useViewerStore = defineStore( try { console.log("VIEWER LOCK GRANTED !", lock); status.value = Status.CONNECTING; - vtkWSLinkClient.setSmartConnectClass(SmartConnect); - - if (_.isEmpty(client.value)) { - client.value = newInstance(); - } - - client.value.onBusyChange((count) => { - busy.value = count; - }); - client.value.onConnectionError((httpReq) => { - const message = httpReq?.response?.error || `Connection error`; - console.error(message); - }); - client.value.onConnectionClose((httpReq) => { - const message = httpReq?.response?.error || `Connection close`; - status.value = Status.NOT_CONNECTED; - console.error(message); - }); - - client.value.beginBusy(); - await client.value.connect({ - application: "Viewer", - sessionURL: base_url.value, - }); + client.value = await initWebSocketClient(base_url.value, client.value); connectImageStream(client.value.getConnection().getSession()); client.value.endBusy(); const schema = schemas.opengeodeweb_viewer.viewer.reset_visualization; - const timeout = undefined; - await request({ schema, timeout }); + await request({ schema }); status.value = Status.CONNECTED; } catch (error) { console.error("ws_connect error", error); diff --git a/internal/stores/data_style/mesh/points/visibility.js b/internal/stores/data_style/mesh/points/visibility.js index fa929e861..df3c367be 100644 --- a/internal/stores/data_style/mesh/points/visibility.js +++ b/internal/stores/data_style/mesh/points/visibility.js @@ -23,7 +23,9 @@ export function useMeshPointsVisibilityStyle() { params, }, { - response_function: () => meshPointsCommonStyle.mutateMeshPointsStyle(id, { visibility }), + response_function: (response) => { + meshPointsCommonStyle.mutateMeshPointsStyle(response.id, { visibility: response.visibility }); + } }, ); } diff --git a/internal/utils/api_fetch.js b/internal/utils/api_fetch.js index a417b1e78..557758509 100644 --- a/internal/utils/api_fetch.js +++ b/internal/utils/api_fetch.js @@ -15,8 +15,8 @@ export function api_fetch( return fetchSchema( { schema, - baseURL: microservice.base_url, params, + baseURL: microservice.base_url, headers, max_retry: schema.max_retry, timeout, diff --git a/internal/utils/viewer_call.js b/internal/utils/viewer_call.js index d03a98484..d333bd51f 100644 --- a/internal/utils/viewer_call.js +++ b/internal/utils/viewer_call.js @@ -1,12 +1,6 @@ -// Third party imports -import pTimeout from "p-timeout"; - -// Local imports import { endRequestLog, startRequestLog } from "@ogw_front/utils/log"; +import { callSchema } from "@ogw_shared/utils/call_schema"; import { useFeedbackStore } from "@ogw_front/stores/feedback"; -import { validate_schema } from "@ogw_shared/utils/validate_schema"; - -const ERROR_400 = 400; export function viewer_call( microservice, @@ -14,52 +8,42 @@ export function viewer_call( { request_error_function, response_function, response_error_function } = {}, ) { const feedbackStore = useFeedbackStore(); - - const { valid, error: schema_error } = validate_schema(schema, params); - - if (!valid) { - if (process.env.NODE_ENV !== "production") { - console.log("Bad request", schema_error, schema, params); - } - feedbackStore.add_error(ERROR_400, schema.$id, "Bad request", schema_error); - throw new Error(`${schema.$id}: ${schema_error}`); - } - const { client } = microservice; - async function performCall() { - if (!client.getConnection) { - return; - } - microservice.start_request(); - const requestStart = startRequestLog(microservice, schema); - try { - const value = await client.getConnection().getSession().call(schema.$id, [params]); - endRequestLog(microservice, schema, requestStart); - if (response_function) { - await response_function(value); - } - return value; - } catch (error) { - feedbackStore.add_error(error.code, schema.$id, error.message, error.message); - if (request_error_function) { - request_error_function(error); - } - if (response_error_function) { - response_error_function(error); - } - throw error; - } finally { - microservice.stop_request(); - } - } - - if (timeout > 0) { - return pTimeout(performCall(), { - milliseconds: timeout, - message: `${schema.$id}: Timed out after ${timeout}ms`, - }); - } - - return performCall(); + const requestStartingTime = startRequestLog(microservice, schema); + return callSchema( + { + schema, + params, + client, + timeout, + }, + { + request_error_function(error) { + microservice.stop_request(); + feedbackStore.add_error(error.code, schema.$id, error.message, error.message); + if (request_error_function) { + request_error_function(error); + } + }, + response_function(data) { + endRequestLog(microservice, schema, requestStartingTime); + microservice.stop_request(); + if (response_function) { + response_function(data); + } + }, + response_error_function(response) { + microservice.stop_request(); + feedbackStore.add_error(error.code, schema.$id, error.message, error.message); + if (response_error_function) { + response_error_function(response); + } + }, + validation_error_function({ code, name, error }) { + microservice.stop_request(); + feedbackStore.add_error(code, schema.$id, name, error); + }, + }, + ); } diff --git a/shared/utils/call_raw.js b/shared/utils/call_raw.js new file mode 100644 index 000000000..b9b396849 --- /dev/null +++ b/shared/utils/call_raw.js @@ -0,0 +1,43 @@ +// Third party imports +import _ from "lodash"; +import pTimeout from "p-timeout"; + +// Local imports + +function callRaw( + { rpc, params = {}, client, timeout }, + { request_error_function, response_function, response_error_function } = {}, +) { + if (!client.getConnection) { + return; + } + + async function performCall() { + try { + const response = await client.getConnection().getSession().call(rpc, [params]); + if (response_function) { + await response_function(response); + } + return response; + } catch (error) { + if (request_error_function) { + request_error_function(error); + } + if (response_error_function) { + response_error_function(error); + } + throw error; + } + } + + if (timeout > 0) { + return pTimeout(performCall(), { + milliseconds: timeout, + message: `${rpc}: Timed out after ${timeout}ms`, + }); + } + + return performCall(); +} + +export { callRaw }; diff --git a/shared/utils/call_schema.js b/shared/utils/call_schema.js new file mode 100644 index 000000000..e374a2f5a --- /dev/null +++ b/shared/utils/call_schema.js @@ -0,0 +1,45 @@ +// Third party imports + +// Local imports +import { callRaw } from "./call_raw.js"; +import { validateSchema } from "./validate_schema.js"; + +const ERROR_400 = 400; + +function callSchema( + { schema, params = {}, client, timeout }, + { + request_error_function, + response_function, + response_error_function, + validation_error_function, + } = {}, +) { + const { valid, error: schema_error } = validateSchema(schema, params); + + if (!valid) { + if (process.env.NODE_ENV !== "production") { + console.log("Bad request", schema_error, schema, params); + } + if (validation_error_function) { + validation_error_function({ code: ERROR_400, name: "Bad request", error: schema_error }); + } + throw new Error(`${schema.$id}: ${schema_error}`); + } + + return callRaw( + { + rpc: schema.$id, + params, + client, + timeout, + }, + { + request_error_function, + response_function, + response_error_function, + }, + ); +} + +export { callSchema }; diff --git a/shared/utils/fetch_schema.js b/shared/utils/fetch_schema.js index e85344265..72e6e6a0b 100644 --- a/shared/utils/fetch_schema.js +++ b/shared/utils/fetch_schema.js @@ -2,7 +2,7 @@ // Local imports import { fetchRaw } from "./fetch_raw.js"; -import { validate_schema } from "./validate_schema.js"; +import { validateSchema } from "./validate_schema.js"; const ERROR_400 = 400; @@ -15,8 +15,7 @@ function fetchSchema( validation_error_function, } = {}, ) { - console.log("fetchSchema", { schema, baseURL, params, headers, timeout }); - const { valid, error: schema_error } = validate_schema(schema, params); + const { valid, error: schema_error } = validateSchema(schema, params); if (!valid) { if (process.env.NODE_ENV !== "production") { @@ -39,7 +38,11 @@ function fetchSchema( timeout, expectEvent, }, - { request_error_function, response_function, response_error_function }, + { + request_error_function, + response_function, + response_error_function, + }, ); } diff --git a/shared/utils/validate_schema.js b/shared/utils/validate_schema.js index e1b59aa4f..ca5b5a9fb 100644 --- a/shared/utils/validate_schema.js +++ b/shared/utils/validate_schema.js @@ -1,6 +1,6 @@ import Ajv from "ajv"; -function validate_schema(schema, body) { +function validateSchema(schema, body) { const ajv = new Ajv(); const list_keywords = ["methods", "route", "max_retry", "rpc"]; for (const keyword of list_keywords) { @@ -10,4 +10,4 @@ function validate_schema(schema, body) { return { valid, error: ajv.errorsText() }; } -export { validate_schema }; +export { validateSchema }; diff --git a/shared/utils/ws_client.js b/shared/utils/ws_client.js new file mode 100644 index 000000000..4c2ed641a --- /dev/null +++ b/shared/utils/ws_client.js @@ -0,0 +1,29 @@ +// Third party imports +import vtkWSLinkClient, { newInstance } from "@kitware/vtk.js/IO/Core/WSLinkClient.js"; +import _ from "lodash"; +import SmartConnect from "wslink/src/SmartConnect/index.js"; + +async function initWebSocketClient(baseUrl, initialClient = {}) { + vtkWSLinkClient.setSmartConnectClass(SmartConnect); + const client = _.isEmpty(initialClient) ? newInstance() : initialClient; + + client.onConnectionError((httpReq) => { + const message = httpReq?.response?.error || `Connection error`; + console.error(message); + }); + client.onConnectionClose((httpReq) => { + const message = httpReq?.response?.error || `Connection close`; + status.value = Status.NOT_CONNECTED; + console.error(message); + }); + + client.beginBusy(); + await client.connect({ + application: "Viewer", + sessionURL: baseUrl, + }); + + return client; +} + +export { initWebSocketClient } \ No newline at end of file diff --git a/tests/unit/utils/validate_schema.nuxt.test.js b/tests/unit/utils/validate_schema.nuxt.test.js index 8fa29b267..a43d3554e 100644 --- a/tests/unit/utils/validate_schema.nuxt.test.js +++ b/tests/unit/utils/validate_schema.nuxt.test.js @@ -2,7 +2,7 @@ import { describe, expect, test } from "vitest"; // Local imports -import { validate_schema } from "@ogw_shared/utils/validate_schema"; +import { validateSchema } from "@ogw_shared/utils/validate_schema"; // CONSTANTS const MIN_0 = 0; @@ -24,14 +24,14 @@ describe("validate schema", () => { test("ajv wrong params", () => { const params = {}; - const { valid, error } = validate_schema(schema, params); + const { valid, error } = validateSchema(schema, params); expect(valid).toBe(false); expect(error).toBe("data must have required property 'var_1'"); }); test("good params", () => { const params = { var_1: "test", var_2: VAL_5 }; - const { valid, error } = validate_schema(schema, params); + const { valid, error } = validateSchema(schema, params); expect(valid).toBe(true); expect(error).toBe("No errors"); }); From 971eecf35da41f48b69e6758728fb948c9d4468f Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Fri, 7 Aug 2026 10:29:22 +0200 Subject: [PATCH 02/13] vite optimizeDeps in ogw-front --- nuxt.config.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/nuxt.config.js b/nuxt.config.js index 577b566bf..3edc8421d 100644 --- a/nuxt.config.js +++ b/nuxt.config.js @@ -59,13 +59,36 @@ export default defineNuxtConfig({ vite: { optimizeDeps: { include: [ + "@kitware/vtk.js", + "@kitware/vtk.js/Common/Core/Math", + "@kitware/vtk.js/IO/Core/WSLinkClient", + "@kitware/vtk.js/IO/XML/XMLPolyDataReader", + "@kitware/vtk.js/Rendering/Core/Actor", + "@kitware/vtk.js/Rendering/Core/AnnotatedCubeActor", + "@kitware/vtk.js/Rendering/Core/ColorTransferFunction", + "@kitware/vtk.js/Rendering/Core/Mapper", + "@kitware/vtk.js/Rendering/Misc/GenericRenderWindow", + "@kitware/vtk.js/Rendering/Misc/RemoteView", + "@kitware/vtk.js/Rendering/OpenGL/Profiles/Geometry", + "@kitware/vtk.js/Widgets/Core/WidgetManager", + "@kitware/vtk.js/Widgets/Widgets3D/ImplicitPlaneWidget", + "@vue/devtools-core", + "@vue/devtools-kit", "ajv", - "fast-deep-equal", + "broadcast-channel", + "dexie", "globalthis", "h3", "js-file-download", "lodash", + "lodash/merge", + "p-timeout", "seedrandom", + "spark-md5", + "uuid", + "wslink", + "wslink/src/SmartConnect", + "xmlbuilder2", ], }, }, From feaab6814edf094c9fbf40ba357c4b1324ec348c Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Fri, 7 Aug 2026 10:29:40 +0200 Subject: [PATCH 03/13] refactor ws logic --- app/stores/viewer.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/stores/viewer.js b/app/stores/viewer.js index 1d3d0350d..b1e893f3a 100644 --- a/app/stores/viewer.js +++ b/app/stores/viewer.js @@ -1,11 +1,9 @@ // Third party imports -import vtkWSLinkClient, { newInstance } from "@kitware/vtk.js/IO/Core/WSLinkClient"; import _ from "lodash"; // oxlint-disable-next-line no-unassigned-import import "@kitware/vtk.js/Rendering/OpenGL/Profiles/Geometry"; -import SmartConnect from "wslink/src/SmartConnect"; -import { initWebSocketClient } from "@ogw_shared/utils/ws_client"; import { connectImageStream } from "@kitware/vtk.js/Rendering/Misc/RemoteView"; +import { initWebSocketClient } from "@ogw_internal/utils/ws_client"; import schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json"; // Local imports From e00a5492f222d4a1192bba8d416beefc6065ce2b Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Fri, 7 Aug 2026 10:29:48 +0200 Subject: [PATCH 04/13] refactor ws logic --- internal/utils/ws_client.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 internal/utils/ws_client.js diff --git a/internal/utils/ws_client.js b/internal/utils/ws_client.js new file mode 100644 index 000000000..e11ac9ace --- /dev/null +++ b/internal/utils/ws_client.js @@ -0,0 +1,29 @@ +// Third party imports +import vtkWSLinkClient, { newInstance } from "@kitware/vtk.js/IO/Core/WSLinkClient"; +import SmartConnect from "wslink/src/SmartConnect"; +import _ from "lodash"; + +async function initWebSocketClient(baseUrl, initialClient = {}) { + vtkWSLinkClient.setSmartConnectClass(SmartConnect); + const client = _.isEmpty(initialClient) ? newInstance() : initialClient; + + client.onConnectionError((httpReq) => { + const message = httpReq?.response?.error || `Connection error`; + console.error(message); + }); + client.onConnectionClose((httpReq) => { + const message = httpReq?.response?.error || `Connection close`; + status.value = Status.NOT_CONNECTED; + console.error(message); + }); + + client.beginBusy(); + await client.connect({ + application: "Viewer", + sessionURL: baseUrl, + }); + + return client; +} + +export { initWebSocketClient } \ No newline at end of file From f15cb4eda8557645bccc8550025614703d6caf05 Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Fri, 7 Aug 2026 10:30:33 +0200 Subject: [PATCH 05/13] store ws server client --- server/utils/server_config.js | 29 +++++++++++ server/utils/ws_client.js | 97 +++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 server/utils/ws_client.js diff --git a/server/utils/server_config.js b/server/utils/server_config.js index 6a3eaf844..fd848563e 100644 --- a/server/utils/server_config.js +++ b/server/utils/server_config.js @@ -1,5 +1,9 @@ +// Third party imports import { createStorage, prefixStorage } from "unstorage"; +// Local imports +import { createServerWsRpcClient } from "./ws_client.js"; + const storage = createStorage(); const config = prefixStorage(storage, "config"); @@ -22,11 +26,36 @@ function setViewerBaseUrl(baseUrl) { return config.setItem("VIEWER_BASE_URL", baseUrl); } +let viewerClient = undefined; + +async function getViewerWebSocketClient() { + console.log("getViewerWebSocketClient", { viewerClient }); + if (viewerClient?.isOpen()) { + return viewerClient; + } + const viewerBaseUrl = await getViewerBaseUrl(); + return setViewerWebSocketClient(viewerBaseUrl); +} + +async function setViewerWebSocketClient(baseUrl) { + const client = createServerWsRpcClient(baseUrl); + client.onConnectionClose(() => { + if (viewerClient === client) { + viewerClient = undefined; + } + }); + await client.ready; + viewerClient = client; + return client; +} + export { getAppBaseUrl, setAppBaseUrl, getBackBaseUrl, getViewerBaseUrl, + getViewerWebSocketClient, setBackBaseUrl, setViewerBaseUrl, + setViewerWebSocketClient, }; diff --git a/server/utils/ws_client.js b/server/utils/ws_client.js new file mode 100644 index 000000000..c23a4b25a --- /dev/null +++ b/server/utils/ws_client.js @@ -0,0 +1,97 @@ +// Third party imports +import { WebSocket } from "ws"; +import { v4 as uuidv4 } from "uuid"; + +// Local imports + +const HELLO_ID = "system:hello"; +const HELLO_SECRET = "wslink-secret"; + +//oxlint-disable-next-line max-lines-per-function +function createServerWsRpcClient(baseUrl) { + const socket = new WebSocket(baseUrl); + const pending = new Map(); + let onCloseCallback = undefined; + let onErrorCallback = undefined; + + //oxlint-disable-next-line promise/avoid-new + const ready = new Promise((resolve, reject) => { + socket.on("open", () => { + socket.send( + JSON.stringify({ + id: HELLO_ID, + method: "wslink.hello", + args: [{ secret: HELLO_SECRET }], + }), + ); + }); + + socket.on("message", (raw) => { + console.log("RAW WS MESSAGE:", raw.toString()); + let message = undefined; + try { + message = JSON.parse(raw.toString()); + } catch { + return; + } + + if (message.id === HELLO_ID) { + resolve(); + return; + } + + const entry = pending.get(message.id); + if (!entry) { + return; + } + pending.delete(message.id); + if (message.error) { + entry.reject(new Error(message.error.message || "wslink RPC error")); + } else { + entry.resolve(message.result); + } + }); + + socket.on("error", (error) => { + onErrorCallback?.(error); + reject(error); + }); + + socket.on("close", () => { + onCloseCallback?.(); + // Reject any calls still in flight + pending.forEach(({ reject: rejectPending }) => rejectPending(new Error("WebSocket closed"))); + pending.clear(); + }); + }); + + async function call(rpc, params = {}) { + await ready; + const id = uuidv4(); + //oxlint-disable-next-line promise/avoid-new + return new Promise((resolve, reject) => { + pending.set(id, { resolve, reject }); + socket.send(JSON.stringify({ wslink: '1.0', id, method: rpc, args: [params] })); + }); + } + + function close() { + socket.close(); + } + + function isOpen() { + return socket.readyState === WebSocket.OPEN; + } + + function onConnectionClose(callback) { + onCloseCallback = callback; + } + + function onConnectionError(callback) { + onErrorCallback = callback; + } + + return { call, close, isOpen, onConnectionClose, onConnectionError, ready }; +} + +export { createServerWsRpcClient }; From 391c17b910b89a5dce505f2f158dcd3dd6c77737 Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Fri, 7 Aug 2026 10:31:12 +0200 Subject: [PATCH 06/13] callRaw callable from cient and server side --- shared/utils/call_raw.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/shared/utils/call_raw.js b/shared/utils/call_raw.js index b9b396849..8592bbf20 100644 --- a/shared/utils/call_raw.js +++ b/shared/utils/call_raw.js @@ -4,17 +4,27 @@ import pTimeout from "p-timeout"; // Local imports +function callClient( + { rpc, params = {}, client }) { + console.log("callClient", { rpc, params, client }); + + if (globalThis.window !== undefined) { + return client.getConnection().getSession().call(rpc, [params]); + } + console.log(`Calling from server ${rpc} with params:`, params); + return client.call(rpc, params); +} + function callRaw( { rpc, params = {}, client, timeout }, { request_error_function, response_function, response_error_function } = {}, ) { - if (!client.getConnection) { - return; - } + console.log("callRaw", { rpc, params, timeout }); async function performCall() { try { - const response = await client.getConnection().getSession().call(rpc, [params]); + const response = await callClient({ rpc, params, client }); + console.log(`callRaw response for ${rpc}:`, { response }); if (response_function) { await response_function(response); } From 01ce287940b481b202f605ffc5bab4055cfc6b79 Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Fri, 7 Aug 2026 10:31:58 +0200 Subject: [PATCH 07/13] parse boolean --- shared/utils/parse_boolean.js | 16 ++++++++++++++++ shared/utils/ws_client.js | 29 ----------------------------- 2 files changed, 16 insertions(+), 29 deletions(-) create mode 100644 shared/utils/parse_boolean.js delete mode 100644 shared/utils/ws_client.js diff --git a/shared/utils/parse_boolean.js b/shared/utils/parse_boolean.js new file mode 100644 index 000000000..a89afc06a --- /dev/null +++ b/shared/utils/parse_boolean.js @@ -0,0 +1,16 @@ +const TRUTHY_VALUES = new Set([true, 1, "1", "true", "yes"]); +const FALSY_VALUES = new Set([false, 0, "0", "false", "no"]); + +function parseBoolean(value) { + const normalized = typeof value === "string" ? value.trim().toLowerCase() : value; + + if (TRUTHY_VALUES.has(normalized)) { + return true; + } + if (FALSY_VALUES.has(normalized)) { + return false; + } + throw new Error(`Cannot parse boolean from: ${value}`); +} + +export { parseBoolean }; diff --git a/shared/utils/ws_client.js b/shared/utils/ws_client.js deleted file mode 100644 index 4c2ed641a..000000000 --- a/shared/utils/ws_client.js +++ /dev/null @@ -1,29 +0,0 @@ -// Third party imports -import vtkWSLinkClient, { newInstance } from "@kitware/vtk.js/IO/Core/WSLinkClient.js"; -import _ from "lodash"; -import SmartConnect from "wslink/src/SmartConnect/index.js"; - -async function initWebSocketClient(baseUrl, initialClient = {}) { - vtkWSLinkClient.setSmartConnectClass(SmartConnect); - const client = _.isEmpty(initialClient) ? newInstance() : initialClient; - - client.onConnectionError((httpReq) => { - const message = httpReq?.response?.error || `Connection error`; - console.error(message); - }); - client.onConnectionClose((httpReq) => { - const message = httpReq?.response?.error || `Connection close`; - status.value = Status.NOT_CONNECTED; - console.error(message); - }); - - client.beginBusy(); - await client.connect({ - application: "Viewer", - sessionURL: baseUrl, - }); - - return client; -} - -export { initWebSocketClient } \ No newline at end of file From 441ac11972c9ef55bfbf6179fa847b7d12955bd6 Mon Sep 17 00:00:00 2001 From: Arnaud Botella Date: Fri, 7 Aug 2026 14:50:31 +0200 Subject: [PATCH 08/13] wip [skip ci] --- internal/stores/data_style/mesh/points/common.js | 7 ++++++- internal/stores/data_style/mesh/points/visibility.js | 4 +--- shared/utils/call_raw.js | 5 ++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/internal/stores/data_style/mesh/points/common.js b/internal/stores/data_style/mesh/points/common.js index ec8971c51..0644ed0d7 100644 --- a/internal/stores/data_style/mesh/points/common.js +++ b/internal/stores/data_style/mesh/points/common.js @@ -9,6 +9,10 @@ export function useMeshPointsCommonStyle() { }); } + function mutateMeshPointsVisibility(response) { + return mutateMeshPointsStyle(response.id, { visibility: response.visibility }); + } + function meshPointsStyle(id) { return dataStyleState.getStyle(id).points; } @@ -26,7 +30,8 @@ export function useMeshPointsCommonStyle() { return { meshPointsStyle, meshPointsColoring, - mutateMeshPointsStyle, + // mutateMeshPointsStyle, mutateMeshPointsColoring, + mutateMeshPointsVisibility, }; } diff --git a/internal/stores/data_style/mesh/points/visibility.js b/internal/stores/data_style/mesh/points/visibility.js index df3c367be..51dc83baf 100644 --- a/internal/stores/data_style/mesh/points/visibility.js +++ b/internal/stores/data_style/mesh/points/visibility.js @@ -23,9 +23,7 @@ export function useMeshPointsVisibilityStyle() { params, }, { - response_function: (response) => { - meshPointsCommonStyle.mutateMeshPointsStyle(response.id, { visibility: response.visibility }); - } + response_function: meshPointsCommonStyle.mutateMeshPointsVisibility, }, ); } diff --git a/shared/utils/call_raw.js b/shared/utils/call_raw.js index 8592bbf20..cf271fdf2 100644 --- a/shared/utils/call_raw.js +++ b/shared/utils/call_raw.js @@ -4,15 +4,14 @@ import pTimeout from "p-timeout"; // Local imports -function callClient( - { rpc, params = {}, client }) { +function callClient({ rpc, params = {}, client }) { console.log("callClient", { rpc, params, client }); if (globalThis.window !== undefined) { return client.getConnection().getSession().call(rpc, [params]); } console.log(`Calling from server ${rpc} with params:`, params); - return client.call(rpc, params); + return client.call(rpc, params, { stream: true }); } function callRaw( From 9ece117aae379db779ee28d7e1fd73f0f9eda9ac Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Mon, 10 Aug 2026 11:48:25 +0200 Subject: [PATCH 09/13] feat(Refactor): callRaw & callSchema, viewer events --- .../stores/data_style/mesh/points/common.js | 2 +- .../data_style/mesh/points/visibility.js | 4 +++- server/utils/ws_client.js | 21 ++++++++++++++++--- shared/utils/call_raw.js | 7 +------ 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/internal/stores/data_style/mesh/points/common.js b/internal/stores/data_style/mesh/points/common.js index 0644ed0d7..a0ec2acaa 100644 --- a/internal/stores/data_style/mesh/points/common.js +++ b/internal/stores/data_style/mesh/points/common.js @@ -30,8 +30,8 @@ export function useMeshPointsCommonStyle() { return { meshPointsStyle, meshPointsColoring, - // mutateMeshPointsStyle, mutateMeshPointsColoring, + mutateMeshPointsStyle, mutateMeshPointsVisibility, }; } diff --git a/internal/stores/data_style/mesh/points/visibility.js b/internal/stores/data_style/mesh/points/visibility.js index 51dc83baf..e01242db7 100644 --- a/internal/stores/data_style/mesh/points/visibility.js +++ b/internal/stores/data_style/mesh/points/visibility.js @@ -23,7 +23,9 @@ export function useMeshPointsVisibilityStyle() { params, }, { - response_function: meshPointsCommonStyle.mutateMeshPointsVisibility, + response_function(response) { + return meshPointsCommonStyle.mutateMeshPointsVisibility(response); + }, }, ); } diff --git a/server/utils/ws_client.js b/server/utils/ws_client.js index c23a4b25a..0519c6540 100644 --- a/server/utils/ws_client.js +++ b/server/utils/ws_client.js @@ -40,6 +40,10 @@ function createServerWsRpcClient(baseUrl) { return; } + if (typeof message.id === "string" && message.id.startsWith("publish:")) { + return; + } + const entry = pending.get(message.id); if (!entry) { return; @@ -59,8 +63,9 @@ function createServerWsRpcClient(baseUrl) { socket.on("close", () => { onCloseCallback?.(); - // Reject any calls still in flight - pending.forEach(({ reject: rejectPending }) => rejectPending(new Error("WebSocket closed"))); + for (const { reject: rejectPending } of pending.values()) { + rejectPending(new Error("WebSocket closed")); + } pending.clear(); }); }); @@ -71,7 +76,15 @@ function createServerWsRpcClient(baseUrl) { //oxlint-disable-next-line promise/avoid-new return new Promise((resolve, reject) => { pending.set(id, { resolve, reject }); - socket.send(JSON.stringify({ wslink: '1.0', id, method: rpc, args: [params] })); + socket.send( + JSON.stringify({ + wslink: "1.0", + id, + method: rpc, + args: [params], + kwargs: { stream: true }, + }), + ); }); } @@ -83,10 +96,12 @@ function createServerWsRpcClient(baseUrl) { return socket.readyState === WebSocket.OPEN; } + //oxlint-disable-next-line promise/prefer-await-to-callbacks function onConnectionClose(callback) { onCloseCallback = callback; } + //oxlint-disable-next-line promise/prefer-await-to-callbacks function onConnectionError(callback) { onErrorCallback = callback; } diff --git a/shared/utils/call_raw.js b/shared/utils/call_raw.js index cf271fdf2..716420d65 100644 --- a/shared/utils/call_raw.js +++ b/shared/utils/call_raw.js @@ -5,25 +5,20 @@ import pTimeout from "p-timeout"; // Local imports function callClient({ rpc, params = {}, client }) { - console.log("callClient", { rpc, params, client }); - if (globalThis.window !== undefined) { return client.getConnection().getSession().call(rpc, [params]); } - console.log(`Calling from server ${rpc} with params:`, params); - return client.call(rpc, params, { stream: true }); + return client.call(rpc, params); } function callRaw( { rpc, params = {}, client, timeout }, { request_error_function, response_function, response_error_function } = {}, ) { - console.log("callRaw", { rpc, params, timeout }); async function performCall() { try { const response = await callClient({ rpc, params, client }); - console.log(`callRaw response for ${rpc}:`, { response }); if (response_function) { await response_function(response); } From ac185ae51c16b417613582d3a7fec7841f752d60 Mon Sep 17 00:00:00 2001 From: JulienChampagnol <91873154+JulienChampagnol@users.noreply.github.com> Date: Mon, 10 Aug 2026 09:54:39 +0000 Subject: [PATCH 10/13] Apply prepare changes --- internal/utils/ws_client.js | 2 +- server/utils/server_config.js | 1 - shared/utils/call_raw.js | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/utils/ws_client.js b/internal/utils/ws_client.js index e11ac9ace..47b3cbb4e 100644 --- a/internal/utils/ws_client.js +++ b/internal/utils/ws_client.js @@ -26,4 +26,4 @@ async function initWebSocketClient(baseUrl, initialClient = {}) { return client; } -export { initWebSocketClient } \ No newline at end of file +export { initWebSocketClient }; diff --git a/server/utils/server_config.js b/server/utils/server_config.js index c3aca9f09..7edab11be 100644 --- a/server/utils/server_config.js +++ b/server/utils/server_config.js @@ -25,7 +25,6 @@ function setIsAppReady(isAppReady) { return storage.set("IS_APP_READY", isAppReady); } - async function getViewerWebSocketClient() { const viewerClient = storage.get("VIEWER_CLIENT") ?? undefined; if (viewerClient?.isOpen()) { diff --git a/shared/utils/call_raw.js b/shared/utils/call_raw.js index 716420d65..40632be3b 100644 --- a/shared/utils/call_raw.js +++ b/shared/utils/call_raw.js @@ -15,7 +15,6 @@ function callRaw( { rpc, params = {}, client, timeout }, { request_error_function, response_function, response_error_function } = {}, ) { - async function performCall() { try { const response = await callClient({ rpc, params, client }); From 73ebc875c6c5bfe78fbd00a750f4f78e109c5fe8 Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Mon, 10 Aug 2026 16:09:18 +0200 Subject: [PATCH 11/13] oxlint From 260fc229d98708e1b3b3637679070f0f98359ad0 Mon Sep 17 00:00:00 2001 From: JulienChampagnol <91873154+JulienChampagnol@users.noreply.github.com> Date: Mon, 10 Aug 2026 14:10:11 +0000 Subject: [PATCH 12/13] Apply prepare changes --- .oxlintrc.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.oxlintrc.json b/.oxlintrc.json index cecf04fee..0ca9b024a 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -20,6 +20,7 @@ "eslint/no-ternary": "off", // A utiliser pour des opérations simples "unicorn/prefer-ternary": "off", "oxc/no-async-await": "off", + "one-var": "off", "oxc/no-rest-spread-properties": "off", // Enable if older browser support is needed "eslint/max-statements": ["warn", 20], "eslint/id-length": [ From 8179b3394bd36d7f1fcbeee4e156b164190b17d3 Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Wed, 12 Aug 2026 16:46:45 +0200 Subject: [PATCH 13/13] import createServerWsRpcClient --- server/utils/server_config.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/utils/server_config.js b/server/utils/server_config.js index 7edab11be..9d9083813 100644 --- a/server/utils/server_config.js +++ b/server/utils/server_config.js @@ -1,3 +1,5 @@ +import { createServerWsRpcClient } from "./ws_client.js"; + const storage = new Map(); function getAppBaseUrl() {