BNS indexer core, source chain and pipe protocol as shared modules
Design step 1 (TheseusNavigator/DESIGN-bns-indexer-service.md). Theseus's
bns-indexer.js and Ariadne's daemon each had their own index logic, and
Ariadne's warm-started from the operator's pre-resolved list, which has no
owners. Ariadne's Thread is to own indexing for the whole machine, with
Theseus reading from it, so both need the same code and the same results.
- bns-index-core.js: the 0.3.70 indexer logic without the Electron port:
local copies -> published raw snapshot (dl.silentmode.st, Sia mirror)
-> electrum delta every 30 s, index rebuilt from the beacon txs with
owner, TLDs from tlds.bch. Writes two local copies atomically (current
+ previous), emits change/fresh with the names that changed, and can
pause its network work (Economy) without stopping.
- bns-source-chain.js: how readers get names without depending on the
indexer: memory -> live indexer over the pipe -> local copies -> public
snapshots -> public BNS indexer for single names, marked provisional
and replaced when verified data arrives.
- bns-pipe.js: newline-JSON hello/snapshot/get/subscribe/poll over
\.\pipe\ariadne-bns[-<SID>]. Node cannot reach the pipe handle, so the
client trusts a connection only through a helper that checks the server
process (GetNamedPipeServerProcessId) and then relays that same
connection. A pipe name can be squatted while the indexer is down;
a 127.0.0.1 port cannot be checked at all.
Theseus keeps its own bns-indexer.js unchanged until migration step 3.
2026-10-03 16:01:26 +02:00
|
|
|
// The source chain: how a reader (Ariadne's resolver, and Theseus from
|
|
|
|
|
// migration step 3) gets BNS names without depending on the indexer.
|
|
|
|
|
// Spec: TheseusNavigator/DESIGN-bns-indexer-service.md, "The source chain".
|
|
|
|
|
//
|
|
|
|
|
// 1. memory whatever is loaded (normally all of it)
|
|
|
|
|
// 2. the live indexer over the pipe, server-checked: snapshot, then pushes
|
|
|
|
|
// 3. local copies the indexer's two files + this reader's own copy
|
|
|
|
|
// 4. public snapshots one download, rebuilt locally; background only,
|
|
|
|
|
// when the local data is older than an hour
|
|
|
|
|
// 5. public BNS indexer GET <gateway>/api/name/<n>, single names, only when
|
|
|
|
|
// there is no data yet or it is stale; PROVISIONAL:
|
|
|
|
|
// served at once, replaced when verified data
|
|
|
|
|
// arrives, never good enough for publisher checks
|
|
|
|
|
//
|
|
|
|
|
// A lookup never waits on 2-4; they refresh memory in the background.
|
|
|
|
|
import fs from "node:fs";
|
|
|
|
|
import path from "node:path";
|
|
|
|
|
import { EventEmitter } from "node:events";
|
Theseus: read BNS names from Ariadne's indexer; its own is the standby
Migration step 3 (DESIGN-bns-indexer-service.md). Ariadne's Thread now owns
BNS indexing on the machine, so Theseus no longer runs a second electrum
indexer beside it.
bns-indexer.js keeps its process and its messages to main.js, but inside
it is now an index host on the shared source chain:
- Ariadne's indexer over its pipe, trusted only after ariadne-helper.exe
has checked the server process on that connection (found through
Ariadne's uninstall key), then pushes;
- the local copies: Ariadne's files for both scopes, Theseus's own raw
copy, the bundled one. The richest wins.
- Theseus's own index copy, written from the pipe data.
The shared core runs as Theseus's own indexer only while Ariadne is
unhealthy. That means: no pipe 4 s after launch, a pipe that fails the
check, a pipe that went silent, or an index not confirmed for 10 min while
Ariadne is not paused. The own indexer warm-starts from Ariadne's
snapshot, so there is no download and no cold sync. It hands back after
90 s of health, so a flapping service does not start and stop it. Economy
is not a failure and never triggers a takeover. With no checkable Ariadne
(portable, not installed, older than the pipe) the own indexer starts at
once, as in 0.3.70. The one thing Theseus does on Ariadne's side is run
the indexer's task at launch when "Launch at start" is off.
main.js passes the shared module paths (packaged as .mjs, which is why the
shared modules no longer import each other) and keeps the host's status.
The Ariadne panel takes its state from the indexer task when one exists,
and the sub-page says where Theseus's names come from.
2026-10-03 17:06:24 +02:00
|
|
|
|
|
|
|
|
// No imports between the shared modules: Theseus ships each as a single .mjs
|
|
|
|
|
// in its resources, where a relative import would not resolve. These two
|
|
|
|
|
// are the same as in bns-index-core.js.
|
|
|
|
|
function readSnapshotFile(p) {
|
|
|
|
|
try {
|
|
|
|
|
if (!p || !fs.existsSync(p)) return null;
|
|
|
|
|
const parsed = JSON.parse(fs.readFileSync(p, "utf8").replace(/^\uFEFF/, ""));
|
|
|
|
|
if (!parsed || Array.isArray(parsed) || !Array.isArray(parsed.history) || typeof parsed.txs !== "object") return null;
|
|
|
|
|
return parsed;
|
|
|
|
|
} catch { return null; }
|
|
|
|
|
}
|
|
|
|
|
function diffIndex(prev, next) {
|
|
|
|
|
const changed = [];
|
|
|
|
|
if (!prev) return [...next.keys()];
|
|
|
|
|
for (const [k, e] of next) {
|
|
|
|
|
const o = prev.get(k);
|
|
|
|
|
if (!o || JSON.stringify(o) !== JSON.stringify(e)) changed.push(k);
|
|
|
|
|
}
|
|
|
|
|
for (const k of prev.keys()) if (!next.has(k)) changed.push(k);
|
|
|
|
|
return changed;
|
|
|
|
|
}
|
BNS indexer core, source chain and pipe protocol as shared modules
Design step 1 (TheseusNavigator/DESIGN-bns-indexer-service.md). Theseus's
bns-indexer.js and Ariadne's daemon each had their own index logic, and
Ariadne's warm-started from the operator's pre-resolved list, which has no
owners. Ariadne's Thread is to own indexing for the whole machine, with
Theseus reading from it, so both need the same code and the same results.
- bns-index-core.js: the 0.3.70 indexer logic without the Electron port:
local copies -> published raw snapshot (dl.silentmode.st, Sia mirror)
-> electrum delta every 30 s, index rebuilt from the beacon txs with
owner, TLDs from tlds.bch. Writes two local copies atomically (current
+ previous), emits change/fresh with the names that changed, and can
pause its network work (Economy) without stopping.
- bns-source-chain.js: how readers get names without depending on the
indexer: memory -> live indexer over the pipe -> local copies -> public
snapshots -> public BNS indexer for single names, marked provisional
and replaced when verified data arrives.
- bns-pipe.js: newline-JSON hello/snapshot/get/subscribe/poll over
\.\pipe\ariadne-bns[-<SID>]. Node cannot reach the pipe handle, so the
client trusts a connection only through a helper that checks the server
process (GetNamedPipeServerProcessId) and then relays that same
connection. A pipe name can be squatted while the indexer is down;
a 127.0.0.1 port cannot be checked at all.
Theseus keeps its own bns-indexer.js unchanged until migration step 3.
2026-10-03 16:01:26 +02:00
|
|
|
|
|
|
|
|
const PROVISIONAL_TTL_MS = 30_000;
|
|
|
|
|
const NEGATIVE_TTL_MS = 30_000;
|
|
|
|
|
|
|
|
|
|
// Index-format file: what a reader keeps as its own copy, since over the pipe
|
|
|
|
|
// it receives the index, not the raw evidence.
|
|
|
|
|
function readIndexFile(p) {
|
|
|
|
|
try {
|
|
|
|
|
if (!p || !fs.existsSync(p)) return null;
|
|
|
|
|
const j = JSON.parse(fs.readFileSync(p, "utf8"));
|
|
|
|
|
if (j && j.format === "bns-index" && Array.isArray(j.names)) return j;
|
|
|
|
|
} catch {}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// opts:
|
|
|
|
|
// resolver resolver-web.js module
|
|
|
|
|
// connectPipe async () => client (bns-pipe.js connectIndexerPipe), or null
|
Theseus: read BNS names from Ariadne's indexer; its own is the standby
Migration step 3 (DESIGN-bns-indexer-service.md). Ariadne's Thread now owns
BNS indexing on the machine, so Theseus no longer runs a second electrum
indexer beside it.
bns-indexer.js keeps its process and its messages to main.js, but inside
it is now an index host on the shared source chain:
- Ariadne's indexer over its pipe, trusted only after ariadne-helper.exe
has checked the server process on that connection (found through
Ariadne's uninstall key), then pushes;
- the local copies: Ariadne's files for both scopes, Theseus's own raw
copy, the bundled one. The richest wins.
- Theseus's own index copy, written from the pipe data.
The shared core runs as Theseus's own indexer only while Ariadne is
unhealthy. That means: no pipe 4 s after launch, a pipe that fails the
check, a pipe that went silent, or an index not confirmed for 10 min while
Ariadne is not paused. The own indexer warm-starts from Ariadne's
snapshot, so there is no download and no cold sync. It hands back after
90 s of health, so a flapping service does not start and stop it. Economy
is not a failure and never triggers a takeover. With no checkable Ariadne
(portable, not installed, older than the pipe) the own indexer starts at
once, as in 0.3.70. The one thing Theseus does on Ariadne's side is run
the indexer's task at launch when "Launch at start" is off.
main.js passes the shared module paths (packaged as .mjs, which is why the
shared modules no longer import each other) and keeps the host's status.
The Ariadne panel takes its state from the indexer task when one exists,
and the sub-page says where Theseus's names come from.
2026-10-03 17:06:24 +02:00
|
|
|
// localPaths raw snapshots to read: the indexer's copies (current,
|
|
|
|
|
// previous), then any others; the one with the most
|
|
|
|
|
// evidence wins
|
BNS indexer core, source chain and pipe protocol as shared modules
Design step 1 (TheseusNavigator/DESIGN-bns-indexer-service.md). Theseus's
bns-indexer.js and Ariadne's daemon each had their own index logic, and
Ariadne's warm-started from the operator's pre-resolved list, which has no
owners. Ariadne's Thread is to own indexing for the whole machine, with
Theseus reading from it, so both need the same code and the same results.
- bns-index-core.js: the 0.3.70 indexer logic without the Electron port:
local copies -> published raw snapshot (dl.silentmode.st, Sia mirror)
-> electrum delta every 30 s, index rebuilt from the beacon txs with
owner, TLDs from tlds.bch. Writes two local copies atomically (current
+ previous), emits change/fresh with the names that changed, and can
pause its network work (Economy) without stopping.
- bns-source-chain.js: how readers get names without depending on the
indexer: memory -> live indexer over the pipe -> local copies -> public
snapshots -> public BNS indexer for single names, marked provisional
and replaced when verified data arrives.
- bns-pipe.js: newline-JSON hello/snapshot/get/subscribe/poll over
\.\pipe\ariadne-bns[-<SID>]. Node cannot reach the pipe handle, so the
client trusts a connection only through a helper that checks the server
process (GetNamedPipeServerProcessId) and then relays that same
connection. A pipe name can be squatted while the indexer is down;
a 127.0.0.1 port cannot be checked at all.
Theseus keeps its own bns-indexer.js unchanged until migration step 3.
2026-10-03 16:01:26 +02:00
|
|
|
// statePath the indexer's small state file ({builtAt, ...}), optional
|
|
|
|
|
// ownCopyPath this reader's last-known-good copy (index format)
|
Theseus: read BNS names from Ariadne's indexer; its own is the standby
Migration step 3 (DESIGN-bns-indexer-service.md). Ariadne's Thread now owns
BNS indexing on the machine, so Theseus no longer runs a second electrum
indexer beside it.
bns-indexer.js keeps its process and its messages to main.js, but inside
it is now an index host on the shared source chain:
- Ariadne's indexer over its pipe, trusted only after ariadne-helper.exe
has checked the server process on that connection (found through
Ariadne's uninstall key), then pushes;
- the local copies: Ariadne's files for both scopes, Theseus's own raw
copy, the bundled one. The richest wins.
- Theseus's own index copy, written from the pipe data.
The shared core runs as Theseus's own indexer only while Ariadne is
unhealthy. That means: no pipe 4 s after launch, a pipe that fails the
check, a pipe that went silent, or an index not confirmed for 10 min while
Ariadne is not paused. The own indexer warm-starts from Ariadne's
snapshot, so there is no download and no cold sync. It hands back after
90 s of health, so a flapping service does not start and stop it. Economy
is not a failure and never triggers a takeover. With no checkable Ariadne
(portable, not installed, older than the pipe) the own indexer starts at
once, as in 0.3.70. The one thing Theseus does on Ariadne's side is run
the indexer's task at launch when "Launch at start" is off.
main.js passes the shared module paths (packaged as .mjs, which is why the
shared modules no longer import each other) and keeps the host's status.
The Ariadne panel takes its state from the indexer task when one exists,
and the sub-page says where Theseus's names come from.
2026-10-03 17:06:24 +02:00
|
|
|
// publicSnapshotUrls, publicIndexerUrl [] / null switch a source off
|
BNS indexer core, source chain and pipe protocol as shared modules
Design step 1 (TheseusNavigator/DESIGN-bns-indexer-service.md). Theseus's
bns-indexer.js and Ariadne's daemon each had their own index logic, and
Ariadne's warm-started from the operator's pre-resolved list, which has no
owners. Ariadne's Thread is to own indexing for the whole machine, with
Theseus reading from it, so both need the same code and the same results.
- bns-index-core.js: the 0.3.70 indexer logic without the Electron port:
local copies -> published raw snapshot (dl.silentmode.st, Sia mirror)
-> electrum delta every 30 s, index rebuilt from the beacon txs with
owner, TLDs from tlds.bch. Writes two local copies atomically (current
+ previous), emits change/fresh with the names that changed, and can
pause its network work (Economy) without stopping.
- bns-source-chain.js: how readers get names without depending on the
indexer: memory -> live indexer over the pipe -> local copies -> public
snapshots -> public BNS indexer for single names, marked provisional
and replaced when verified data arrives.
- bns-pipe.js: newline-JSON hello/snapshot/get/subscribe/poll over
\.\pipe\ariadne-bns[-<SID>]. Node cannot reach the pipe handle, so the
client trusts a connection only through a helper that checks the server
process (GetNamedPipeServerProcessId) and then relays that same
connection. A pipe name can be squatted while the indexer is down;
a 127.0.0.1 port cannot be checked at all.
Theseus keeps its own bns-indexer.js unchanged until migration step 3.
2026-10-03 16:01:26 +02:00
|
|
|
// staleMs local data older than this counts as stale (default 5 min)
|
|
|
|
|
// log
|
|
|
|
|
// Events: "change" {changed, source}, "mismatch" {name, provisional, verified},
|
Theseus: read BNS names from Ariadne's indexer; its own is the standby
Migration step 3 (DESIGN-bns-indexer-service.md). Ariadne's Thread now owns
BNS indexing on the machine, so Theseus no longer runs a second electrum
indexer beside it.
bns-indexer.js keeps its process and its messages to main.js, but inside
it is now an index host on the shared source chain:
- Ariadne's indexer over its pipe, trusted only after ariadne-helper.exe
has checked the server process on that connection (found through
Ariadne's uninstall key), then pushes;
- the local copies: Ariadne's files for both scopes, Theseus's own raw
copy, the bundled one. The richest wins.
- Theseus's own index copy, written from the pipe data.
The shared core runs as Theseus's own indexer only while Ariadne is
unhealthy. That means: no pipe 4 s after launch, a pipe that fails the
check, a pipe that went silent, or an index not confirmed for 10 min while
Ariadne is not paused. The own indexer warm-starts from Ariadne's
snapshot, so there is no download and no cold sync. It hands back after
90 s of health, so a flapping service does not start and stop it. Economy
is not a failure and never triggers a takeover. With no checkable Ariadne
(portable, not installed, older than the pipe) the own indexer starts at
once, as in 0.3.70. The one thing Theseus does on Ariadne's side is run
the indexer's task at launch when "Launch at start" is off.
main.js passes the shared module paths (packaged as .mjs, which is why the
shared modules no longer import each other) and keeps the host's status.
The Ariadne panel takes its state from the indexer task when one exists,
and the sub-page says where Theseus's names come from.
2026-10-03 17:06:24 +02:00
|
|
|
// "pipe" {connected, error?}, "fresh" {builtAt}, "paused" boolean
|
BNS indexer core, source chain and pipe protocol as shared modules
Design step 1 (TheseusNavigator/DESIGN-bns-indexer-service.md). Theseus's
bns-indexer.js and Ariadne's daemon each had their own index logic, and
Ariadne's warm-started from the operator's pre-resolved list, which has no
owners. Ariadne's Thread is to own indexing for the whole machine, with
Theseus reading from it, so both need the same code and the same results.
- bns-index-core.js: the 0.3.70 indexer logic without the Electron port:
local copies -> published raw snapshot (dl.silentmode.st, Sia mirror)
-> electrum delta every 30 s, index rebuilt from the beacon txs with
owner, TLDs from tlds.bch. Writes two local copies atomically (current
+ previous), emits change/fresh with the names that changed, and can
pause its network work (Economy) without stopping.
- bns-source-chain.js: how readers get names without depending on the
indexer: memory -> live indexer over the pipe -> local copies -> public
snapshots -> public BNS indexer for single names, marked provisional
and replaced when verified data arrives.
- bns-pipe.js: newline-JSON hello/snapshot/get/subscribe/poll over
\.\pipe\ariadne-bns[-<SID>]. Node cannot reach the pipe handle, so the
client trusts a connection only through a helper that checks the server
process (GetNamedPipeServerProcessId) and then relays that same
connection. A pipe name can be squatted while the indexer is down;
a 127.0.0.1 port cannot be checked at all.
Theseus keeps its own bns-indexer.js unchanged until migration step 3.
2026-10-03 16:01:26 +02:00
|
|
|
export function createSourceChain(opts) {
|
|
|
|
|
const R = opts.resolver;
|
|
|
|
|
const log = opts.log || (() => {});
|
|
|
|
|
const ev = new EventEmitter();
|
|
|
|
|
const staleMs = opts.staleMs ?? 5 * 60_000;
|
|
|
|
|
const publicRefreshMs = opts.publicRefreshMs ?? 60 * 60_000;
|
|
|
|
|
const publicSnapshotUrls = (opts.publicSnapshotUrls || [R.NETWORK?.nameListUrl, R.NETWORK?.nameListMirrorUrl]).filter(Boolean);
|
Theseus: read BNS names from Ariadne's indexer; its own is the standby
Migration step 3 (DESIGN-bns-indexer-service.md). Ariadne's Thread now owns
BNS indexing on the machine, so Theseus no longer runs a second electrum
indexer beside it.
bns-indexer.js keeps its process and its messages to main.js, but inside
it is now an index host on the shared source chain:
- Ariadne's indexer over its pipe, trusted only after ariadne-helper.exe
has checked the server process on that connection (found through
Ariadne's uninstall key), then pushes;
- the local copies: Ariadne's files for both scopes, Theseus's own raw
copy, the bundled one. The richest wins.
- Theseus's own index copy, written from the pipe data.
The shared core runs as Theseus's own indexer only while Ariadne is
unhealthy. That means: no pipe 4 s after launch, a pipe that fails the
check, a pipe that went silent, or an index not confirmed for 10 min while
Ariadne is not paused. The own indexer warm-starts from Ariadne's
snapshot, so there is no download and no cold sync. It hands back after
90 s of health, so a flapping service does not start and stop it. Economy
is not a failure and never triggers a takeover. With no checkable Ariadne
(portable, not installed, older than the pipe) the own indexer starts at
once, as in 0.3.70. The one thing Theseus does on Ariadne's side is run
the indexer's task at launch when "Launch at start" is off.
main.js passes the shared module paths (packaged as .mjs, which is why the
shared modules no longer import each other) and keeps the host's status.
The Ariadne panel takes its state from the indexer task when one exists,
and the sub-page says where Theseus's names come from.
2026-10-03 17:06:24 +02:00
|
|
|
const publicIndexerUrl = opts.publicIndexerUrl === undefined ? "https://navigate.st" : opts.publicIndexerUrl;
|
BNS indexer core, source chain and pipe protocol as shared modules
Design step 1 (TheseusNavigator/DESIGN-bns-indexer-service.md). Theseus's
bns-indexer.js and Ariadne's daemon each had their own index logic, and
Ariadne's warm-started from the operator's pre-resolved list, which has no
owners. Ariadne's Thread is to own indexing for the whole machine, with
Theseus reading from it, so both need the same code and the same results.
- bns-index-core.js: the 0.3.70 indexer logic without the Electron port:
local copies -> published raw snapshot (dl.silentmode.st, Sia mirror)
-> electrum delta every 30 s, index rebuilt from the beacon txs with
owner, TLDs from tlds.bch. Writes two local copies atomically (current
+ previous), emits change/fresh with the names that changed, and can
pause its network work (Economy) without stopping.
- bns-source-chain.js: how readers get names without depending on the
indexer: memory -> live indexer over the pipe -> local copies -> public
snapshots -> public BNS indexer for single names, marked provisional
and replaced when verified data arrives.
- bns-pipe.js: newline-JSON hello/snapshot/get/subscribe/poll over
\.\pipe\ariadne-bns[-<SID>]. Node cannot reach the pipe handle, so the
client trusts a connection only through a helper that checks the server
process (GetNamedPipeServerProcessId) and then relays that same
connection. A pipe name can be squatted while the indexer is down;
a 127.0.0.1 port cannot be checked at all.
Theseus keeps its own bns-indexer.js unchanged until migration step 3.
2026-10-03 16:01:26 +02:00
|
|
|
|
|
|
|
|
const st = {
|
|
|
|
|
index: new Map(), source: null, builtAt: 0, gen: 0, tlds: [], historyLen: 0,
|
|
|
|
|
fileMtimes: {}, pipe: null, pipeError: null, pipeSince: 0, lastPush: 0,
|
Theseus: read BNS names from Ariadne's indexer; its own is the standby
Migration step 3 (DESIGN-bns-indexer-service.md). Ariadne's Thread now owns
BNS indexing on the machine, so Theseus no longer runs a second electrum
indexer beside it.
bns-indexer.js keeps its process and its messages to main.js, but inside
it is now an index host on the shared source chain:
- Ariadne's indexer over its pipe, trusted only after ariadne-helper.exe
has checked the server process on that connection (found through
Ariadne's uninstall key), then pushes;
- the local copies: Ariadne's files for both scopes, Theseus's own raw
copy, the bundled one. The richest wins.
- Theseus's own index copy, written from the pipe data.
The shared core runs as Theseus's own indexer only while Ariadne is
unhealthy. That means: no pipe 4 s after launch, a pipe that fails the
check, a pipe that went silent, or an index not confirmed for 10 min while
Ariadne is not paused. The own indexer warm-starts from Ariadne's
snapshot, so there is no download and no cold sync. It hands back after
90 s of health, so a flapping service does not start and stop it. Economy
is not a failure and never triggers a takeover. With no checkable Ariadne
(portable, not installed, older than the pipe) the own indexer starts at
once, as in 0.3.70. The one thing Theseus does on Ariadne's side is run
the indexer's task at launch when "Launch at start" is off.
main.js passes the shared module paths (packaged as .mjs, which is why the
shared modules no longer import each other) and keeps the host's status.
The Ariadne panel takes its state from the indexer task when one exists,
and the sub-page says where Theseus's names come from.
2026-10-03 17:06:24 +02:00
|
|
|
lastPublic: 0, lastOwnWrite: 0, paused: false,
|
BNS indexer core, source chain and pipe protocol as shared modules
Design step 1 (TheseusNavigator/DESIGN-bns-indexer-service.md). Theseus's
bns-indexer.js and Ariadne's daemon each had their own index logic, and
Ariadne's warm-started from the operator's pre-resolved list, which has no
owners. Ariadne's Thread is to own indexing for the whole machine, with
Theseus reading from it, so both need the same code and the same results.
- bns-index-core.js: the 0.3.70 indexer logic without the Electron port:
local copies -> published raw snapshot (dl.silentmode.st, Sia mirror)
-> electrum delta every 30 s, index rebuilt from the beacon txs with
owner, TLDs from tlds.bch. Writes two local copies atomically (current
+ previous), emits change/fresh with the names that changed, and can
pause its network work (Economy) without stopping.
- bns-source-chain.js: how readers get names without depending on the
indexer: memory -> live indexer over the pipe -> local copies -> public
snapshots -> public BNS indexer for single names, marked provisional
and replaced when verified data arrives.
- bns-pipe.js: newline-JSON hello/snapshot/get/subscribe/poll over
\.\pipe\ariadne-bns[-<SID>]. Node cannot reach the pipe handle, so the
client trusts a connection only through a helper that checks the server
process (GetNamedPipeServerProcessId) and then relays that same
connection. A pipe name can be squatted while the indexer is down;
a 127.0.0.1 port cannot be checked at all.
Theseus keeps its own bns-indexer.js unchanged until migration step 3.
2026-10-03 16:01:26 +02:00
|
|
|
};
|
|
|
|
|
const provisional = new Map(); // key -> { at, entry|null }
|
|
|
|
|
|
|
|
|
|
function adopt(next, source, { builtAt, gen, tlds, historyLen } = {}) {
|
|
|
|
|
const changed = diffIndex(st.index, next);
|
|
|
|
|
st.index = next;
|
|
|
|
|
st.source = source;
|
|
|
|
|
if (builtAt != null) st.builtAt = builtAt;
|
|
|
|
|
if (gen != null) st.gen = gen;
|
|
|
|
|
st.tlds = tlds || (typeof R.getBcnrTlds === "function" ? R.getBcnrTlds(next) : st.tlds);
|
|
|
|
|
if (historyLen != null) st.historyLen = historyLen;
|
|
|
|
|
checkProvisional(changed);
|
|
|
|
|
if (changed.length) ev.emit("change", { changed, source });
|
|
|
|
|
return changed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verified data arrived: drop provisional answers it covers, say so when
|
|
|
|
|
// they differ (the reader replaces tabs / cached answers).
|
|
|
|
|
function checkProvisional(changed) {
|
|
|
|
|
for (const [k, p] of provisional) {
|
|
|
|
|
const v = st.index.get(k);
|
|
|
|
|
if (!v) continue;
|
|
|
|
|
provisional.delete(k);
|
|
|
|
|
if (p.entry && JSON.stringify(strip(p.entry)) !== JSON.stringify(strip(v))) ev.emit("mismatch", { name: k, provisional: p.entry, verified: v });
|
|
|
|
|
}
|
|
|
|
|
void changed;
|
|
|
|
|
}
|
|
|
|
|
const strip = (e) => ({ records: e.records || {}, owner: e.owner ?? null, category: e.category ?? null });
|
|
|
|
|
|
|
|
|
|
// ---- 3: local copies ----
|
|
|
|
|
function readState() {
|
|
|
|
|
try { return opts.statePath ? JSON.parse(fs.readFileSync(opts.statePath, "utf8")) : null; } catch { return null; }
|
|
|
|
|
}
|
|
|
|
|
function loadLocal(reason = "start") {
|
Theseus: read BNS names from Ariadne's indexer; its own is the standby
Migration step 3 (DESIGN-bns-indexer-service.md). Ariadne's Thread now owns
BNS indexing on the machine, so Theseus no longer runs a second electrum
indexer beside it.
bns-indexer.js keeps its process and its messages to main.js, but inside
it is now an index host on the shared source chain:
- Ariadne's indexer over its pipe, trusted only after ariadne-helper.exe
has checked the server process on that connection (found through
Ariadne's uninstall key), then pushes;
- the local copies: Ariadne's files for both scopes, Theseus's own raw
copy, the bundled one. The richest wins.
- Theseus's own index copy, written from the pipe data.
The shared core runs as Theseus's own indexer only while Ariadne is
unhealthy. That means: no pipe 4 s after launch, a pipe that fails the
check, a pipe that went silent, or an index not confirmed for 10 min while
Ariadne is not paused. The own indexer warm-starts from Ariadne's
snapshot, so there is no download and no cold sync. It hands back after
90 s of health, so a flapping service does not start and stop it. Economy
is not a failure and never triggers a takeover. With no checkable Ariadne
(portable, not installed, older than the pipe) the own indexer starts at
once, as in 0.3.70. The one thing Theseus does on Ariadne's side is run
the indexer's task at launch when "Launch at start" is off.
main.js passes the shared module paths (packaged as .mjs, which is why the
shared modules no longer import each other) and keeps the host's status.
The Ariadne panel takes its state from the indexer task when one exists,
and the sub-page says where Theseus's names come from.
2026-10-03 17:06:24 +02:00
|
|
|
// The copy with the most evidence; the earlier path on a tie.
|
|
|
|
|
let best = null;
|
BNS indexer core, source chain and pipe protocol as shared modules
Design step 1 (TheseusNavigator/DESIGN-bns-indexer-service.md). Theseus's
bns-indexer.js and Ariadne's daemon each had their own index logic, and
Ariadne's warm-started from the operator's pre-resolved list, which has no
owners. Ariadne's Thread is to own indexing for the whole machine, with
Theseus reading from it, so both need the same code and the same results.
- bns-index-core.js: the 0.3.70 indexer logic without the Electron port:
local copies -> published raw snapshot (dl.silentmode.st, Sia mirror)
-> electrum delta every 30 s, index rebuilt from the beacon txs with
owner, TLDs from tlds.bch. Writes two local copies atomically (current
+ previous), emits change/fresh with the names that changed, and can
pause its network work (Economy) without stopping.
- bns-source-chain.js: how readers get names without depending on the
indexer: memory -> live indexer over the pipe -> local copies -> public
snapshots -> public BNS indexer for single names, marked provisional
and replaced when verified data arrives.
- bns-pipe.js: newline-JSON hello/snapshot/get/subscribe/poll over
\.\pipe\ariadne-bns[-<SID>]. Node cannot reach the pipe handle, so the
client trusts a connection only through a helper that checks the server
process (GetNamedPipeServerProcessId) and then relays that same
connection. A pipe name can be squatted while the indexer is down;
a 127.0.0.1 port cannot be checked at all.
Theseus keeps its own bns-indexer.js unchanged until migration step 3.
2026-10-03 16:01:26 +02:00
|
|
|
for (const p of opts.localPaths || []) {
|
|
|
|
|
const snap = readSnapshotFile(p);
|
Theseus: read BNS names from Ariadne's indexer; its own is the standby
Migration step 3 (DESIGN-bns-indexer-service.md). Ariadne's Thread now owns
BNS indexing on the machine, so Theseus no longer runs a second electrum
indexer beside it.
bns-indexer.js keeps its process and its messages to main.js, but inside
it is now an index host on the shared source chain:
- Ariadne's indexer over its pipe, trusted only after ariadne-helper.exe
has checked the server process on that connection (found through
Ariadne's uninstall key), then pushes;
- the local copies: Ariadne's files for both scopes, Theseus's own raw
copy, the bundled one. The richest wins.
- Theseus's own index copy, written from the pipe data.
The shared core runs as Theseus's own indexer only while Ariadne is
unhealthy. That means: no pipe 4 s after launch, a pipe that fails the
check, a pipe that went silent, or an index not confirmed for 10 min while
Ariadne is not paused. The own indexer warm-starts from Ariadne's
snapshot, so there is no download and no cold sync. It hands back after
90 s of health, so a flapping service does not start and stop it. Economy
is not a failure and never triggers a takeover. With no checkable Ariadne
(portable, not installed, older than the pipe) the own indexer starts at
once, as in 0.3.70. The one thing Theseus does on Ariadne's side is run
the indexer's task at launch when "Launch at start" is off.
main.js passes the shared module paths (packaged as .mjs, which is why the
shared modules no longer import each other) and keeps the host's status.
The Ariadne panel takes its state from the indexer task when one exists,
and the sub-page says where Theseus's names come from.
2026-10-03 17:06:24 +02:00
|
|
|
if (!snap || (R.BEACON_SCRIPTHASH && snap.beacon && snap.beacon !== R.BEACON_SCRIPTHASH)) continue;
|
|
|
|
|
try { st.fileMtimes[p] = fs.statSync(p).mtimeMs; } catch {}
|
|
|
|
|
if (!best || snap.history.length > best.snap.history.length) best = { p, snap };
|
|
|
|
|
}
|
|
|
|
|
if (best) {
|
|
|
|
|
const { p, snap } = best;
|
BNS indexer core, source chain and pipe protocol as shared modules
Design step 1 (TheseusNavigator/DESIGN-bns-indexer-service.md). Theseus's
bns-indexer.js and Ariadne's daemon each had their own index logic, and
Ariadne's warm-started from the operator's pre-resolved list, which has no
owners. Ariadne's Thread is to own indexing for the whole machine, with
Theseus reading from it, so both need the same code and the same results.
- bns-index-core.js: the 0.3.70 indexer logic without the Electron port:
local copies -> published raw snapshot (dl.silentmode.st, Sia mirror)
-> electrum delta every 30 s, index rebuilt from the beacon txs with
owner, TLDs from tlds.bch. Writes two local copies atomically (current
+ previous), emits change/fresh with the names that changed, and can
pause its network work (Economy) without stopping.
- bns-source-chain.js: how readers get names without depending on the
indexer: memory -> live indexer over the pipe -> local copies -> public
snapshots -> public BNS indexer for single names, marked provisional
and replaced when verified data arrives.
- bns-pipe.js: newline-JSON hello/snapshot/get/subscribe/poll over
\.\pipe\ariadne-bns[-<SID>]. Node cannot reach the pipe handle, so the
client trusts a connection only through a helper that checks the server
process (GetNamedPipeServerProcessId) and then relays that same
connection. A pipe name can be squatted while the indexer is down;
a 127.0.0.1 port cannot be checked at all.
Theseus keeps its own bns-indexer.js unchanged until migration step 3.
2026-10-03 16:01:26 +02:00
|
|
|
try {
|
|
|
|
|
if (snap.history.length < st.historyLen && st.source !== null && reason !== "start") return false;
|
|
|
|
|
const idx = R.buildIndexFromSnapshot({ snapshot: snap });
|
|
|
|
|
const state = readState();
|
Theseus: read BNS names from Ariadne's indexer; its own is the standby
Migration step 3 (DESIGN-bns-indexer-service.md). Ariadne's Thread now owns
BNS indexing on the machine, so Theseus no longer runs a second electrum
indexer beside it.
bns-indexer.js keeps its process and its messages to main.js, but inside
it is now an index host on the shared source chain:
- Ariadne's indexer over its pipe, trusted only after ariadne-helper.exe
has checked the server process on that connection (found through
Ariadne's uninstall key), then pushes;
- the local copies: Ariadne's files for both scopes, Theseus's own raw
copy, the bundled one. The richest wins.
- Theseus's own index copy, written from the pipe data.
The shared core runs as Theseus's own indexer only while Ariadne is
unhealthy. That means: no pipe 4 s after launch, a pipe that fails the
check, a pipe that went silent, or an index not confirmed for 10 min while
Ariadne is not paused. The own indexer warm-starts from Ariadne's
snapshot, so there is no download and no cold sync. It hands back after
90 s of health, so a flapping service does not start and stop it. Economy
is not a failure and never triggers a takeover. With no checkable Ariadne
(portable, not installed, older than the pipe) the own indexer starts at
once, as in 0.3.70. The one thing Theseus does on Ariadne's side is run
the indexer's task at launch when "Launch at start" is off.
main.js passes the shared module paths (packaged as .mjs, which is why the
shared modules no longer import each other) and keeps the host's status.
The Ariadne panel takes its state from the indexer task when one exists,
and the sub-page says where Theseus's names come from.
2026-10-03 17:06:24 +02:00
|
|
|
const saved = Date.parse(snap.savedAt || "") || st.fileMtimes[p] || 0;
|
BNS indexer core, source chain and pipe protocol as shared modules
Design step 1 (TheseusNavigator/DESIGN-bns-indexer-service.md). Theseus's
bns-indexer.js and Ariadne's daemon each had their own index logic, and
Ariadne's warm-started from the operator's pre-resolved list, which has no
owners. Ariadne's Thread is to own indexing for the whole machine, with
Theseus reading from it, so both need the same code and the same results.
- bns-index-core.js: the 0.3.70 indexer logic without the Electron port:
local copies -> published raw snapshot (dl.silentmode.st, Sia mirror)
-> electrum delta every 30 s, index rebuilt from the beacon txs with
owner, TLDs from tlds.bch. Writes two local copies atomically (current
+ previous), emits change/fresh with the names that changed, and can
pause its network work (Economy) without stopping.
- bns-source-chain.js: how readers get names without depending on the
indexer: memory -> live indexer over the pipe -> local copies -> public
snapshots -> public BNS indexer for single names, marked provisional
and replaced when verified data arrives.
- bns-pipe.js: newline-JSON hello/snapshot/get/subscribe/poll over
\.\pipe\ariadne-bns[-<SID>]. Node cannot reach the pipe handle, so the
client trusts a connection only through a helper that checks the server
process (GetNamedPipeServerProcessId) and then relays that same
connection. A pipe name can be squatted while the indexer is down;
a 127.0.0.1 port cannot be checked at all.
Theseus keeps its own bns-indexer.js unchanged until migration step 3.
2026-10-03 16:01:26 +02:00
|
|
|
const builtAt = Math.max(saved, Number(state?.builtAt) || 0);
|
Theseus: read BNS names from Ariadne's indexer; its own is the standby
Migration step 3 (DESIGN-bns-indexer-service.md). Ariadne's Thread now owns
BNS indexing on the machine, so Theseus no longer runs a second electrum
indexer beside it.
bns-indexer.js keeps its process and its messages to main.js, but inside
it is now an index host on the shared source chain:
- Ariadne's indexer over its pipe, trusted only after ariadne-helper.exe
has checked the server process on that connection (found through
Ariadne's uninstall key), then pushes;
- the local copies: Ariadne's files for both scopes, Theseus's own raw
copy, the bundled one. The richest wins.
- Theseus's own index copy, written from the pipe data.
The shared core runs as Theseus's own indexer only while Ariadne is
unhealthy. That means: no pipe 4 s after launch, a pipe that fails the
check, a pipe that went silent, or an index not confirmed for 10 min while
Ariadne is not paused. The own indexer warm-starts from Ariadne's
snapshot, so there is no download and no cold sync. It hands back after
90 s of health, so a flapping service does not start and stop it. Economy
is not a failure and never triggers a takeover. With no checkable Ariadne
(portable, not installed, older than the pipe) the own indexer starts at
once, as in 0.3.70. The one thing Theseus does on Ariadne's side is run
the indexer's task at launch when "Launch at start" is off.
main.js passes the shared module paths (packaged as .mjs, which is why the
shared modules no longer import each other) and keeps the host's status.
The Ariadne panel takes its state from the indexer task when one exists,
and the sub-page says where Theseus's names come from.
2026-10-03 17:06:24 +02:00
|
|
|
adopt(idx, `local:${p}`, { builtAt, historyLen: snap.history.length, gen: Number(state?.gen) || st.gen });
|
BNS indexer core, source chain and pipe protocol as shared modules
Design step 1 (TheseusNavigator/DESIGN-bns-indexer-service.md). Theseus's
bns-indexer.js and Ariadne's daemon each had their own index logic, and
Ariadne's warm-started from the operator's pre-resolved list, which has no
owners. Ariadne's Thread is to own indexing for the whole machine, with
Theseus reading from it, so both need the same code and the same results.
- bns-index-core.js: the 0.3.70 indexer logic without the Electron port:
local copies -> published raw snapshot (dl.silentmode.st, Sia mirror)
-> electrum delta every 30 s, index rebuilt from the beacon txs with
owner, TLDs from tlds.bch. Writes two local copies atomically (current
+ previous), emits change/fresh with the names that changed, and can
pause its network work (Economy) without stopping.
- bns-source-chain.js: how readers get names without depending on the
indexer: memory -> live indexer over the pipe -> local copies -> public
snapshots -> public BNS indexer for single names, marked provisional
and replaced when verified data arrives.
- bns-pipe.js: newline-JSON hello/snapshot/get/subscribe/poll over
\.\pipe\ariadne-bns[-<SID>]. Node cannot reach the pipe handle, so the
client trusts a connection only through a helper that checks the server
process (GetNamedPipeServerProcessId) and then relays that same
connection. A pipe name can be squatted while the indexer is down;
a 127.0.0.1 port cannot be checked at all.
Theseus keeps its own bns-indexer.js unchanged until migration step 3.
2026-10-03 16:01:26 +02:00
|
|
|
return true;
|
|
|
|
|
} catch (e) { log(`source chain: ${p} unusable: ${e.message}`); }
|
|
|
|
|
}
|
|
|
|
|
const own = readIndexFile(opts.ownCopyPath);
|
|
|
|
|
if (own && own.names.length >= st.index.size) {
|
|
|
|
|
adopt(new Map(own.names), "own-copy", { builtAt: own.builtAt || 0, gen: own.gen, tlds: own.tlds });
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// Only reload when a file actually changed (one stat per file per tick).
|
|
|
|
|
function localChanged() {
|
|
|
|
|
for (const p of opts.localPaths || []) {
|
|
|
|
|
try { const m = fs.statSync(p).mtimeMs; if (m !== st.fileMtimes[p]) return true; } catch {}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function writeOwnCopy() {
|
|
|
|
|
if (!opts.ownCopyPath || Date.now() - st.lastOwnWrite < 60_000) return;
|
|
|
|
|
st.lastOwnWrite = Date.now();
|
|
|
|
|
try {
|
|
|
|
|
fs.mkdirSync(path.dirname(opts.ownCopyPath), { recursive: true });
|
|
|
|
|
const tmp = opts.ownCopyPath + ".tmp";
|
|
|
|
|
fs.writeFileSync(tmp, JSON.stringify({ format: "bns-index", network: R.NETWORK?.id, gen: st.gen, builtAt: st.builtAt, tlds: st.tlds, names: [...st.index] }));
|
|
|
|
|
fs.renameSync(tmp, opts.ownCopyPath);
|
|
|
|
|
} catch (e) { log("source chain: own copy not written:", e.message); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---- 2: the live indexer ----
|
|
|
|
|
let backoff = 5_000, reconnectTimer = null, stopped = false;
|
|
|
|
|
async function connect() {
|
|
|
|
|
reconnectTimer = null;
|
|
|
|
|
if (stopped || !opts.connectPipe || st.pipe) return;
|
|
|
|
|
let c;
|
|
|
|
|
try {
|
|
|
|
|
c = await opts.connectPipe();
|
|
|
|
|
const hello = await c.request("hello");
|
|
|
|
|
if (hello.network && R.NETWORK?.id && hello.network !== R.NETWORK.id) throw new Error(`indexer is on ${hello.network}`);
|
|
|
|
|
const snap = await c.request("snapshot", {}, { timeoutMs: 60_000 });
|
|
|
|
|
st.pipe = c; st.pipeError = null; st.pipeSince = st.lastPush = Date.now(); backoff = 5_000;
|
Theseus: read BNS names from Ariadne's indexer; its own is the standby
Migration step 3 (DESIGN-bns-indexer-service.md). Ariadne's Thread now owns
BNS indexing on the machine, so Theseus no longer runs a second electrum
indexer beside it.
bns-indexer.js keeps its process and its messages to main.js, but inside
it is now an index host on the shared source chain:
- Ariadne's indexer over its pipe, trusted only after ariadne-helper.exe
has checked the server process on that connection (found through
Ariadne's uninstall key), then pushes;
- the local copies: Ariadne's files for both scopes, Theseus's own raw
copy, the bundled one. The richest wins.
- Theseus's own index copy, written from the pipe data.
The shared core runs as Theseus's own indexer only while Ariadne is
unhealthy. That means: no pipe 4 s after launch, a pipe that fails the
check, a pipe that went silent, or an index not confirmed for 10 min while
Ariadne is not paused. The own indexer warm-starts from Ariadne's
snapshot, so there is no download and no cold sync. It hands back after
90 s of health, so a flapping service does not start and stop it. Economy
is not a failure and never triggers a takeover. With no checkable Ariadne
(portable, not installed, older than the pipe) the own indexer starts at
once, as in 0.3.70. The one thing Theseus does on Ariadne's side is run
the indexer's task at launch when "Launch at start" is off.
main.js passes the shared module paths (packaged as .mjs, which is why the
shared modules no longer import each other) and keeps the host's status.
The Ariadne panel takes its state from the indexer task when one exists,
and the sub-page says where Theseus's names come from.
2026-10-03 17:06:24 +02:00
|
|
|
setPaused(!!hello.paused);
|
BNS indexer core, source chain and pipe protocol as shared modules
Design step 1 (TheseusNavigator/DESIGN-bns-indexer-service.md). Theseus's
bns-indexer.js and Ariadne's daemon each had their own index logic, and
Ariadne's warm-started from the operator's pre-resolved list, which has no
owners. Ariadne's Thread is to own indexing for the whole machine, with
Theseus reading from it, so both need the same code and the same results.
- bns-index-core.js: the 0.3.70 indexer logic without the Electron port:
local copies -> published raw snapshot (dl.silentmode.st, Sia mirror)
-> electrum delta every 30 s, index rebuilt from the beacon txs with
owner, TLDs from tlds.bch. Writes two local copies atomically (current
+ previous), emits change/fresh with the names that changed, and can
pause its network work (Economy) without stopping.
- bns-source-chain.js: how readers get names without depending on the
indexer: memory -> live indexer over the pipe -> local copies -> public
snapshots -> public BNS indexer for single names, marked provisional
and replaced when verified data arrives.
- bns-pipe.js: newline-JSON hello/snapshot/get/subscribe/poll over
\.\pipe\ariadne-bns[-<SID>]. Node cannot reach the pipe handle, so the
client trusts a connection only through a helper that checks the server
process (GetNamedPipeServerProcessId) and then relays that same
connection. A pipe name can be squatted while the indexer is down;
a 127.0.0.1 port cannot be checked at all.
Theseus keeps its own bns-indexer.js unchanged until migration step 3.
2026-10-03 16:01:26 +02:00
|
|
|
adopt(new Map(snap.names), "pipe", { builtAt: snap.builtAt, gen: snap.gen, tlds: snap.tlds });
|
|
|
|
|
writeOwnCopy();
|
|
|
|
|
c.on("push", onPush);
|
|
|
|
|
c.once("close", () => {
|
|
|
|
|
if (st.pipe !== c) return;
|
|
|
|
|
st.pipe = null;
|
|
|
|
|
st.pipeError = "closed";
|
|
|
|
|
log("source chain: indexer pipe closed, reading local copies");
|
|
|
|
|
ev.emit("pipe", { connected: false, error: "closed" });
|
|
|
|
|
loadLocal("pipe-closed");
|
|
|
|
|
schedule();
|
|
|
|
|
});
|
|
|
|
|
await c.request("subscribe");
|
|
|
|
|
log(`source chain: live indexer (gen ${snap.gen}, ${snap.names.length} names)`);
|
|
|
|
|
ev.emit("pipe", { connected: true, verdict: c.verdict });
|
|
|
|
|
} catch (e) {
|
|
|
|
|
st.pipeError = e?.message || String(e);
|
|
|
|
|
try { c?.close(); } catch {}
|
|
|
|
|
if (st.pipe === c) st.pipe = null;
|
|
|
|
|
ev.emit("pipe", { connected: false, error: st.pipeError });
|
|
|
|
|
schedule();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
function schedule() {
|
|
|
|
|
if (stopped || reconnectTimer || !opts.connectPipe) return;
|
|
|
|
|
reconnectTimer = setTimeout(connect, backoff);
|
|
|
|
|
backoff = Math.min(backoff * 2, 60_000);
|
|
|
|
|
}
|
|
|
|
|
async function onPush(m) {
|
|
|
|
|
st.lastPush = Date.now();
|
Theseus: read BNS names from Ariadne's indexer; its own is the standby
Migration step 3 (DESIGN-bns-indexer-service.md). Ariadne's Thread now owns
BNS indexing on the machine, so Theseus no longer runs a second electrum
indexer beside it.
bns-indexer.js keeps its process and its messages to main.js, but inside
it is now an index host on the shared source chain:
- Ariadne's indexer over its pipe, trusted only after ariadne-helper.exe
has checked the server process on that connection (found through
Ariadne's uninstall key), then pushes;
- the local copies: Ariadne's files for both scopes, Theseus's own raw
copy, the bundled one. The richest wins.
- Theseus's own index copy, written from the pipe data.
The shared core runs as Theseus's own indexer only while Ariadne is
unhealthy. That means: no pipe 4 s after launch, a pipe that fails the
check, a pipe that went silent, or an index not confirmed for 10 min while
Ariadne is not paused. The own indexer warm-starts from Ariadne's
snapshot, so there is no download and no cold sync. It hands back after
90 s of health, so a flapping service does not start and stop it. Economy
is not a failure and never triggers a takeover. With no checkable Ariadne
(portable, not installed, older than the pipe) the own indexer starts at
once, as in 0.3.70. The one thing Theseus does on Ariadne's side is run
the indexer's task at launch when "Launch at start" is off.
main.js passes the shared module paths (packaged as .mjs, which is why the
shared modules no longer import each other) and keeps the host's status.
The Ariadne panel takes its state from the indexer task when one exists,
and the sub-page says where Theseus's names come from.
2026-10-03 17:06:24 +02:00
|
|
|
if (m.event === "fresh") { st.builtAt = m.builtAt || st.builtAt; ev.emit("fresh", { builtAt: st.builtAt }); return; }
|
|
|
|
|
if (m.event === "paused") { setPaused(!!m.paused); return; }
|
BNS indexer core, source chain and pipe protocol as shared modules
Design step 1 (TheseusNavigator/DESIGN-bns-indexer-service.md). Theseus's
bns-indexer.js and Ariadne's daemon each had their own index logic, and
Ariadne's warm-started from the operator's pre-resolved list, which has no
owners. Ariadne's Thread is to own indexing for the whole machine, with
Theseus reading from it, so both need the same code and the same results.
- bns-index-core.js: the 0.3.70 indexer logic without the Electron port:
local copies -> published raw snapshot (dl.silentmode.st, Sia mirror)
-> electrum delta every 30 s, index rebuilt from the beacon txs with
owner, TLDs from tlds.bch. Writes two local copies atomically (current
+ previous), emits change/fresh with the names that changed, and can
pause its network work (Economy) without stopping.
- bns-source-chain.js: how readers get names without depending on the
indexer: memory -> live indexer over the pipe -> local copies -> public
snapshots -> public BNS indexer for single names, marked provisional
and replaced when verified data arrives.
- bns-pipe.js: newline-JSON hello/snapshot/get/subscribe/poll over
\.\pipe\ariadne-bns[-<SID>]. Node cannot reach the pipe handle, so the
client trusts a connection only through a helper that checks the server
process (GetNamedPipeServerProcessId) and then relays that same
connection. A pipe name can be squatted while the indexer is down;
a 127.0.0.1 port cannot be checked at all.
Theseus keeps its own bns-indexer.js unchanged until migration step 3.
2026-10-03 16:01:26 +02:00
|
|
|
if (m.event !== "change") return;
|
|
|
|
|
if (m.gen <= st.gen) return;
|
|
|
|
|
if (m.entries && m.gen === st.gen + 1) {
|
|
|
|
|
const next = new Map(st.index);
|
|
|
|
|
for (const n of m.changed) { const e = m.entries[n]; e ? next.set(n, e) : next.delete(n); }
|
|
|
|
|
adopt(next, "pipe", { gen: m.gen, builtAt: Date.now() });
|
|
|
|
|
} else {
|
|
|
|
|
// missed a generation, or too many changes for a push: take it all
|
|
|
|
|
try {
|
|
|
|
|
const snap = await st.pipe.request("snapshot", {}, { timeoutMs: 60_000 });
|
|
|
|
|
adopt(new Map(snap.names), "pipe", { builtAt: snap.builtAt, gen: snap.gen, tlds: snap.tlds });
|
|
|
|
|
} catch {}
|
|
|
|
|
}
|
|
|
|
|
writeOwnCopy();
|
|
|
|
|
}
|
|
|
|
|
|
Theseus: read BNS names from Ariadne's indexer; its own is the standby
Migration step 3 (DESIGN-bns-indexer-service.md). Ariadne's Thread now owns
BNS indexing on the machine, so Theseus no longer runs a second electrum
indexer beside it.
bns-indexer.js keeps its process and its messages to main.js, but inside
it is now an index host on the shared source chain:
- Ariadne's indexer over its pipe, trusted only after ariadne-helper.exe
has checked the server process on that connection (found through
Ariadne's uninstall key), then pushes;
- the local copies: Ariadne's files for both scopes, Theseus's own raw
copy, the bundled one. The richest wins.
- Theseus's own index copy, written from the pipe data.
The shared core runs as Theseus's own indexer only while Ariadne is
unhealthy. That means: no pipe 4 s after launch, a pipe that fails the
check, a pipe that went silent, or an index not confirmed for 10 min while
Ariadne is not paused. The own indexer warm-starts from Ariadne's
snapshot, so there is no download and no cold sync. It hands back after
90 s of health, so a flapping service does not start and stop it. Economy
is not a failure and never triggers a takeover. With no checkable Ariadne
(portable, not installed, older than the pipe) the own indexer starts at
once, as in 0.3.70. The one thing Theseus does on Ariadne's side is run
the indexer's task at launch when "Launch at start" is off.
main.js passes the shared module paths (packaged as .mjs, which is why the
shared modules no longer import each other) and keeps the host's status.
The Ariadne panel takes its state from the indexer task when one exists,
and the sub-page says where Theseus's names come from.
2026-10-03 17:06:24 +02:00
|
|
|
function setPaused(on) {
|
|
|
|
|
if (on === st.paused) return;
|
|
|
|
|
st.paused = on;
|
|
|
|
|
ev.emit("paused", on);
|
|
|
|
|
}
|
|
|
|
|
// One delta poll in the live indexer, on demand (it refuses while paused).
|
|
|
|
|
async function requestPoll() {
|
|
|
|
|
const c = st.pipe;
|
|
|
|
|
if (!c) return false;
|
|
|
|
|
try { const r = await c.request("poll", {}, { timeoutMs: 50_000 }); return !!r.polled; } catch { return false; }
|
|
|
|
|
}
|
|
|
|
|
|
BNS indexer core, source chain and pipe protocol as shared modules
Design step 1 (TheseusNavigator/DESIGN-bns-indexer-service.md). Theseus's
bns-indexer.js and Ariadne's daemon each had their own index logic, and
Ariadne's warm-started from the operator's pre-resolved list, which has no
owners. Ariadne's Thread is to own indexing for the whole machine, with
Theseus reading from it, so both need the same code and the same results.
- bns-index-core.js: the 0.3.70 indexer logic without the Electron port:
local copies -> published raw snapshot (dl.silentmode.st, Sia mirror)
-> electrum delta every 30 s, index rebuilt from the beacon txs with
owner, TLDs from tlds.bch. Writes two local copies atomically (current
+ previous), emits change/fresh with the names that changed, and can
pause its network work (Economy) without stopping.
- bns-source-chain.js: how readers get names without depending on the
indexer: memory -> live indexer over the pipe -> local copies -> public
snapshots -> public BNS indexer for single names, marked provisional
and replaced when verified data arrives.
- bns-pipe.js: newline-JSON hello/snapshot/get/subscribe/poll over
\.\pipe\ariadne-bns[-<SID>]. Node cannot reach the pipe handle, so the
client trusts a connection only through a helper that checks the server
process (GetNamedPipeServerProcessId) and then relays that same
connection. A pipe name can be squatted while the indexer is down;
a 127.0.0.1 port cannot be checked at all.
Theseus keeps its own bns-indexer.js unchanged until migration step 3.
2026-10-03 16:01:26 +02:00
|
|
|
// ---- 4: public snapshots ----
|
|
|
|
|
async function refreshPublic() {
|
|
|
|
|
st.lastPublic = Date.now();
|
|
|
|
|
for (const url of publicSnapshotUrls) {
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(20_000) });
|
|
|
|
|
if (!res.ok) continue;
|
|
|
|
|
const snap = await res.json();
|
|
|
|
|
if (!snap || !Array.isArray(snap.history) || (R.BEACON_SCRIPTHASH && snap.beacon && snap.beacon !== R.BEACON_SCRIPTHASH)) continue;
|
|
|
|
|
if (st.pipe) return false; // the live indexer took over meanwhile
|
|
|
|
|
if (snap.history.length < st.historyLen) return false;
|
|
|
|
|
adopt(R.buildIndexFromSnapshot({ snapshot: snap }), `public:${new URL(url).host}`, { builtAt: Date.now(), historyLen: snap.history.length });
|
|
|
|
|
writeOwnCopy();
|
|
|
|
|
log(`source chain: public snapshot from ${new URL(url).host} (${st.index.size} names)`);
|
|
|
|
|
return true;
|
|
|
|
|
} catch (e) { log(`source chain: public snapshot ${url}: ${e?.cause?.code || e?.message}`); }
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---- 5: public BNS indexer (provisional) ----
|
|
|
|
|
function stale() {
|
|
|
|
|
if (st.pipe) return false;
|
|
|
|
|
return !st.index.size || Date.now() - st.builtAt > staleMs;
|
|
|
|
|
}
|
|
|
|
|
async function fetchProvisional(key) {
|
|
|
|
|
const c = provisional.get(key);
|
|
|
|
|
if (c?.pending) return c.pending;
|
|
|
|
|
if (c && Date.now() - c.at < (c.entry ? PROVISIONAL_TTL_MS : NEGATIVE_TTL_MS)) return c.entry;
|
|
|
|
|
const pending = (async () => {
|
|
|
|
|
try {
|
|
|
|
|
const r = await fetch(`${publicIndexerUrl}/api/name/${encodeURIComponent(key)}`, { signal: AbortSignal.timeout(5_000), headers: { "user-agent": "ariadne-source-chain/1" } });
|
|
|
|
|
if (!r.ok) return null;
|
|
|
|
|
const j = await r.json();
|
|
|
|
|
if (!j || j.registered === false || !j.records) return null;
|
|
|
|
|
return { name: j.name || key, records: j.records, category: j.category, txid: j.txid, height: j.height, owner: j.owner ?? null };
|
|
|
|
|
} catch { return null; }
|
|
|
|
|
})();
|
|
|
|
|
provisional.set(key, { at: Date.now(), entry: null, pending });
|
|
|
|
|
const entry = await pending;
|
|
|
|
|
provisional.set(key, { at: Date.now(), entry, pending: null });
|
|
|
|
|
return entry;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Memory only, synchronous: for hot paths (DNS, SNI).
|
|
|
|
|
function lookup(key) {
|
|
|
|
|
const e = st.index.get(key);
|
|
|
|
|
if (e) return { entry: e, provisional: false, source: st.source };
|
|
|
|
|
const p = provisional.get(key);
|
|
|
|
|
if (p?.entry && Date.now() - p.at < PROVISIONAL_TTL_MS) return { entry: p.entry, provisional: true, source: "public-indexer" };
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
// Memory, then the public indexer when the data is missing or stale.
|
|
|
|
|
// { verifiedOnly: true } never answers provisionally (publisher checks).
|
|
|
|
|
async function resolve(key, { verifiedOnly = false } = {}) {
|
|
|
|
|
const hit = lookup(key);
|
|
|
|
|
if (hit && (!hit.provisional || !verifiedOnly)) return hit;
|
Theseus: read BNS names from Ariadne's indexer; its own is the standby
Migration step 3 (DESIGN-bns-indexer-service.md). Ariadne's Thread now owns
BNS indexing on the machine, so Theseus no longer runs a second electrum
indexer beside it.
bns-indexer.js keeps its process and its messages to main.js, but inside
it is now an index host on the shared source chain:
- Ariadne's indexer over its pipe, trusted only after ariadne-helper.exe
has checked the server process on that connection (found through
Ariadne's uninstall key), then pushes;
- the local copies: Ariadne's files for both scopes, Theseus's own raw
copy, the bundled one. The richest wins.
- Theseus's own index copy, written from the pipe data.
The shared core runs as Theseus's own indexer only while Ariadne is
unhealthy. That means: no pipe 4 s after launch, a pipe that fails the
check, a pipe that went silent, or an index not confirmed for 10 min while
Ariadne is not paused. The own indexer warm-starts from Ariadne's
snapshot, so there is no download and no cold sync. It hands back after
90 s of health, so a flapping service does not start and stop it. Economy
is not a failure and never triggers a takeover. With no checkable Ariadne
(portable, not installed, older than the pipe) the own indexer starts at
once, as in 0.3.70. The one thing Theseus does on Ariadne's side is run
the indexer's task at launch when "Launch at start" is off.
main.js passes the shared module paths (packaged as .mjs, which is why the
shared modules no longer import each other) and keeps the host's status.
The Ariadne panel takes its state from the indexer task when one exists,
and the sub-page says where Theseus's names come from.
2026-10-03 17:06:24 +02:00
|
|
|
if (verifiedOnly || !stale() || !publicIndexerUrl) return null;
|
BNS indexer core, source chain and pipe protocol as shared modules
Design step 1 (TheseusNavigator/DESIGN-bns-indexer-service.md). Theseus's
bns-indexer.js and Ariadne's daemon each had their own index logic, and
Ariadne's warm-started from the operator's pre-resolved list, which has no
owners. Ariadne's Thread is to own indexing for the whole machine, with
Theseus reading from it, so both need the same code and the same results.
- bns-index-core.js: the 0.3.70 indexer logic without the Electron port:
local copies -> published raw snapshot (dl.silentmode.st, Sia mirror)
-> electrum delta every 30 s, index rebuilt from the beacon txs with
owner, TLDs from tlds.bch. Writes two local copies atomically (current
+ previous), emits change/fresh with the names that changed, and can
pause its network work (Economy) without stopping.
- bns-source-chain.js: how readers get names without depending on the
indexer: memory -> live indexer over the pipe -> local copies -> public
snapshots -> public BNS indexer for single names, marked provisional
and replaced when verified data arrives.
- bns-pipe.js: newline-JSON hello/snapshot/get/subscribe/poll over
\.\pipe\ariadne-bns[-<SID>]. Node cannot reach the pipe handle, so the
client trusts a connection only through a helper that checks the server
process (GetNamedPipeServerProcessId) and then relays that same
connection. A pipe name can be squatted while the indexer is down;
a 127.0.0.1 port cannot be checked at all.
Theseus keeps its own bns-indexer.js unchanged until migration step 3.
2026-10-03 16:01:26 +02:00
|
|
|
const entry = await fetchProvisional(key);
|
|
|
|
|
return entry ? { entry, provisional: true, source: "public-indexer" } : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let tick = null, beat = null;
|
|
|
|
|
function start() {
|
|
|
|
|
loadLocal("start");
|
|
|
|
|
connect();
|
|
|
|
|
tick = setInterval(() => {
|
|
|
|
|
if (!st.pipe && localChanged()) loadLocal("file-changed");
|
Theseus: read BNS names from Ariadne's indexer; its own is the standby
Migration step 3 (DESIGN-bns-indexer-service.md). Ariadne's Thread now owns
BNS indexing on the machine, so Theseus no longer runs a second electrum
indexer beside it.
bns-indexer.js keeps its process and its messages to main.js, but inside
it is now an index host on the shared source chain:
- Ariadne's indexer over its pipe, trusted only after ariadne-helper.exe
has checked the server process on that connection (found through
Ariadne's uninstall key), then pushes;
- the local copies: Ariadne's files for both scopes, Theseus's own raw
copy, the bundled one. The richest wins.
- Theseus's own index copy, written from the pipe data.
The shared core runs as Theseus's own indexer only while Ariadne is
unhealthy. That means: no pipe 4 s after launch, a pipe that fails the
check, a pipe that went silent, or an index not confirmed for 10 min while
Ariadne is not paused. The own indexer warm-starts from Ariadne's
snapshot, so there is no download and no cold sync. It hands back after
90 s of health, so a flapping service does not start and stop it. Economy
is not a failure and never triggers a takeover. With no checkable Ariadne
(portable, not installed, older than the pipe) the own indexer starts at
once, as in 0.3.70. The one thing Theseus does on Ariadne's side is run
the indexer's task at launch when "Launch at start" is off.
main.js passes the shared module paths (packaged as .mjs, which is why the
shared modules no longer import each other) and keeps the host's status.
The Ariadne panel takes its state from the indexer task when one exists,
and the sub-page says where Theseus's names come from.
2026-10-03 17:06:24 +02:00
|
|
|
if (!st.pipe && publicSnapshotUrls.length && Date.now() - st.builtAt > publicRefreshMs && Date.now() - st.lastPublic > publicRefreshMs) refreshPublic();
|
BNS indexer core, source chain and pipe protocol as shared modules
Design step 1 (TheseusNavigator/DESIGN-bns-indexer-service.md). Theseus's
bns-indexer.js and Ariadne's daemon each had their own index logic, and
Ariadne's warm-started from the operator's pre-resolved list, which has no
owners. Ariadne's Thread is to own indexing for the whole machine, with
Theseus reading from it, so both need the same code and the same results.
- bns-index-core.js: the 0.3.70 indexer logic without the Electron port:
local copies -> published raw snapshot (dl.silentmode.st, Sia mirror)
-> electrum delta every 30 s, index rebuilt from the beacon txs with
owner, TLDs from tlds.bch. Writes two local copies atomically (current
+ previous), emits change/fresh with the names that changed, and can
pause its network work (Economy) without stopping.
- bns-source-chain.js: how readers get names without depending on the
indexer: memory -> live indexer over the pipe -> local copies -> public
snapshots -> public BNS indexer for single names, marked provisional
and replaced when verified data arrives.
- bns-pipe.js: newline-JSON hello/snapshot/get/subscribe/poll over
\.\pipe\ariadne-bns[-<SID>]. Node cannot reach the pipe handle, so the
client trusts a connection only through a helper that checks the server
process (GetNamedPipeServerProcessId) and then relays that same
connection. A pipe name can be squatted while the indexer is down;
a 127.0.0.1 port cannot be checked at all.
Theseus keeps its own bns-indexer.js unchanged until migration step 3.
2026-10-03 16:01:26 +02:00
|
|
|
}, 10_000);
|
|
|
|
|
// A silent pipe is a wedged indexer: the indexer pushes "fresh" after
|
|
|
|
|
// every 30 s poll, so minutes of silence means ask, and drop it if mute.
|
|
|
|
|
beat = setInterval(async () => {
|
|
|
|
|
const c = st.pipe;
|
|
|
|
|
if (!c || Date.now() - st.lastPush < 3 * 60_000) return;
|
Theseus: read BNS names from Ariadne's indexer; its own is the standby
Migration step 3 (DESIGN-bns-indexer-service.md). Ariadne's Thread now owns
BNS indexing on the machine, so Theseus no longer runs a second electrum
indexer beside it.
bns-indexer.js keeps its process and its messages to main.js, but inside
it is now an index host on the shared source chain:
- Ariadne's indexer over its pipe, trusted only after ariadne-helper.exe
has checked the server process on that connection (found through
Ariadne's uninstall key), then pushes;
- the local copies: Ariadne's files for both scopes, Theseus's own raw
copy, the bundled one. The richest wins.
- Theseus's own index copy, written from the pipe data.
The shared core runs as Theseus's own indexer only while Ariadne is
unhealthy. That means: no pipe 4 s after launch, a pipe that fails the
check, a pipe that went silent, or an index not confirmed for 10 min while
Ariadne is not paused. The own indexer warm-starts from Ariadne's
snapshot, so there is no download and no cold sync. It hands back after
90 s of health, so a flapping service does not start and stop it. Economy
is not a failure and never triggers a takeover. With no checkable Ariadne
(portable, not installed, older than the pipe) the own indexer starts at
once, as in 0.3.70. The one thing Theseus does on Ariadne's side is run
the indexer's task at launch when "Launch at start" is off.
main.js passes the shared module paths (packaged as .mjs, which is why the
shared modules no longer import each other) and keeps the host's status.
The Ariadne panel takes its state from the indexer task when one exists,
and the sub-page says where Theseus's names come from.
2026-10-03 17:06:24 +02:00
|
|
|
try { const h = await c.request("hello", {}, { timeoutMs: 10_000 }); st.lastPush = Date.now(); setPaused(!!h.paused); if (h.gen > st.gen) onPush({ event: "change", gen: h.gen, changed: [] }); }
|
BNS indexer core, source chain and pipe protocol as shared modules
Design step 1 (TheseusNavigator/DESIGN-bns-indexer-service.md). Theseus's
bns-indexer.js and Ariadne's daemon each had their own index logic, and
Ariadne's warm-started from the operator's pre-resolved list, which has no
owners. Ariadne's Thread is to own indexing for the whole machine, with
Theseus reading from it, so both need the same code and the same results.
- bns-index-core.js: the 0.3.70 indexer logic without the Electron port:
local copies -> published raw snapshot (dl.silentmode.st, Sia mirror)
-> electrum delta every 30 s, index rebuilt from the beacon txs with
owner, TLDs from tlds.bch. Writes two local copies atomically (current
+ previous), emits change/fresh with the names that changed, and can
pause its network work (Economy) without stopping.
- bns-source-chain.js: how readers get names without depending on the
indexer: memory -> live indexer over the pipe -> local copies -> public
snapshots -> public BNS indexer for single names, marked provisional
and replaced when verified data arrives.
- bns-pipe.js: newline-JSON hello/snapshot/get/subscribe/poll over
\.\pipe\ariadne-bns[-<SID>]. Node cannot reach the pipe handle, so the
client trusts a connection only through a helper that checks the server
process (GetNamedPipeServerProcessId) and then relays that same
connection. A pipe name can be squatted while the indexer is down;
a 127.0.0.1 port cannot be checked at all.
Theseus keeps its own bns-indexer.js unchanged until migration step 3.
2026-10-03 16:01:26 +02:00
|
|
|
catch { c.close(); }
|
|
|
|
|
}, 60_000);
|
|
|
|
|
// No data at all yet: the public snapshot now, in the background.
|
Theseus: read BNS names from Ariadne's indexer; its own is the standby
Migration step 3 (DESIGN-bns-indexer-service.md). Ariadne's Thread now owns
BNS indexing on the machine, so Theseus no longer runs a second electrum
indexer beside it.
bns-indexer.js keeps its process and its messages to main.js, but inside
it is now an index host on the shared source chain:
- Ariadne's indexer over its pipe, trusted only after ariadne-helper.exe
has checked the server process on that connection (found through
Ariadne's uninstall key), then pushes;
- the local copies: Ariadne's files for both scopes, Theseus's own raw
copy, the bundled one. The richest wins.
- Theseus's own index copy, written from the pipe data.
The shared core runs as Theseus's own indexer only while Ariadne is
unhealthy. That means: no pipe 4 s after launch, a pipe that fails the
check, a pipe that went silent, or an index not confirmed for 10 min while
Ariadne is not paused. The own indexer warm-starts from Ariadne's
snapshot, so there is no download and no cold sync. It hands back after
90 s of health, so a flapping service does not start and stop it. Economy
is not a failure and never triggers a takeover. With no checkable Ariadne
(portable, not installed, older than the pipe) the own indexer starts at
once, as in 0.3.70. The one thing Theseus does on Ariadne's side is run
the indexer's task at launch when "Launch at start" is off.
main.js passes the shared module paths (packaged as .mjs, which is why the
shared modules no longer import each other) and keeps the host's status.
The Ariadne panel takes its state from the indexer task when one exists,
and the sub-page says where Theseus's names come from.
2026-10-03 17:06:24 +02:00
|
|
|
if (!st.index.size && publicSnapshotUrls.length) setTimeout(() => { if (!st.pipe) refreshPublic(); }, opts.connectPipe ? 3_000 : 0);
|
BNS indexer core, source chain and pipe protocol as shared modules
Design step 1 (TheseusNavigator/DESIGN-bns-indexer-service.md). Theseus's
bns-indexer.js and Ariadne's daemon each had their own index logic, and
Ariadne's warm-started from the operator's pre-resolved list, which has no
owners. Ariadne's Thread is to own indexing for the whole machine, with
Theseus reading from it, so both need the same code and the same results.
- bns-index-core.js: the 0.3.70 indexer logic without the Electron port:
local copies -> published raw snapshot (dl.silentmode.st, Sia mirror)
-> electrum delta every 30 s, index rebuilt from the beacon txs with
owner, TLDs from tlds.bch. Writes two local copies atomically (current
+ previous), emits change/fresh with the names that changed, and can
pause its network work (Economy) without stopping.
- bns-source-chain.js: how readers get names without depending on the
indexer: memory -> live indexer over the pipe -> local copies -> public
snapshots -> public BNS indexer for single names, marked provisional
and replaced when verified data arrives.
- bns-pipe.js: newline-JSON hello/snapshot/get/subscribe/poll over
\.\pipe\ariadne-bns[-<SID>]. Node cannot reach the pipe handle, so the
client trusts a connection only through a helper that checks the server
process (GetNamedPipeServerProcessId) and then relays that same
connection. A pipe name can be squatted while the indexer is down;
a 127.0.0.1 port cannot be checked at all.
Theseus keeps its own bns-indexer.js unchanged until migration step 3.
2026-10-03 16:01:26 +02:00
|
|
|
tick.unref?.(); beat.unref?.();
|
|
|
|
|
}
|
|
|
|
|
function stop() {
|
|
|
|
|
stopped = true;
|
|
|
|
|
clearInterval(tick); clearInterval(beat); clearTimeout(reconnectTimer);
|
|
|
|
|
try { st.pipe?.close(); } catch {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// defineProperties, not assign: assign would freeze the getters.
|
|
|
|
|
return Object.defineProperties(ev, Object.getOwnPropertyDescriptors({
|
Theseus: read BNS names from Ariadne's indexer; its own is the standby
Migration step 3 (DESIGN-bns-indexer-service.md). Ariadne's Thread now owns
BNS indexing on the machine, so Theseus no longer runs a second electrum
indexer beside it.
bns-indexer.js keeps its process and its messages to main.js, but inside
it is now an index host on the shared source chain:
- Ariadne's indexer over its pipe, trusted only after ariadne-helper.exe
has checked the server process on that connection (found through
Ariadne's uninstall key), then pushes;
- the local copies: Ariadne's files for both scopes, Theseus's own raw
copy, the bundled one. The richest wins.
- Theseus's own index copy, written from the pipe data.
The shared core runs as Theseus's own indexer only while Ariadne is
unhealthy. That means: no pipe 4 s after launch, a pipe that fails the
check, a pipe that went silent, or an index not confirmed for 10 min while
Ariadne is not paused. The own indexer warm-starts from Ariadne's
snapshot, so there is no download and no cold sync. It hands back after
90 s of health, so a flapping service does not start and stop it. Economy
is not a failure and never triggers a takeover. With no checkable Ariadne
(portable, not installed, older than the pipe) the own indexer starts at
once, as in 0.3.70. The one thing Theseus does on Ariadne's side is run
the indexer's task at launch when "Launch at start" is off.
main.js passes the shared module paths (packaged as .mjs, which is why the
shared modules no longer import each other) and keeps the host's status.
The Ariadne panel takes its state from the indexer task when one exists,
and the sub-page says where Theseus's names come from.
2026-10-03 17:06:24 +02:00
|
|
|
start, stop, lookup, resolve, refreshPublic, loadLocal, requestPoll,
|
|
|
|
|
get builtAt() { return st.builtAt; },
|
|
|
|
|
get paused() { return st.paused; },
|
|
|
|
|
get pipeConnected() { return !!st.pipe; },
|
|
|
|
|
get source() { return st.source; },
|
BNS indexer core, source chain and pipe protocol as shared modules
Design step 1 (TheseusNavigator/DESIGN-bns-indexer-service.md). Theseus's
bns-indexer.js and Ariadne's daemon each had their own index logic, and
Ariadne's warm-started from the operator's pre-resolved list, which has no
owners. Ariadne's Thread is to own indexing for the whole machine, with
Theseus reading from it, so both need the same code and the same results.
- bns-index-core.js: the 0.3.70 indexer logic without the Electron port:
local copies -> published raw snapshot (dl.silentmode.st, Sia mirror)
-> electrum delta every 30 s, index rebuilt from the beacon txs with
owner, TLDs from tlds.bch. Writes two local copies atomically (current
+ previous), emits change/fresh with the names that changed, and can
pause its network work (Economy) without stopping.
- bns-source-chain.js: how readers get names without depending on the
indexer: memory -> live indexer over the pipe -> local copies -> public
snapshots -> public BNS indexer for single names, marked provisional
and replaced when verified data arrives.
- bns-pipe.js: newline-JSON hello/snapshot/get/subscribe/poll over
\.\pipe\ariadne-bns[-<SID>]. Node cannot reach the pipe handle, so the
client trusts a connection only through a helper that checks the server
process (GetNamedPipeServerProcessId) and then relays that same
connection. A pipe name can be squatted while the indexer is down;
a 127.0.0.1 port cannot be checked at all.
Theseus keeps its own bns-indexer.js unchanged until migration step 3.
2026-10-03 16:01:26 +02:00
|
|
|
get index() { return st.index; },
|
|
|
|
|
get tlds() { return st.tlds; },
|
|
|
|
|
status() {
|
|
|
|
|
return {
|
Theseus: read BNS names from Ariadne's indexer; its own is the standby
Migration step 3 (DESIGN-bns-indexer-service.md). Ariadne's Thread now owns
BNS indexing on the machine, so Theseus no longer runs a second electrum
indexer beside it.
bns-indexer.js keeps its process and its messages to main.js, but inside
it is now an index host on the shared source chain:
- Ariadne's indexer over its pipe, trusted only after ariadne-helper.exe
has checked the server process on that connection (found through
Ariadne's uninstall key), then pushes;
- the local copies: Ariadne's files for both scopes, Theseus's own raw
copy, the bundled one. The richest wins.
- Theseus's own index copy, written from the pipe data.
The shared core runs as Theseus's own indexer only while Ariadne is
unhealthy. That means: no pipe 4 s after launch, a pipe that fails the
check, a pipe that went silent, or an index not confirmed for 10 min while
Ariadne is not paused. The own indexer warm-starts from Ariadne's
snapshot, so there is no download and no cold sync. It hands back after
90 s of health, so a flapping service does not start and stop it. Economy
is not a failure and never triggers a takeover. With no checkable Ariadne
(portable, not installed, older than the pipe) the own indexer starts at
once, as in 0.3.70. The one thing Theseus does on Ariadne's side is run
the indexer's task at launch when "Launch at start" is off.
main.js passes the shared module paths (packaged as .mjs, which is why the
shared modules no longer import each other) and keeps the host's status.
The Ariadne panel takes its state from the indexer task when one exists,
and the sub-page says where Theseus's names come from.
2026-10-03 17:06:24 +02:00
|
|
|
source: st.source, size: st.index.size, builtAt: st.builtAt, gen: st.gen, stale: stale(), paused: st.paused,
|
BNS indexer core, source chain and pipe protocol as shared modules
Design step 1 (TheseusNavigator/DESIGN-bns-indexer-service.md). Theseus's
bns-indexer.js and Ariadne's daemon each had their own index logic, and
Ariadne's warm-started from the operator's pre-resolved list, which has no
owners. Ariadne's Thread is to own indexing for the whole machine, with
Theseus reading from it, so both need the same code and the same results.
- bns-index-core.js: the 0.3.70 indexer logic without the Electron port:
local copies -> published raw snapshot (dl.silentmode.st, Sia mirror)
-> electrum delta every 30 s, index rebuilt from the beacon txs with
owner, TLDs from tlds.bch. Writes two local copies atomically (current
+ previous), emits change/fresh with the names that changed, and can
pause its network work (Economy) without stopping.
- bns-source-chain.js: how readers get names without depending on the
indexer: memory -> live indexer over the pipe -> local copies -> public
snapshots -> public BNS indexer for single names, marked provisional
and replaced when verified data arrives.
- bns-pipe.js: newline-JSON hello/snapshot/get/subscribe/poll over
\.\pipe\ariadne-bns[-<SID>]. Node cannot reach the pipe handle, so the
client trusts a connection only through a helper that checks the server
process (GetNamedPipeServerProcessId) and then relays that same
connection. A pipe name can be squatted while the indexer is down;
a 127.0.0.1 port cannot be checked at all.
Theseus keeps its own bns-indexer.js unchanged until migration step 3.
2026-10-03 16:01:26 +02:00
|
|
|
pipe: { connected: !!st.pipe, since: st.pipe ? st.pipeSince : null, error: st.pipeError, verdict: st.pipe?.verdict || null },
|
|
|
|
|
localPaths: opts.localPaths || [], ownCopyPath: opts.ownCopyPath || null,
|
|
|
|
|
provisional: [...provisional.values()].filter((p) => p.entry).length,
|
|
|
|
|
lastPublic: st.lastPublic,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
}));
|
|
|
|
|
}
|