taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 0412802078c5e547c4121985c228924f37f53434
parent 3cc0f920ae7b24cbb0c6df078650de06f5176dba
Author: Sebastian <sebasjm@taler-systems.com>
Date:   Thu, 16 Apr 2026 11:16:21 -0300

install dir to server_dir

Diffstat:
Mpackages/bank-ui/build.mjs | 1+
Mpackages/bank-ui/dev.mjs | 1+
Mpackages/web-util/build.mjs | 3++-
Mpackages/web-util/src/index.build.ts | 27+++++++++++++++++++++++----
Apackages/web-util/src/live-reload-poll.ts | 10++++++++++
Apackages/web-util/src/live-reload-ws.ts | 10++++++++++
Mpackages/web-util/src/live-reload.ts | 91++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mpackages/web-util/src/serve.ts | 23+++++++++++++++++++++++
8 files changed, 132 insertions(+), 34 deletions(-)

diff --git a/packages/bank-ui/build.mjs b/packages/bank-ui/build.mjs @@ -19,6 +19,7 @@ import { build } from "@gnu-taler/web-util/build"; await build({ type: "production", + importMeta: import.meta, source: { js: ["src/index.tsx"], assets: [{ base: "src", files: ["src/index.html"] }], diff --git a/packages/bank-ui/dev.mjs b/packages/bank-ui/dev.mjs @@ -22,6 +22,7 @@ const devEntryPoints = ["src/stories.tsx", "src/index.tsx"]; const build = initializeDev({ type: "development", + importMeta: import.meta, source: { js: devEntryPoints, assets: [{ base: "src", files: ["src/index.html", "src/settings.json"] }], diff --git a/packages/web-util/build.mjs b/packages/web-util/build.mjs @@ -183,7 +183,8 @@ const buildConfigBrowser = { "src/tests/mock.ts", "src/tests/swr.ts", "src/index.browser.ts", - "src/live-reload.ts", + "src/live-reload-ws.ts", + "src/live-reload-poll.ts", "src/stories.tsx", ], outExtension: { diff --git a/packages/web-util/src/index.build.ts b/packages/web-util/src/index.build.ts @@ -91,6 +91,22 @@ function copyFilesPlugin(assets: Assets | Assets[]) { }; } +function buildTimePlugin() { + return { + name: "build-timestamp", + setup(build: PluginBuild) { + const outDir = build.initialOptions.outdir; + if (outDir === undefined) { + throw Error("esbuild build options does not specify outdir"); + } + build.onEnd(() => { + const destination = path.join(outDir, 'build-time.txt'); + fs.writeFileSync(destination, new Date().toUTCString()); + }); + }, + }; +} + const DEFAULT_SASS_FILTER = /\.(s[ac]ss|css)$/; const sassPlugin: esbuild.Plugin = { @@ -228,7 +244,7 @@ export interface BuildParams { }; public?: string; /** - * Location directory of the output. Can be redirected with INSTALL_DIR environment + * Location directory of the output. Can be redirected with SERVER_DIR environment */ destination: string; css: "sass" | "postcss" | "linaria"; @@ -310,7 +326,7 @@ export function computeConfig(params: BuildParams): { absWorkingDir: baseDir, entryPoints: params.source.js, publicPath: params.public, - outdir: process.env.INSTALL_DIR ?? params.destination, + outdir: process.env.SERVER_DIR ?? params.destination, treeShaking: true, metafile: true, minify: params.type === "production", @@ -339,9 +355,11 @@ export async function build(config: BuildParams) { return res; } +const liveReloadType = process.env.SERVER_DIR ? "poll" : "ws" + const LIVE_RELOAD_SCRIPT = - "./node_modules/@gnu-taler/web-util/lib/live-reload.mjs"; -const LIVE_RELOAD_SCRIPT_LOCALLY = "./lib/live-reload.mjs"; + `./node_modules/@gnu-taler/web-util/lib/live-reload-${liveReloadType}.mjs`; +const LIVE_RELOAD_SCRIPT_LOCALLY = `./lib/live-reload-${liveReloadType}.mjs`; /** * Do startup for development environment @@ -370,6 +388,7 @@ export function initializeDev( ): () => Promise<esbuild.BuildResult> { function buildDevelopment() { const { esBuildOptions } = computeConfig(config); + esBuildOptions.plugins?.push(buildTimePlugin()) if (!options.dontDoLiveReload) { esBuildOptions.inject = [LIVE_RELOAD_SCRIPT]; } diff --git a/packages/web-util/src/live-reload-poll.ts b/packages/web-util/src/live-reload-poll.ts @@ -0,0 +1,10 @@ +import { poll } from "./live-reload.js"; + +function setupLiveReload(): void { + const stopWs = localStorage.getItem("stop-ws"); + if (!!stopWs) return; + + poll(); +} + +setupLiveReload(); diff --git a/packages/web-util/src/live-reload-ws.ts b/packages/web-util/src/live-reload-ws.ts @@ -0,0 +1,10 @@ +import { listen } from "./live-reload.js"; + +function setupLiveReload(): void { + const stopWs = localStorage.getItem("stop-ws"); + if (!!stopWs) return; + + listen(); +} + +setupLiveReload(); diff --git a/packages/web-util/src/live-reload.ts b/packages/web-util/src/live-reload.ts @@ -1,14 +1,22 @@ /* eslint-disable no-undef */ -function setupLiveReload(): void { - const stopWs = localStorage.getItem("stop-ws"); - if (!!stopWs) return; - const protocol = window.location.protocol === "http:" ? "ws:" : "wss:"; - const ws = new WebSocket( - `${protocol}//${window.location.hostname}:${window.location.port}/ws`, - ); +const protocol = window.location.protocol === "http:" ? "ws:" : "wss:"; +const POLL_URL = `./build-time.txt`; +const WS_URL = `${protocol}//${window.location.hostname}:${window.location.port}/ws`; + +const POLL_MS = 1500; + +let lastBuildtime: string; + +export function listen() { + let reloadRequired = false; + const ws = new WebSocket(WS_URL); + ws.addEventListener("close", (e) => { - window.location.reload(); + if (reloadRequired) { + reloadAppCode(); + } + setTimeout(listen, POLL_MS); }); ws.addEventListener("message", (message) => { @@ -19,31 +27,17 @@ function setupLiveReload(): void { return; } if (event.type === "file-updated-done") { + reloadRequired = true; ws.close(1000); return; } if (event.type === "file-updated-failed") { - const h1 = document.getElementById("overlay-text"); - if (h1) { - h1.innerHTML = "compilation failed"; - h1.style.color = "red"; - h1.style.margin = ""; - } - const div = document.getElementById("overlay"); - if (div) { - const content = JSON.stringify(event.data, undefined, 2); - const pre = document.createElement("pre"); - pre.id = "error-text"; - pre.style.margin = ""; - pre.textContent = content; - div.style.backgroundColor = "rgba(0,0,0,0.8)"; - div.style.flexDirection = "column"; - div.appendChild(pre); - } + showErrorOnOverlay(event.data); console.error(event.data.error); return; } if (event.type === "file-updated") { + reloadRequired = true; ws.close(1000); return; } @@ -56,12 +50,46 @@ function setupLiveReload(): void { ws.addEventListener("error", (error) => { console.error(error); }); - ws.addEventListener("close", (message) => { - setTimeout(setupLiveReload, 1500); - }); } -setupLiveReload(); +export async function poll() { + try { + const response = await fetch(POLL_URL); + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); + + const currentContent = await response.text(); + + if (!lastBuildtime) { + lastBuildtime = currentContent; + } else if (currentContent !== lastBuildtime) { + reloadAppCode(); + } + } catch (error) { + console.error("Polling error:", error); + } finally { + setTimeout(poll, POLL_MS); + } +} + +function showErrorOnOverlay(data: unknown): void { + const h1 = document.getElementById("overlay-text"); + if (h1) { + h1.innerHTML = "compilation failed"; + h1.style.color = "red"; + h1.style.margin = ""; + } + const div = document.getElementById("overlay"); + if (div) { + const content = JSON.stringify(data, undefined, 2); + const pre = document.createElement("pre"); + pre.id = "error-text"; + pre.style.margin = ""; + pre.textContent = content; + div.style.backgroundColor = "rgba(0,0,0,0.8)"; + div.style.flexDirection = "column"; + div.appendChild(pre); + } +} function showReloadOverlay(): void { const d = document.createElement("div"); d.id = "overlay"; @@ -88,3 +116,8 @@ function showReloadOverlay(): void { document.body.appendChild(d); } } + +function reloadAppCode() { + // instead we could re-hydrate the preact state + window.location.reload(); +} diff --git a/packages/web-util/src/serve.ts b/packages/web-util/src/serve.ts @@ -19,6 +19,26 @@ const httpServerOptions = { const logger = new Logger("serve.ts"); +async function watch(opts: { + onSourceUpdate?: () => Promise<void>; + folder: string; + source?: string; +}): Promise<void> { + const watchingFolder = opts.source ?? opts.folder; + logger.info(`SERVER_DIR detected, server off`) + logger.info(`watching ${watchingFolder} for changes`); + + chokidar.watch(watchingFolder).on("change", (path, stats) => { + logger.info(`changed: ${path}`); + + if (opts.onSourceUpdate) { + opts.onSourceUpdate(); + } + }); + + if (opts.onSourceUpdate) opts.onSourceUpdate(); +} + export async function serve(opts: { folder: string; port: number; @@ -30,6 +50,9 @@ export async function serve(opts: { appPath?: string; appSamplePath?: string; }): Promise<void> { + if (process.env.SERVER_DIR) { + return watch(opts) + } const PATHS = { WS: "/ws", EXAMPLE: "/examples",