@wenathlan/saddle 2.0.151 → 2.0.153
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/biome.jsonc +12 -5
- package/dist/App.d.ts +5 -0
- package/dist/App.js +52 -0
- package/dist/Sol/Sol.d.ts +3 -0
- package/dist/Sol/Sol.js +46 -0
- package/dist/Sol/agentbrowser/agentbrowser.d.ts +9 -0
- package/dist/Sol/agentbrowser/agentbrowser.js +121 -0
- package/dist/Sol/architecture/architecture.d.ts +9 -0
- package/dist/Sol/architecture/architecture.js +93 -0
- package/dist/Sol/compute/compute.d.ts +9 -0
- package/dist/Sol/compute/compute.js +22 -0
- package/dist/Sol/console/console.d.ts +15 -0
- package/dist/Sol/console/console.js +292 -0
- package/dist/Sol/dashboard/dashboard.d.ts +10 -0
- package/dist/Sol/dashboard/dashboard.js +181 -0
- package/dist/Sol/docs/docs.d.ts +9 -0
- package/dist/Sol/docs/docs.js +28 -0
- package/dist/Sol/home/home.d.ts +35 -0
- package/dist/Sol/home/home.js +239 -0
- package/dist/Sol/integrations/integrations.d.ts +9 -0
- package/dist/Sol/integrations/integrations.js +83 -0
- package/dist/Sol/intro/intro.d.ts +7 -0
- package/dist/Sol/intro/intro.js +102 -0
- package/dist/Sol/login/login.d.ts +16 -0
- package/dist/Sol/login/login.js +49 -0
- package/dist/Sol/notfound/notfound.d.ts +9 -0
- package/dist/Sol/notfound/notfound.js +15 -0
- package/dist/Sol/onboarding/onboarding.d.ts +25 -0
- package/dist/Sol/onboarding/onboarding.js +60 -0
- package/dist/Sol/playground/playground.d.ts +9 -0
- package/dist/Sol/playground/playground.js +127 -0
- package/dist/Sol/register/register.d.ts +16 -0
- package/dist/Sol/register/register.js +193 -0
- package/dist/Sol/shell/Shell.d.ts +124 -0
- package/dist/Sol/shell/Shell.js +335 -0
- package/dist/Sol/wingets/ComputeBootladder.d.ts +6 -0
- package/dist/Sol/wingets/ComputeBootladder.js +71 -0
- package/dist/Sol/wingets/ComputeModalitycatalog.d.ts +7 -0
- package/dist/Sol/wingets/ComputeModalitycatalog.js +71 -0
- package/dist/Sol/wingets/ConsoleApibadge.d.ts +7 -0
- package/dist/Sol/wingets/ConsoleApibadge.js +5 -0
- package/dist/Sol/wingets/ConsoleEdgelaw.d.ts +6 -0
- package/dist/Sol/wingets/ConsoleEdgelaw.js +36 -0
- package/dist/Sol/wingets/ConsoleEventstimeline.d.ts +19 -0
- package/dist/Sol/wingets/ConsoleEventstimeline.js +12 -0
- package/dist/Sol/wingets/ConsoleSnapshotcontrols.d.ts +8 -0
- package/dist/Sol/wingets/ConsoleSnapshotcontrols.js +8 -0
- package/dist/Sol/wingets/ConsoleSpecpanel.d.ts +21 -0
- package/dist/Sol/wingets/ConsoleSpecpanel.js +75 -0
- package/dist/Sol/wingets/ConsoleTerminal.d.ts +22 -0
- package/dist/Sol/wingets/ConsoleTerminal.js +77 -0
- package/dist/Sol/wingets/DashboardAccountpanel.d.ts +18 -0
- package/dist/Sol/wingets/DashboardAccountpanel.js +65 -0
- package/dist/Sol/wingets/DashboardAdminpanel.d.ts +6 -0
- package/dist/Sol/wingets/DashboardAdminpanel.js +90 -0
- package/dist/Sol/wingets/DashboardAuditlog.d.ts +8 -0
- package/dist/Sol/wingets/DashboardAuditlog.js +50 -0
- package/dist/Sol/wingets/DashboardCreatesandboxform.d.ts +10 -0
- package/dist/Sol/wingets/DashboardCreatesandboxform.js +71 -0
- package/dist/Sol/wingets/DashboardEventstimeline.d.ts +8 -0
- package/dist/Sol/wingets/DashboardEventstimeline.js +83 -0
- package/dist/Sol/wingets/DashboardGlobalsandboxestable.d.ts +8 -0
- package/dist/Sol/wingets/DashboardGlobalsandboxestable.js +57 -0
- package/dist/Sol/wingets/DashboardMeshnodestable.d.ts +8 -0
- package/dist/Sol/wingets/DashboardMeshnodestable.js +103 -0
- package/dist/Sol/wingets/DashboardSandboxlist.d.ts +10 -0
- package/dist/Sol/wingets/DashboardSandboxlist.js +163 -0
- package/dist/Sol/wingets/DashboardUserstable.d.ts +8 -0
- package/dist/Sol/wingets/DashboardUserstable.js +78 -0
- package/dist/Sol/wingets/HomeEntrygate.d.ts +23 -0
- package/dist/Sol/wingets/HomeEntrygate.js +73 -0
- package/dist/Sol/wingets/HomeMetricstrip.d.ts +9 -0
- package/dist/Sol/wingets/HomeMetricstrip.js +4 -0
- package/dist/Sol/wingets/HomeRuntimediagram.d.ts +1 -0
- package/dist/Sol/wingets/HomeRuntimediagram.js +22 -0
- package/dist/Sol/wingets/LoginForm.d.ts +19 -0
- package/dist/Sol/wingets/LoginForm.js +141 -0
- package/dist/Sol/wingets/RegisterPasswordconfirm.d.ts +13 -0
- package/dist/Sol/wingets/RegisterPasswordconfirm.js +16 -0
- package/dist/Sol/wingets/RegisterPasswordmeter.d.ts +19 -0
- package/dist/Sol/wingets/RegisterPasswordmeter.js +65 -0
- package/dist/Sol/wingets/RegisterUsernamerules.d.ts +11 -0
- package/dist/Sol/wingets/RegisterUsernamerules.js +16 -0
- package/dist/Sol/wingets/index.d.ts +39 -0
- package/dist/Sol/wingets/index.js +39 -0
- package/dist/alternatives.d.ts +508 -0
- package/dist/alternatives.js +950 -0
- package/dist/api.d.ts +154 -0
- package/dist/api.js +174 -0
- package/dist/auth.d.ts +268 -0
- package/dist/auth.js +476 -0
- package/dist/automation.d.ts +425 -0
- package/dist/automation.js +566 -0
- package/dist/browser.d.ts +561 -0
- package/dist/browser.js +1058 -0
- package/dist/capacitor.config.d.ts +45 -0
- package/dist/capacitor.config.js +48 -0
- package/dist/catalog.d.ts +43 -0
- package/dist/catalog.js +44 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.js +168 -0
- package/dist/communication.d.ts +181 -0
- package/dist/communication.js +376 -0
- package/dist/compute.d.ts +1160 -0
- package/dist/compute.js +2272 -0
- package/dist/dashboardlib.d.ts +58 -0
- package/dist/dashboardlib.js +161 -0
- package/dist/db.d.ts +569 -0
- package/dist/db.js +1080 -0
- package/dist/distribution.d.ts +528 -0
- package/dist/distribution.js +810 -0
- package/dist/drizzle.config.d.ts +20 -0
- package/dist/drizzle.config.js +18 -0
- package/dist/edgelandes.d.ts +70 -0
- package/dist/edgelandes.host.d.ts +26 -0
- package/dist/edgelandes.host.js +1089 -0
- package/dist/edgelandes.inproc.d.ts +27 -0
- package/dist/edgelandes.inproc.js +952 -0
- package/dist/edgelandes.js +1133 -0
- package/dist/edgelandes.ninety.d.ts +31 -0
- package/dist/edgelandes.ninety.js +174 -0
- package/dist/edgelandes.virtual.d.ts +25 -0
- package/dist/edgelandes.virtual.js +629 -0
- package/dist/edgeplane.d.ts +114 -0
- package/dist/edgeplane.js +305 -0
- package/dist/examplelocaljob.d.ts +1 -0
- package/{docs/example-localjob.ts → dist/examplelocaljob.js} +0 -1
- package/dist/execution.d.ts +529 -0
- package/dist/execution.js +760 -0
- package/dist/family.d.ts +16 -0
- package/dist/family.js +42 -0
- package/dist/familyurl.d.ts +21 -0
- package/dist/familyurl.js +24 -0
- package/dist/format.d.ts +18 -0
- package/dist/format.js +59 -0
- package/dist/foundation.d.ts +172 -0
- package/dist/foundation.js +312 -0
- package/dist/index.d.ts +735 -0
- package/dist/index.js +1020 -0
- package/dist/integration.d.ts +235 -0
- package/dist/integration.js +228 -0
- package/dist/intelligence.d.ts +93 -0
- package/dist/intelligence.js +144 -0
- package/dist/isolation.d.ts +128 -0
- package/dist/isolation.js +95 -0
- package/dist/labels.d.ts +665 -0
- package/dist/labels.js +751 -0
- package/dist/localauth.d.ts +127 -0
- package/dist/localauth.js +413 -0
- package/dist/media.d.ts +343 -0
- package/dist/media.js +985 -0
- package/dist/mesh.d.ts +177 -0
- package/dist/mesh.js +468 -0
- package/dist/modalityregistry.d.ts +109 -0
- package/dist/modalityregistry.js +1386 -0
- package/dist/modes.d.ts +358 -0
- package/dist/modes.js +133 -0
- package/dist/onboardingstate.d.ts +79 -0
- package/dist/onboardingstate.js +282 -0
- package/dist/orchestrator.d.ts +1338 -0
- package/dist/orchestrator.js +4310 -0
- package/dist/packager/targetcli.js +4 -0
- package/dist/paths.d.ts +2 -0
- package/dist/paths.js +14 -0
- package/dist/performance.d.ts +415 -0
- package/dist/performance.js +826 -0
- package/dist/quantum.d.ts +809 -0
- package/dist/quantum.js +2072 -0
- package/dist/render.d.ts +380 -0
- package/dist/render.js +1072 -0
- package/dist/runall.d.ts +2 -0
- package/dist/runall.js +39 -0
- package/dist/runtimeprobe.d.ts +4 -0
- package/dist/runtimeprobe.js +8 -0
- package/dist/sandbox.d.ts +355 -0
- package/dist/sandbox.js +1721 -0
- package/dist/sandboxenvprofile.d.ts +29 -0
- package/dist/sandboxenvprofile.js +89 -0
- package/dist/sandboxprofile.d.ts +137 -0
- package/dist/sandboxprofile.js +241 -0
- package/dist/sandboxqueue.d.ts +108 -0
- package/dist/sandboxqueue.js +201 -0
- package/dist/sandboxquota.d.ts +121 -0
- package/dist/sandboxquota.js +236 -0
- package/dist/scheduler.d.ts +595 -0
- package/dist/scheduler.js +1258 -0
- package/dist/scriptlanes.d.ts +182 -0
- package/dist/scriptlanes.js +660 -0
- package/dist/scripts.buildengine.d.ts +1 -0
- package/dist/scripts.buildengine.js +39 -0
- package/dist/scripts.securitygate.d.ts +1 -0
- package/dist/scripts.securitygate.js +68 -0
- package/dist/scripts.validateflatnative.d.ts +1 -0
- package/dist/scripts.validateflatnative.js +72 -0
- package/dist/security.d.ts +500 -0
- package/dist/security.js +1169 -0
- package/dist/seedcatalog.d.ts +13 -0
- package/dist/seedcatalog.js +46 -0
- package/dist/server.d.ts +20 -0
- package/dist/server.js +53 -0
- package/dist/speccatalog.d.ts +31 -0
- package/dist/speccatalog.js +163 -0
- package/dist/tiers.d.ts +1001 -0
- package/dist/tiers.js +2169 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +5 -0
- package/dist/vaulthandoff.d.ts +141 -0
- package/dist/vaulthandoff.js +444 -0
- package/dist/virtual.d.ts +640 -0
- package/dist/virtual.js +1284 -0
- package/dist/virtualcpu.d.ts +315 -0
- package/dist/virtualcpu.js +1180 -0
- package/dist/virtualgpu.d.ts +344 -0
- package/dist/virtualgpu.js +1000 -0
- package/dist/virtualization.d.ts +606 -0
- package/dist/virtualization.js +1186 -0
- package/dist/virtualmemory.d.ts +817 -0
- package/dist/virtualmemory.js +1795 -0
- package/dist/vite.config.d.ts +3 -0
- package/dist/vite.config.js +43 -0
- package/dist/vitest.config.d.ts +2 -0
- package/dist/vitest.config.js +25 -0
- package/dist/webserver.d.ts +166 -0
- package/dist/webserver.js +2226 -0
- package/dist/workloadlaw.d.ts +133 -0
- package/dist/workloadlaw.js +193 -0
- package/docs/artifactavailability.md +2 -2
- package/docs/{containerplatforms-2.1.0.md → containerplatforms.2.1.0.md} +1 -1
- package/docs/{documentmap-1.8.12.md → documentmap.1.8.12.md} +1 -1
- package/docs/ninety.modalities.md +108 -0
- package/docs/planning.ninety.md +391 -0
- package/docs/{releasenotes-1.8.13.md → releasenotes.1.8.13.md} +2 -2
- package/docs/{releasenotes-1.8.14.md → releasenotes.1.8.14.md} +1 -1
- package/docs/{repo-synthesis-1.8.16.md → repo.synthesis.1.8.16.md} +1 -1
- package/docs/{todo-1.8.15.md → todo.1.8.15.md} +1 -1
- package/docs/{todo-1.8.16.md → todo.1.8.16.md} +5 -5
- package/docs/todo.md +2 -2
- package/docs/workloadlaw.md +104 -0
- package/index.ts +1 -1
- package/manifest.json +1 -1
- package/modalityregistry.ts +1371 -34
- package/orchestrator.ts +1 -1
- package/package.json +78 -33
- package/docs/CONVERSA.txt +0 -1523
- package/docs/architecture-1.8.19.png +0 -0
- package/docs/assets/architecture.svg +0 -45
- package/docs/assets/saddle-browser-trace.webp +0 -0
- package/docs/assets/saddle-hero-bridge.webp +0 -0
- package/docs/assets/saddle-mark.webp +0 -0
- package/docs/assets/saddle-runtime-map.webp +0 -0
- package/docs/assets/saddlemark.svg +0 -13
- package/docs/saddle.archive.1.8.17.tar.gz.gpg +0 -0
- /package/docs/{architecture-1.8.19.md → architecture.1.8.19.md} +0 -0
- /package/docs/{architecture-1.8.19.mmd → architecture.1.8.19.mmd} +0 -0
- /package/docs/{artifactresearch-1.8.9.md → artifactresearch.1.8.9.md} +0 -0
- /package/docs/{batches-1816-1820.md → batches.1816.to.1820.md} +0 -0
- /package/docs/{brancharchive-2026-08-18.md → brancharchive.2026.08.18.md} +0 -0
- /package/docs/{containerplatforms-1.8.17.md → containerplatforms.1.8.17.md} +0 -0
- /package/docs/{e2ugh-engine.md → e2ugh.engine.md} +0 -0
- /package/docs/{flatbuildresearch-1.8.11.md → flatbuildresearch.1.8.11.md} +0 -0
- /package/docs/{licenseaudit-1.8.12.md → licenseaudit.1.8.12.md} +0 -0
- /package/docs/{mobileconversionresearch-1.8.10.md → mobileconversionresearch.1.8.10.md} +0 -0
- /package/docs/{native-wrappers.md → native.wrappers.md} +0 -0
- /package/docs/{nativeidentityresearch-1.8.11.md → nativeidentityresearch.1.8.11.md} +0 -0
- /package/docs/{releasenotes-1.8.12.md → releasenotes.1.8.12.md} +0 -0
- /package/docs/{releasenotes-1.8.15.md → releasenotes.1.8.15.md} +0 -0
- /package/docs/{releasenotes-1.8.16.md → releasenotes.1.8.16.md} +0 -0
- /package/docs/{releasenotes-1.8.17.md → releasenotes.1.8.17.md} +0 -0
- /package/docs/{releasenotes-1.8.18.md → releasenotes.1.8.18.md} +0 -0
- /package/docs/{releasenotes-1.8.19.md → releasenotes.1.8.19.md} +0 -0
- /package/docs/{reorganization-1.8.8.md → reorganization.1.8.8.md} +0 -0
- /package/docs/{reorganization-1.8.9.md → reorganization.1.8.9.md} +0 -0
- /package/docs/{repo-analysis-1.8.21.json → repo.analysis.1.8.21.json} +0 -0
- /package/docs/{repo-candidates-1.8.21.jsonl → repo.candidates.1.8.21.jsonl} +0 -0
- /package/docs/{repo-research-1.8.16.md → repo.research.1.8.16.md} +0 -0
- /package/docs/{repo-research-1.8.21.md → repo.research.1.8.21.md} +0 -0
- /package/docs/{repo-selected-1.8.21.json → repo.selected.1.8.21.json} +0 -0
- /package/docs/{repo-synthesis-1.8.21.md → repo.synthesis.1.8.21.md} +0 -0
- /package/docs/{research-1.8.18-isolation.md → research.1.8.18.isolation.md} +0 -0
- /package/docs/{research-1.8.19-virtual-browser.md → research.1.8.19.virtual.browser.md} +0 -0
- /package/docs/{scanningresearch-1.8.11.md → scanningresearch.1.8.11.md} +0 -0
- /package/docs/{securityaudit-1.8.7.md → securityaudit.1.8.7.md} +0 -0
- /package/docs/{securityreferences-1.8.11.md → securityreferences.1.8.11.md} +0 -0
- /package/docs/{securityresearch-1.8.11.md → securityresearch.1.8.11.md} +0 -0
- /package/docs/{signingresearch-1.8.12.md → signingresearch.1.8.12.md} +0 -0
- /package/docs/{tauri-glib-risk-1.8.11.md → tauri.glib.risk.1.8.11.md} +0 -0
- /package/docs/{todo-1.8.18.md → todo.1.8.18.md} +0 -0
- /package/docs/{todo-1.8.19.md → todo.1.8.19.md} +0 -0
- /package/docs/{toolchainresearch-1.8.9.md → toolchainresearch.1.8.9.md} +0 -0
- /package/docs/{web-duplicatecleanup.md → web.duplicatecleanup.md} +0 -0
- /package/docs/{web-ideas.md → web.ideas.md} +0 -0
- /package/docs/{workflowimprovements-2026-08-19.md → workflowimprovements.2026.08.19.md} +0 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* onboardingstate.ts — the ONE writer of the visitor's first-run flags, the
|
|
3
|
+
* state of the entry-flow contract (intro → onboarding → the platform). The
|
|
4
|
+
* state is { introseen, onboardingseen } and it is catalogued, not just
|
|
5
|
+
* browser: the accessor consults the platform database over GET
|
|
6
|
+
* /api/v1/onboarding and writes it over POST /api/v1/onboarding, both HTTPS
|
|
7
|
+
* only — the base url arrives through VITE_ONBOARDING_URL and every
|
|
8
|
+
* candidate host is validated first (localhost, loopback, private,
|
|
9
|
+
* link-local, CGNAT and the reserved documentation ranges are refused, so
|
|
10
|
+
* the flags never ride a dev or internal endpoint; mapped IPv6 literals are
|
|
11
|
+
* normalized back to their embedded IPv4 before the public check). The
|
|
12
|
+
* localStorage key saddle.onboarding.seen is purely the offline cache of
|
|
13
|
+
* the database (the DB is the source of truth; the cache avoids replaying
|
|
14
|
+
* the flow offline) and module memory covers client-side re-navigation.
|
|
15
|
+
* Reads reconcile with a seen-wins merge — a stage seen anywhere stays
|
|
16
|
+
* seen, the flow is never forced twice. The page anchors never touch the
|
|
17
|
+
* flags directly: the intro marks introseen, the onboarding marks
|
|
18
|
+
* onboardingseen, the home entry gate only reads.
|
|
19
|
+
*/
|
|
20
|
+
/** the localStorage cache key (the DB mirror, never the source of truth). */
|
|
21
|
+
const CACHE_KEY = 'saddle.onboarding.seen';
|
|
22
|
+
/** the localStorage key of the stable per-browser visitor id. */
|
|
23
|
+
const VISITOR_KEY = 'saddle.visitor.id';
|
|
24
|
+
/** the configured platform base (HTTPS self-hosted gateway); empty answers cache-only. */
|
|
25
|
+
const BASE = (typeof import.meta !== 'undefined'
|
|
26
|
+
? import.meta.env?.VITE_ONBOARDING_URL
|
|
27
|
+
: undefined)?.trim().replace(/\/+$/, '') ?? '';
|
|
28
|
+
/** the platform route the accessor rides (the gateway serves /api/v1/*). */
|
|
29
|
+
const API_PATH = '/api/v1/onboarding';
|
|
30
|
+
/** the abort window of the platform read (ms) — the flow never blocks on a slow gateway. */
|
|
31
|
+
const READ_TIMEOUT_MS = 2500;
|
|
32
|
+
/** module memory: the flags of the session, consulted before any i/o. */
|
|
33
|
+
let memory = null;
|
|
34
|
+
/**
|
|
35
|
+
* Validates a platform base url: HTTPS only, and the host must be public —
|
|
36
|
+
* localhost, loopback, private, link-local, CGNAT, benchmarking,
|
|
37
|
+
* unique-local and the reserved documentation ranges are refused; a mapped
|
|
38
|
+
* IPv6 literal is normalized to its embedded IPv4 first; any other IPv6
|
|
39
|
+
* literal is refused (the gateway rides a name).
|
|
40
|
+
*
|
|
41
|
+
* @param base the configured base url candidate.
|
|
42
|
+
* @returns true when the base may be consulted.
|
|
43
|
+
*/
|
|
44
|
+
export function hostallowed(base) {
|
|
45
|
+
try {
|
|
46
|
+
const url = new URL(base);
|
|
47
|
+
if (url.protocol !== 'https:')
|
|
48
|
+
return false;
|
|
49
|
+
const host = url.hostname
|
|
50
|
+
.toLowerCase()
|
|
51
|
+
.replace(/\.+$/, '')
|
|
52
|
+
.replace(/^\[|\]$/g, '');
|
|
53
|
+
if (host.length === 0)
|
|
54
|
+
return false;
|
|
55
|
+
if (host === 'localhost' || host === '0.0.0.0' || host === '::1' || host === '::')
|
|
56
|
+
return false;
|
|
57
|
+
if (host.endsWith('.localhost') || host.endsWith('.local') || host.endsWith('.internal')) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
if (host.endsWith('.home.arpa'))
|
|
61
|
+
return false;
|
|
62
|
+
if (host.includes(':'))
|
|
63
|
+
return mappedipv6(host);
|
|
64
|
+
const v4 = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
65
|
+
if (v4)
|
|
66
|
+
return publicipv4(Number(v4[1]), Number(v4[2]));
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Answers whether an IPv6 literal is the IPv4-mapped form of a PUBLIC
|
|
75
|
+
* address (the url parser normalizes `::ffff:127.0.0.1` to hex hextets, so
|
|
76
|
+
* both spellings are normalized here and the embedded IPv4 decides).
|
|
77
|
+
*
|
|
78
|
+
* @param host the bracket-free ipv6 literal.
|
|
79
|
+
* @returns true when the literal maps to a public ipv4 address.
|
|
80
|
+
*/
|
|
81
|
+
function mappedipv6(host) {
|
|
82
|
+
if (!host.startsWith('::ffff:'))
|
|
83
|
+
return false;
|
|
84
|
+
const tail = host.slice('::ffff:'.length);
|
|
85
|
+
const decimals = tail.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
86
|
+
if (decimals)
|
|
87
|
+
return publicipv4(Number(decimals[1]), Number(decimals[2]));
|
|
88
|
+
// the hex spelling the url parser produces: the first hextet of the pair
|
|
89
|
+
// carries the first two ipv4 octets, and they alone decide the ranges
|
|
90
|
+
const hextets = tail.match(/^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
|
|
91
|
+
if (hextets) {
|
|
92
|
+
const high = Number.parseInt(hextets[1], 16);
|
|
93
|
+
return publicipv4((high >> 8) & 0xff, high & 0xff);
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
/** the public side of an ipv4 address (the first two octets decide). */
|
|
98
|
+
function publicipv4(a, b) {
|
|
99
|
+
if (a === 0 || a === 10 || a === 127)
|
|
100
|
+
return false;
|
|
101
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
102
|
+
return false;
|
|
103
|
+
if (a === 192 && b === 168)
|
|
104
|
+
return false;
|
|
105
|
+
if (a === 169 && b === 254)
|
|
106
|
+
return false;
|
|
107
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
108
|
+
return false;
|
|
109
|
+
if (a === 192 && b === 0)
|
|
110
|
+
return false;
|
|
111
|
+
if (a === 198 && (b === 18 || b === 19))
|
|
112
|
+
return false;
|
|
113
|
+
if (a === 198 && (b === 51 || b === 52))
|
|
114
|
+
return false;
|
|
115
|
+
if (a === 203 && b === 0)
|
|
116
|
+
return false;
|
|
117
|
+
if (a >= 224)
|
|
118
|
+
return false;
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Answers the flags after one stage was marked seen (pure: the writer and
|
|
123
|
+
* the tests share this merge — seen-wins, a stage never unsees).
|
|
124
|
+
*
|
|
125
|
+
* @param state the flags before the write.
|
|
126
|
+
* @param stage the stage being marked seen.
|
|
127
|
+
* @returns the flags after the write.
|
|
128
|
+
*/
|
|
129
|
+
export function mergeStage(state, stage) {
|
|
130
|
+
return stage === 'intro'
|
|
131
|
+
? { ...state, introseen: true }
|
|
132
|
+
: { ...state, introseen: true, onboardingseen: true };
|
|
133
|
+
}
|
|
134
|
+
/** Reads the localStorage cache; any corruption answers null (the cache is best effort). */
|
|
135
|
+
function readCache() {
|
|
136
|
+
if (typeof window === 'undefined')
|
|
137
|
+
return null;
|
|
138
|
+
try {
|
|
139
|
+
const raw = window.localStorage.getItem(CACHE_KEY);
|
|
140
|
+
if (!raw)
|
|
141
|
+
return null;
|
|
142
|
+
const parsed = JSON.parse(raw);
|
|
143
|
+
return { introseen: parsed.introseen === true, onboardingseen: parsed.onboardingseen === true };
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/** Mirrors the flags into the localStorage cache (silently skipped when storage is sealed). */
|
|
150
|
+
function writeCache(state) {
|
|
151
|
+
if (typeof window === 'undefined')
|
|
152
|
+
return;
|
|
153
|
+
try {
|
|
154
|
+
window.localStorage.setItem(CACHE_KEY, JSON.stringify(state));
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// the cache is best effort: the DB and the memory stay authoritative
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The stable per-browser visitor id (generated once, cached beside the
|
|
162
|
+
* flags); storage unavailable answers the shared web key.
|
|
163
|
+
*
|
|
164
|
+
* @returns the visitor string the gateway rows are keyed by.
|
|
165
|
+
*/
|
|
166
|
+
function visitor() {
|
|
167
|
+
if (typeof window === 'undefined')
|
|
168
|
+
return 'web';
|
|
169
|
+
try {
|
|
170
|
+
const existing = window.localStorage.getItem(VISITOR_KEY);
|
|
171
|
+
if (existing)
|
|
172
|
+
return existing;
|
|
173
|
+
const fresh = crypto.randomUUID();
|
|
174
|
+
window.localStorage.setItem(VISITOR_KEY, fresh);
|
|
175
|
+
return fresh;
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return 'web';
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/** Reads the flags off the platform database; unreachable or unconfigured answers null. */
|
|
182
|
+
async function readRemote() {
|
|
183
|
+
if (!BASE || !hostallowed(BASE) || typeof fetch === 'undefined')
|
|
184
|
+
return null;
|
|
185
|
+
const controller = new AbortController();
|
|
186
|
+
const timer = setTimeout(() => controller.abort(), READ_TIMEOUT_MS);
|
|
187
|
+
try {
|
|
188
|
+
const response = await fetch(`${BASE}${API_PATH}?visitor=${encodeURIComponent(visitor())}`, {
|
|
189
|
+
headers: { accept: 'application/json' },
|
|
190
|
+
signal: controller.signal,
|
|
191
|
+
});
|
|
192
|
+
if (!response.ok)
|
|
193
|
+
return null;
|
|
194
|
+
const answer = (await response.json());
|
|
195
|
+
return { introseen: answer.introseen === true, onboardingseen: answer.onboardingseen === true };
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
clearTimeout(timer);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/** Writes the flags to the platform database over POST; failures leave the cache holding them. */
|
|
205
|
+
async function writeRemote(state) {
|
|
206
|
+
if (!BASE || !hostallowed(BASE) || typeof fetch === 'undefined')
|
|
207
|
+
return;
|
|
208
|
+
try {
|
|
209
|
+
await fetch(`${BASE}${API_PATH}`, {
|
|
210
|
+
method: 'POST',
|
|
211
|
+
headers: { 'content-type': 'application/json' },
|
|
212
|
+
body: JSON.stringify({ visitor: visitor(), ...state }),
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
// the cache mirrors the flags until the platform answers again
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Synchronous read of the known state: module memory first, then the
|
|
221
|
+
* localStorage cache. The entry gate uses this for the flash-free fast
|
|
222
|
+
* path; null means the state is still unknown and the caller must await
|
|
223
|
+
* loadOnboardingState.
|
|
224
|
+
*
|
|
225
|
+
* @returns the known flags, or null when nothing is known yet.
|
|
226
|
+
*/
|
|
227
|
+
export function readOnboardingState() {
|
|
228
|
+
if (memory)
|
|
229
|
+
return memory;
|
|
230
|
+
const cached = readCache();
|
|
231
|
+
if (cached) {
|
|
232
|
+
memory = cached;
|
|
233
|
+
return cached;
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Full resolve of the flags: memory/cache first, then the platform database
|
|
239
|
+
* over GET /api/v1/onboarding — reconciled with the seen-wins merge so a
|
|
240
|
+
* stage seen anywhere stays seen. The session answer is memoized;
|
|
241
|
+
* re-navigation never refetches.
|
|
242
|
+
*
|
|
243
|
+
* @returns the resolved visitor onboarding flags.
|
|
244
|
+
*/
|
|
245
|
+
export async function loadOnboardingState() {
|
|
246
|
+
const local = readOnboardingState() ?? { introseen: false, onboardingseen: false };
|
|
247
|
+
if (!BASE || !hostallowed(BASE) || (local.introseen && local.onboardingseen))
|
|
248
|
+
return local;
|
|
249
|
+
const remote = await readRemote();
|
|
250
|
+
if (!remote)
|
|
251
|
+
return local;
|
|
252
|
+
const merged = {
|
|
253
|
+
introseen: local.introseen || remote.introseen,
|
|
254
|
+
onboardingseen: local.onboardingseen || remote.onboardingseen,
|
|
255
|
+
};
|
|
256
|
+
memory = merged;
|
|
257
|
+
writeCache(merged);
|
|
258
|
+
return merged;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Marks the intro stage seen and persists it everywhere: module memory, the
|
|
262
|
+
* localStorage cache and — when a platform base is configured — the
|
|
263
|
+
* database over POST /api/v1/onboarding. The ONLY writer of the intro flag.
|
|
264
|
+
*/
|
|
265
|
+
export function markIntroSeen() {
|
|
266
|
+
memory = mergeStage(memory ?? readCache() ?? { introseen: false, onboardingseen: false }, 'intro');
|
|
267
|
+
writeCache(memory);
|
|
268
|
+
void writeRemote(memory);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Marks the onboarding stage seen (finish or skip — both imply the intro)
|
|
272
|
+
* and persists it everywhere. The ONLY writer of the onboarding flag.
|
|
273
|
+
*/
|
|
274
|
+
export function markOnboardingSeen() {
|
|
275
|
+
memory = mergeStage(memory ?? readCache() ?? { introseen: false, onboardingseen: false }, 'onboarding');
|
|
276
|
+
writeCache(memory);
|
|
277
|
+
void writeRemote(memory);
|
|
278
|
+
}
|
|
279
|
+
/** Test seam: clears the module memory (the cache and the platform are untouched). */
|
|
280
|
+
export function resetOnboardingMemory() {
|
|
281
|
+
memory = null;
|
|
282
|
+
}
|