@aria-cli/wireguard 1.0.36 → 1.0.38

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/dist/nat.js DELETED
@@ -1,397 +0,0 @@
1
- "use strict";
2
- /**
3
- * STUN client + NAT traversal for ARIA secure networking.
4
- *
5
- * Discovers external IP:port via STUN (RFC 5389), enabling WireGuard
6
- * peers to find each other through NAT. Falls back to a UDP relay
7
- * when symmetric NAT prevents direct connectivity.
8
- *
9
- * No external dependencies — implements STUN binding request/response
10
- * directly using node:dgram.
11
- */
12
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
- if (k2 === undefined) k2 = k;
14
- var desc = Object.getOwnPropertyDescriptor(m, k);
15
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
- desc = { enumerable: true, get: function() { return m[k]; } };
17
- }
18
- Object.defineProperty(o, k2, desc);
19
- }) : (function(o, m, k, k2) {
20
- if (k2 === undefined) k2 = k;
21
- o[k2] = m[k];
22
- }));
23
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
- Object.defineProperty(o, "default", { enumerable: true, value: v });
25
- }) : function(o, v) {
26
- o["default"] = v;
27
- });
28
- var __importStar = (this && this.__importStar) || (function () {
29
- var ownKeys = function(o) {
30
- ownKeys = Object.getOwnPropertyNames || function (o) {
31
- var ar = [];
32
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
- return ar;
34
- };
35
- return ownKeys(o);
36
- };
37
- return function (mod) {
38
- if (mod && mod.__esModule) return mod;
39
- var result = {};
40
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
- __setModuleDefault(result, mod);
42
- return result;
43
- };
44
- })();
45
- Object.defineProperty(exports, "__esModule", { value: true });
46
- exports.StunClient = void 0;
47
- exports.discoverEndpoint = discoverEndpoint;
48
- exports.detectNatType = detectNatType;
49
- const dgram = __importStar(require("node:dgram"));
50
- const crypto = __importStar(require("node:crypto"));
51
- const os = __importStar(require("node:os"));
52
- // STUN message types (RFC 5389)
53
- const STUN_BINDING_REQUEST = 0x0001;
54
- const STUN_BINDING_RESPONSE = 0x0101;
55
- const STUN_MAGIC_COOKIE = 0x2112a442;
56
- const STUN_ATTR_XOR_MAPPED_ADDRESS = 0x0020;
57
- const STUN_ATTR_MAPPED_ADDRESS = 0x0001;
58
- /**
59
- * Detect whether the route to a STUN server exits through a tunnel/VPN
60
- * interface. If so, STUN results reflect the tunnel exit IP — not the
61
- * machine's real public IP — and should be discarded.
62
- *
63
- * Detection: uses a connected UDP socket to discover the OS-selected source
64
- * address, then checks if that address belongs to an interface with a
65
- * zero MAC address (00:00:00:00:00:00). Tunnel/VPN interfaces (WireGuard,
66
- * OpenVPN, Tailscale, IPSec, ZeroTier, etc.) have no hardware address.
67
- * Physical interfaces (Ethernet, WiFi) always have a real MAC.
68
- */
69
- function routeExitsThroughTunnel(host, port) {
70
- return new Promise((resolve) => {
71
- const probe = dgram.createSocket("udp4");
72
- const timer = setTimeout(() => {
73
- probe.close();
74
- resolve(false);
75
- }, 2000);
76
- probe.connect(port, host, () => {
77
- clearTimeout(timer);
78
- try {
79
- const sourceAddr = probe.address().address;
80
- const ifaces = os.networkInterfaces();
81
- for (const addrs of Object.values(ifaces)) {
82
- for (const a of addrs ?? []) {
83
- if (a.address === sourceAddr && a.mac === "00:00:00:00:00:00") {
84
- probe.close();
85
- resolve(true);
86
- return;
87
- }
88
- }
89
- }
90
- }
91
- catch {
92
- // ignore
93
- }
94
- probe.close();
95
- resolve(false);
96
- });
97
- probe.on("error", () => {
98
- clearTimeout(timer);
99
- probe.close();
100
- resolve(false);
101
- });
102
- });
103
- }
104
- // Well-known public STUN servers (no authentication required)
105
- const DEFAULT_STUN_SERVERS = [
106
- "stun.l.google.com:19302",
107
- "stun1.l.google.com:19302",
108
- "stun.cloudflare.com:3478",
109
- ];
110
- /**
111
- * Build a STUN Binding Request message (RFC 5389).
112
- * Header: type (2) + length (2) + magic cookie (4) + transaction ID (12) = 20 bytes.
113
- */
114
- function buildBindingRequest() {
115
- const msg = Buffer.alloc(20);
116
- msg.writeUInt16BE(STUN_BINDING_REQUEST, 0); // Message type
117
- msg.writeUInt16BE(0, 2); // Message length (no attributes)
118
- msg.writeUInt32BE(STUN_MAGIC_COOKIE, 4); // Magic cookie
119
- const txId = crypto.randomBytes(12);
120
- txId.copy(msg, 8); // Transaction ID
121
- return { message: msg, transactionId: txId };
122
- }
123
- /**
124
- * Parse a STUN Binding Response to extract the mapped address.
125
- * Handles both XOR-MAPPED-ADDRESS (preferred) and MAPPED-ADDRESS (fallback).
126
- */
127
- function parseBindingResponse(msg, expectedTxId) {
128
- if (msg.length < 20)
129
- return null;
130
- const msgType = msg.readUInt16BE(0);
131
- if (msgType !== STUN_BINDING_RESPONSE)
132
- return null;
133
- // Verify magic cookie
134
- if (msg.readUInt32BE(4) !== STUN_MAGIC_COOKIE)
135
- return null;
136
- // Verify transaction ID
137
- if (!msg.subarray(8, 20).equals(expectedTxId))
138
- return null;
139
- const attrLength = msg.readUInt16BE(2);
140
- let offset = 20;
141
- const end = 20 + attrLength;
142
- let mappedAddress = null;
143
- while (offset + 4 <= end) {
144
- const attrType = msg.readUInt16BE(offset);
145
- const attrLen = msg.readUInt16BE(offset + 2);
146
- const attrStart = offset + 4;
147
- if (attrType === STUN_ATTR_XOR_MAPPED_ADDRESS && attrLen >= 8) {
148
- const family = msg.readUInt8(attrStart + 1);
149
- if (family === 0x01) {
150
- // IPv4
151
- const xPort = msg.readUInt16BE(attrStart + 2) ^ (STUN_MAGIC_COOKIE >> 16);
152
- const xAddr = msg.readUInt32BE(attrStart + 4) ^ STUN_MAGIC_COOKIE;
153
- const a = (xAddr >> 24) & 0xff;
154
- const b = (xAddr >> 16) & 0xff;
155
- const c = (xAddr >> 8) & 0xff;
156
- const d = xAddr & 0xff;
157
- return { address: `${a}.${b}.${c}.${d}`, port: xPort };
158
- }
159
- }
160
- if (attrType === STUN_ATTR_MAPPED_ADDRESS && attrLen >= 8) {
161
- const family = msg.readUInt8(attrStart + 1);
162
- if (family === 0x01) {
163
- // IPv4 (not XORed)
164
- const port = msg.readUInt16BE(attrStart + 2);
165
- const addr = msg.readUInt32BE(attrStart + 4);
166
- const a = (addr >> 24) & 0xff;
167
- const b = (addr >> 16) & 0xff;
168
- const c = (addr >> 8) & 0xff;
169
- const d = addr & 0xff;
170
- mappedAddress = { address: `${a}.${b}.${c}.${d}`, port };
171
- }
172
- }
173
- // Attribute value is padded to 4-byte boundary
174
- offset = attrStart + Math.ceil(attrLen / 4) * 4;
175
- }
176
- return mappedAddress;
177
- }
178
- /**
179
- * Discover our external endpoint by sending a STUN Binding Request.
180
- *
181
- * Tries multiple STUN servers in parallel, returns the first successful response.
182
- * Timeout: 3 seconds per server, 5 second total.
183
- */
184
- /**
185
- * Discover external endpoint via STUN.
186
- *
187
- * @param server STUN server hostname:port (or undefined to try defaults)
188
- * @param timeoutMs Timeout in milliseconds
189
- * @param existingSocket Optional: use this socket for STUN instead of creating
190
- * ephemeral ones. This is critical for the shared-socket WG model — STUN must
191
- * discover the NAT mapping of the ACTUAL listening socket, not a throwaway one.
192
- * When provided, the socket is NOT closed after discovery.
193
- */
194
- async function discoverEndpoint(server, timeoutMs = 5000, existingSocket) {
195
- const servers = server ? [server] : DEFAULT_STUN_SERVERS;
196
- // Pre-check: if the route to the STUN server exits through a tunnel
197
- // interface (VPN), the result will be the VPN exit IP — not our real
198
- // public IP. Reject early to prevent advertising a wrong endpoint.
199
- const [firstHost, firstPortStr] = servers[0].split(":");
200
- const isTunnel = await routeExitsThroughTunnel(firstHost, parseInt(firstPortStr ?? "3478", 10));
201
- if (isTunnel) {
202
- throw new Error("STUN route exits through a tunnel interface — result would be unreliable");
203
- }
204
- return new Promise((resolve, reject) => {
205
- let resolved = false;
206
- const ownedSockets = []; // sockets WE created (will be closed)
207
- const timer = setTimeout(() => {
208
- if (!resolved) {
209
- resolved = true;
210
- ownedSockets.forEach((s) => {
211
- try {
212
- s.close();
213
- }
214
- catch {
215
- // ignore
216
- }
217
- });
218
- reject(new Error(`STUN discovery timed out after ${timeoutMs}ms`));
219
- }
220
- }, timeoutMs);
221
- for (const srv of servers) {
222
- const [host, portStr] = srv.split(":");
223
- const port = parseInt(portStr ?? "3478", 10);
224
- // Use existing socket if provided (shared socket model), else create ephemeral
225
- const socket = existingSocket ?? dgram.createSocket("udp4");
226
- if (!existingSocket)
227
- ownedSockets.push(socket);
228
- const { message, transactionId } = buildBindingRequest();
229
- const handler = (msg) => {
230
- if (resolved)
231
- return;
232
- const result = parseBindingResponse(msg, transactionId);
233
- if (result) {
234
- resolved = true;
235
- clearTimeout(timer);
236
- // Remove our handler from the existing socket (don't close it)
237
- if (existingSocket) {
238
- existingSocket.off("message", handler);
239
- }
240
- ownedSockets.forEach((s) => {
241
- try {
242
- s.close();
243
- }
244
- catch {
245
- // ignore
246
- }
247
- });
248
- resolve({ ...result, server: srv });
249
- }
250
- };
251
- socket.on("message", handler);
252
- socket.on("error", () => {
253
- // Individual socket errors are non-fatal — other servers may succeed
254
- if (!existingSocket) {
255
- try {
256
- socket.close();
257
- }
258
- catch {
259
- /* already closed */
260
- }
261
- }
262
- });
263
- socket.send(message, port, host);
264
- }
265
- });
266
- }
267
- /**
268
- * Detect NAT type by querying two STUN servers and comparing mapped ports.
269
- *
270
- * Symmetric NAT allocates a new external port for each destination,
271
- * so mapped ports will differ across STUN servers. Cone NATs reuse
272
- * the same external port, so mapped ports will match.
273
- *
274
- * Returns "unknown" if fewer than 2 STUN servers respond.
275
- */
276
- async function detectNatType(servers = DEFAULT_STUN_SERVERS, timeoutMs = 5000) {
277
- const results = [];
278
- // Query each server individually (sequential to avoid port reuse confusion)
279
- for (const server of servers.slice(0, 3)) {
280
- try {
281
- const result = await discoverEndpoint(server, timeoutMs);
282
- results.push(result);
283
- if (results.length >= 2)
284
- break; // Only need 2 for comparison
285
- }
286
- catch {
287
- // Server unreachable — try next
288
- }
289
- }
290
- if (results.length < 2) {
291
- return { natType: "unknown", results };
292
- }
293
- // Compare mapped ports: if different → symmetric NAT
294
- const port1 = results[0].port;
295
- const port2 = results[1].port;
296
- if (port1 !== port2) {
297
- return { natType: "symmetric", results };
298
- }
299
- // Same port → cone or restricted (both allow direct connections)
300
- // We can't distinguish full cone from restricted cone with STUN alone
301
- // (would need a STUN server that sends from a different IP), but both work for us.
302
- return { natType: "full_cone", results };
303
- }
304
- /**
305
- * STUN client for periodic endpoint discovery.
306
- *
307
- * Use for ongoing NAT traversal — discovers and tracks endpoint changes.
308
- */
309
- class StunClient {
310
- servers;
311
- pollIntervalMs;
312
- interval = null;
313
- lastResult = null;
314
- _natType = null;
315
- /** Number of consecutive discovery failures (reset to 0 on success) */
316
- consecutiveFailures = 0;
317
- constructor(servers = DEFAULT_STUN_SERVERS, pollIntervalMs = 60_000) {
318
- this.servers = servers;
319
- this.pollIntervalMs = pollIntervalMs;
320
- }
321
- /** Get the detected NAT type (null if not yet detected) */
322
- getNatType() {
323
- return this._natType;
324
- }
325
- /** Get the last discovered endpoint */
326
- getEndpoint() {
327
- return this.lastResult;
328
- }
329
- /** Discover endpoint once — uses first configured server (or defaults) */
330
- async discover() {
331
- const result = await discoverEndpoint(this.servers[0], 5000);
332
- this.lastResult = result;
333
- return result;
334
- }
335
- /** Start periodic endpoint discovery. Detects NAT type on first call. */
336
- start(onUpdate) {
337
- if (this.interval)
338
- return;
339
- // Check once if we're behind a VPN. If so, STUN will always return
340
- // the wrong IP — skip the entire poll loop instead of spamming errors.
341
- void (async () => {
342
- const [firstHost, firstPortStr] = this.servers[0].split(":");
343
- const isTunnel = await routeExitsThroughTunnel(firstHost, parseInt(firstPortStr ?? "3478", 10));
344
- if (isTunnel) {
345
- if (typeof process !== "undefined" && process.stderr) {
346
- process.stderr.write("[wireguard] STUN skipped — route exits through VPN/tunnel interface\n");
347
- }
348
- return; // Don't start the poll loop
349
- }
350
- // Initial discovery + NAT type detection
351
- try {
352
- if (!this._natType) {
353
- const detection = await detectNatType(this.servers);
354
- this._natType = detection.natType;
355
- if (detection.results.length > 0) {
356
- this.lastResult = detection.results[0];
357
- this.consecutiveFailures = 0;
358
- onUpdate?.(detection.results[0]);
359
- }
360
- }
361
- if (!this.lastResult) {
362
- const r = await this.discover();
363
- this.consecutiveFailures = 0;
364
- onUpdate?.(r);
365
- }
366
- }
367
- catch {
368
- this.consecutiveFailures++;
369
- }
370
- // Start periodic refresh only after confirming STUN works
371
- this.interval = setInterval(async () => {
372
- try {
373
- const result = await this.discover();
374
- this.consecutiveFailures = 0;
375
- onUpdate?.(result);
376
- }
377
- catch {
378
- this.consecutiveFailures++;
379
- if (this.consecutiveFailures >= 3) {
380
- if (typeof process !== "undefined" && process.stderr) {
381
- process.stderr.write(`[wireguard] STUN discovery failed ${this.consecutiveFailures} consecutive times\n`);
382
- }
383
- }
384
- }
385
- }, this.pollIntervalMs);
386
- })();
387
- }
388
- /** Stop periodic discovery */
389
- stop() {
390
- if (this.interval) {
391
- clearInterval(this.interval);
392
- this.interval = null;
393
- }
394
- }
395
- }
396
- exports.StunClient = StunClient;
397
- //# sourceMappingURL=nat.js.map
@@ -1,248 +0,0 @@
1
- "use strict";
2
- /**
3
- * NetworkStateStore — canonical, machine-scoped persistence for network control-plane state.
4
- *
5
- * All network-adjacent durable state (peers, signing keys, revocations, nonces, trust)
6
- * lives in ONE canonical store at ARIA_HOME/network/state.db, independent of any
7
- * arion-specific Memoria DB. This eliminates the split-brain where peer state could
8
- * drift across multiple DB paths.
9
- *
10
- * PeerRegistry, RevocationStore, and other network stores all use the Database
11
- * returned by getDatabase().
12
- */
13
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
- if (k2 === undefined) k2 = k;
15
- var desc = Object.getOwnPropertyDescriptor(m, k);
16
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
- desc = { enumerable: true, get: function() { return m[k]; } };
18
- }
19
- Object.defineProperty(o, k2, desc);
20
- }) : (function(o, m, k, k2) {
21
- if (k2 === undefined) k2 = k;
22
- o[k2] = m[k];
23
- }));
24
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
- Object.defineProperty(o, "default", { enumerable: true, value: v });
26
- }) : function(o, v) {
27
- o["default"] = v;
28
- });
29
- var __importStar = (this && this.__importStar) || (function () {
30
- var ownKeys = function(o) {
31
- ownKeys = Object.getOwnPropertyNames || function (o) {
32
- var ar = [];
33
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
- return ar;
35
- };
36
- return ownKeys(o);
37
- };
38
- return function (mod) {
39
- if (mod && mod.__esModule) return mod;
40
- var result = {};
41
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
- __setModuleDefault(result, mod);
43
- return result;
44
- };
45
- })();
46
- Object.defineProperty(exports, "__esModule", { value: true });
47
- exports.NetworkStateStore = void 0;
48
- exports.canonicalNetworkStatePath = canonicalNetworkStatePath;
49
- const fs = __importStar(require("node:fs"));
50
- const path = __importStar(require("node:path"));
51
- const network_runtime_1 = require("@aria-cli/tools/network-runtime");
52
- /**
53
- * Canonical schema for the network state DB.
54
- *
55
- * Only creates tables that PeerRegistry depends on (it does NO schema creation).
56
- * Other stores (RevocationStore, PeerTrustStore, PeerSigningKeyStore,
57
- * InviteConsumeLedger, DurableReplayGuard, etc.) create their own tables
58
- * via ensureTable() in their constructors — they're self-managing.
59
- */
60
- const NETWORK_PEERS_TABLE_SQL = `
61
- CREATE TABLE IF NOT EXISTS network_peers (
62
- node_id TEXT PRIMARY KEY,
63
- public_key TEXT NOT NULL,
64
- name TEXT NOT NULL,
65
- endpoint_host TEXT,
66
- endpoint_port INTEGER,
67
- endpoint_revision INTEGER NOT NULL DEFAULT 0,
68
- control_endpoint_host TEXT,
69
- control_endpoint_port INTEGER,
70
- control_tls_ca_fingerprint TEXT,
71
- preshared_key TEXT,
72
- allowed_ips TEXT DEFAULT '0.0.0.0/0',
73
- invite_token TEXT,
74
- status TEXT DEFAULT 'active',
75
- last_handshake INTEGER,
76
- created_at INTEGER,
77
- updated_at INTEGER,
78
- signing_public_key TEXT
79
- );`;
80
- const NETWORK_PEERS_INDEX_SQL = `
81
- CREATE INDEX IF NOT EXISTS idx_network_peers_name ON network_peers(name);
82
- CREATE INDEX IF NOT EXISTS idx_network_peers_status ON network_peers(status);
83
- CREATE UNIQUE INDEX IF NOT EXISTS idx_network_peers_public_key
84
- ON network_peers(public_key);
85
- `;
86
- const SCHEMA_SQL = `
87
- -- Peer registry canonical hard-cut schema
88
- ${NETWORK_PEERS_TABLE_SQL}
89
-
90
- -- Token replay protection (used by PeerRegistry.claimToken/releaseToken)
91
- CREATE TABLE IF NOT EXISTS token_claims (
92
- nonce TEXT PRIMARY KEY,
93
- claimed_at INTEGER NOT NULL
94
- );
95
-
96
- -- Pending tunnel/pair requests (used by PeerRegistry.listByStatus)
97
- CREATE TABLE IF NOT EXISTS pending_tunnel (
98
- id TEXT PRIMARY KEY,
99
- peer_name TEXT NOT NULL,
100
- peer_public_key TEXT NOT NULL,
101
- status TEXT DEFAULT 'pending',
102
- created_at INTEGER NOT NULL,
103
- updated_at INTEGER
104
- );
105
-
106
- -- Schema version tracking
107
- CREATE TABLE IF NOT EXISTS network_schema_version (
108
- version INTEGER NOT NULL
109
- );
110
- `;
111
- const CURRENT_SCHEMA_VERSION = 6;
112
- class NetworkStateStore {
113
- options;
114
- db = null;
115
- dbPath;
116
- constructor(options) {
117
- this.options = options;
118
- this.dbPath = options.dbPath ?? path.join(options.ariaHome, "network", "state.db");
119
- }
120
- /** Canonical path to the network state DB */
121
- get path() {
122
- return this.dbPath;
123
- }
124
- /** Whether the store has been opened */
125
- get isOpen() {
126
- return this.db !== null;
127
- }
128
- /** Open the database, creating schema if needed */
129
- open() {
130
- if (this.db)
131
- return this.db;
132
- // Ensure directory exists
133
- const dir = path.dirname(this.dbPath);
134
- fs.mkdirSync(dir, { recursive: true });
135
- // Dynamic import better-sqlite3 (same pattern as PeerRegistry)
136
- // eslint-disable-next-line @typescript-eslint/no-require-imports
137
- const BetterSqlite3 = require("better-sqlite3");
138
- this.db = new BetterSqlite3(this.dbPath);
139
- try {
140
- this.db.pragma("journal_mode = WAL");
141
- this.db.pragma("busy_timeout = 5000");
142
- // Create schema
143
- this.db.exec(SCHEMA_SQL);
144
- this.reconcilePeerTableSchema(this.db);
145
- this.db.exec(NETWORK_PEERS_INDEX_SQL);
146
- // Track schema version
147
- const versionRow = this.db
148
- .prepare("SELECT version FROM network_schema_version LIMIT 1")
149
- .get();
150
- if (!versionRow) {
151
- this.db
152
- .prepare("INSERT INTO network_schema_version (version) VALUES (?)")
153
- .run(CURRENT_SCHEMA_VERSION);
154
- }
155
- else if (versionRow.version < CURRENT_SCHEMA_VERSION) {
156
- this.db
157
- .prepare("UPDATE network_schema_version SET version = ?")
158
- .run(CURRENT_SCHEMA_VERSION);
159
- }
160
- }
161
- catch (error) {
162
- this.db.close();
163
- this.db = null;
164
- throw error;
165
- }
166
- return this.db;
167
- }
168
- reconcilePeerTableSchema(db) {
169
- const tableRow = db
170
- .prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='network_peers'")
171
- .get();
172
- if (!tableRow?.sql)
173
- return;
174
- const tableSql = tableRow.sql.toLowerCase();
175
- const hasLegacyStatusCheck = tableSql.includes("check") && !tableSql.includes("pending_verification");
176
- const columns = db.prepare("PRAGMA table_info(network_peers)").all();
177
- const columnNames = new Set(columns.map((column) => column.name));
178
- const primaryKey = columns.find((column) => column.pk === 1)?.name;
179
- const requiredColumns = [
180
- "public_key",
181
- "node_id",
182
- "name",
183
- "endpoint_host",
184
- "endpoint_port",
185
- "endpoint_revision",
186
- "control_endpoint_host",
187
- "control_endpoint_port",
188
- "control_tls_ca_fingerprint",
189
- "preshared_key",
190
- "allowed_ips",
191
- "invite_token",
192
- "status",
193
- "last_handshake",
194
- "created_at",
195
- "updated_at",
196
- "signing_public_key",
197
- ];
198
- const missingColumns = requiredColumns.filter((column) => !columnNames.has(column));
199
- const legacySchemaProblems = [];
200
- if (hasLegacyStatusCheck) {
201
- legacySchemaProblems.push("legacy status check");
202
- }
203
- if (columnNames.has("message_port")) {
204
- legacySchemaProblems.push("message_port column");
205
- }
206
- if (columnNames.has("peer_id")) {
207
- legacySchemaProblems.push("legacy peer_id column");
208
- }
209
- if (primaryKey !== "node_id") {
210
- legacySchemaProblems.push(`primary key is ${primaryKey ?? "missing"}`);
211
- }
212
- if (missingColumns.length > 0) {
213
- legacySchemaProblems.push(`missing columns: ${missingColumns.join(", ")}`);
214
- }
215
- if (legacySchemaProblems.length === 0) {
216
- return;
217
- }
218
- throw new Error(`Legacy network_peers schema requires hard reset: ${legacySchemaProblems.join(", ")}`);
219
- }
220
- /** Get the underlying Database handle (opens if needed) */
221
- getDatabase() {
222
- return this.open();
223
- }
224
- /**
225
- * Claim the owner epoch for this runtime on the shared network state DB.
226
- * Must be called after open() and before any durable writes.
227
- * Throws StaleOwnerError if a newer generation already owns the DB.
228
- */
229
- claimOwnerEpoch(generation) {
230
- const db = this.open();
231
- (0, network_runtime_1.claimDbOwnerEpoch)(db, generation);
232
- }
233
- /** Close the database */
234
- close() {
235
- if (this.db) {
236
- this.db.close();
237
- this.db = null;
238
- }
239
- }
240
- }
241
- exports.NetworkStateStore = NetworkStateStore;
242
- /**
243
- * Resolve the canonical network state DB path for a given ARIA home.
244
- */
245
- function canonicalNetworkStatePath(ariaHome) {
246
- return path.join(ariaHome, "network", "state.db");
247
- }
248
- //# sourceMappingURL=network-state-store.js.map