diff --git a/apps/host/package.json b/apps/host/package.json index b3789ff6..39c15f58 100644 --- a/apps/host/package.json +++ b/apps/host/package.json @@ -13,6 +13,7 @@ "test:perf:base": "PERF_RUNS=20 PERF_SAVE_BASE=1 PERF_DOMAIN_A=explore PERF_DOMAIN_B=host-playground npx playwright test --config=tests/performance/playwright.config.ts tests/performance/cold-start.spec.ts", "test:perf:compare": "bun tests/performance/compare.ts", "test:perf:compare:md": "bun tests/performance/compare.ts --markdown", + "test": "vitest run", "lint": "bunx eslint src/", "typecheck": "tsc --noEmit" }, @@ -23,11 +24,13 @@ "playwright": "^1.59.1", "@types/node": "^25.9.3", "eslint": "^10.5.0", + "happy-dom": "^20.10.3", "jsqr": "^1.4.0", "prettier": "^3.8.3", "simple-statistics": "^7.9.0", "typescript": "~6.0.3", "vite": "^8.0.16", + "vitest": "^4.1.8", "vite-plugin-pwa": "^1.2.0", "vite-plugin-wasm": "^3.5.0", "workbox-window": "^7.4.0" diff --git a/apps/host/src/host-url.ts b/apps/host/src/host-url.ts new file mode 100644 index 00000000..7e5d18c3 --- /dev/null +++ b/apps/host/src/host-url.ts @@ -0,0 +1,87 @@ +// Copyright 2026 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: AGPL-3.0-only + +// URL/label parsing helpers for the host shell. Kept separate from the +// main entry wiring so the parsing logic can be unit-tested in isolation. + +import { BASE_DOMAIN, DEBUG } from "@dotli/config/config"; +import { isValidDotLabel } from "@dotli/shared/html"; + +export function parseLocalhostUrl(): string | null { + if (!DEBUG) { + return null; + } + const path = window.location.pathname; + const match = /^\/(localhost(?::\d+)?)(.*)$/.exec(path); + if (match === null) { + return null; + } + const host = match[1]; + const rest = match[2] || ""; + // Strip every reserved host-URL param so they do not leak into the + // proxied product. Covers the five settings axes, the sandbox contract's + // host-only signals (`fullReset`, `v`), and the Playwright auth hook. + const productSearch = new URLSearchParams(window.location.search); + for (const k of RESERVED_HOST_PARAMS) { + productSearch.delete(k); + } + const query = productSearch.toString(); + return `http://${host}${rest}${query ? `?${query}` : ""}${window.location.hash}`; +} + +const RESERVED_HOST_PARAMS = [ + "network", + "chainBackend", + "skipArchiveCache", + "skipCidCache", + "skipWorkerCache", + "fullReset", + "v", + "initAuthSubscribe", +] as const; + +/** + * Extract the `.dot` label from the current hostname. + * + * Returns `"myapp"` for `myapp.dot.li` or `myapp.localhost`. Returns `null` + * for the bare landing pages (`dot.li`, `localhost`) and for sandbox origins + * (`*.app.dot.li`, `*.app.localhost`), which are handled by `app-main.ts`. + * + * The parsed label is validated against the closed `.dot` label charset as + * defense-in-depth before it is threaded into key derivation, origin + * construction (`