@aria-cli/wireguard 1.0.38 → 1.0.40
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/bootstrap-authority.d.ts +2 -0
- package/dist/bootstrap-authority.js +47 -0
- package/dist/bootstrap-tls.d.ts +14 -0
- package/dist/bootstrap-tls.js +69 -0
- package/dist/db-owner-fencing.d.ts +10 -0
- package/dist/db-owner-fencing.js +44 -0
- package/dist/derp-relay.d.ts +75 -0
- package/dist/derp-relay.js +311 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +100 -0
- package/dist/nat.d.ts +84 -0
- package/dist/nat.js +397 -0
- package/dist/network-state-store.d.ts +46 -0
- package/dist/network-state-store.js +248 -0
- package/dist/network.d.ts +590 -0
- package/dist/network.js +3391 -0
- package/dist/peer-discovery.d.ts +133 -0
- package/dist/peer-discovery.js +486 -0
- package/dist/resilient-tunnel.d.ts +70 -0
- package/dist/resilient-tunnel.js +389 -0
- package/dist/route-ownership.d.ts +23 -0
- package/dist/route-ownership.js +79 -0
- package/dist/tunnel.d.ts +141 -0
- package/dist/tunnel.js +474 -0
- package/index.js +52 -52
- package/npm/darwin-arm64/package.json +1 -1
- package/npm/darwin-x64/package.json +1 -1
- package/npm/linux-arm64-gnu/package.json +1 -1
- package/npm/linux-x64-gnu/package.json +1 -1
- package/npm/win32-x64-msvc/package.json +1 -1
- package/package.json +13 -20
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.loadStoredBootstrapCaCert = loadStoredBootstrapCaCert;
|
|
37
|
+
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const path = __importStar(require("node:path"));
|
|
39
|
+
function loadStoredBootstrapCaCert(ariaDir) {
|
|
40
|
+
const caPath = path.join(ariaDir, "network", "tls", "ca.pem");
|
|
41
|
+
if (!fs.existsSync(caPath)) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
const caCert = fs.readFileSync(caPath, "utf8").trim();
|
|
45
|
+
return caCert.length > 0 ? caCert : undefined;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=bootstrap-authority.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface BootstrapTlsRequestOptions {
|
|
2
|
+
method?: string;
|
|
3
|
+
body?: string;
|
|
4
|
+
headers?: Record<string, string>;
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
caCert: string;
|
|
7
|
+
expectedTlsIdentity: string;
|
|
8
|
+
}
|
|
9
|
+
export interface BootstrapTlsResponse {
|
|
10
|
+
status: number;
|
|
11
|
+
body: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function bootstrapTlsRequest(url: string, options: BootstrapTlsRequestOptions): Promise<BootstrapTlsResponse>;
|
|
14
|
+
//# sourceMappingURL=bootstrap-tls.d.ts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.bootstrapTlsRequest = bootstrapTlsRequest;
|
|
37
|
+
const https = __importStar(require("node:https"));
|
|
38
|
+
const tls = __importStar(require("node:tls"));
|
|
39
|
+
function bootstrapTlsRequest(url, options) {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
const parsed = new URL(url);
|
|
42
|
+
const req = https.request({
|
|
43
|
+
hostname: parsed.hostname,
|
|
44
|
+
port: parsed.port,
|
|
45
|
+
path: parsed.pathname + parsed.search,
|
|
46
|
+
method: options.method ?? "GET",
|
|
47
|
+
headers: options.headers,
|
|
48
|
+
ca: options.caCert,
|
|
49
|
+
rejectUnauthorized: true,
|
|
50
|
+
checkServerIdentity: (_hostname, cert) => tls.checkServerIdentity(options.expectedTlsIdentity, cert),
|
|
51
|
+
timeout: options.timeoutMs ?? 5_000,
|
|
52
|
+
}, (res) => {
|
|
53
|
+
let data = "";
|
|
54
|
+
res.on("data", (chunk) => {
|
|
55
|
+
data += chunk.toString();
|
|
56
|
+
});
|
|
57
|
+
res.on("end", () => resolve({ status: res.statusCode ?? 0, body: data }));
|
|
58
|
+
});
|
|
59
|
+
req.on("error", reject);
|
|
60
|
+
req.on("timeout", () => {
|
|
61
|
+
req.destroy();
|
|
62
|
+
reject(new Error("Request timeout"));
|
|
63
|
+
});
|
|
64
|
+
if (options.body)
|
|
65
|
+
req.write(options.body);
|
|
66
|
+
req.end();
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=bootstrap-tls.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type Database from "better-sqlite3";
|
|
2
|
+
export declare class StaleOwnerError extends Error {
|
|
3
|
+
readonly kind: "StaleOwnerError";
|
|
4
|
+
readonly claimedGeneration: number;
|
|
5
|
+
readonly currentGeneration: number;
|
|
6
|
+
constructor(claimed: number, current: number);
|
|
7
|
+
}
|
|
8
|
+
export declare function ensureOwnerEpochTable(db: Database.Database): void;
|
|
9
|
+
export declare function claimDbOwnerEpoch(db: Database.Database, generation: number): void;
|
|
10
|
+
//# sourceMappingURL=db-owner-fencing.d.ts.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StaleOwnerError = void 0;
|
|
4
|
+
exports.ensureOwnerEpochTable = ensureOwnerEpochTable;
|
|
5
|
+
exports.claimDbOwnerEpoch = claimDbOwnerEpoch;
|
|
6
|
+
class StaleOwnerError extends Error {
|
|
7
|
+
kind = "StaleOwnerError";
|
|
8
|
+
claimedGeneration;
|
|
9
|
+
currentGeneration;
|
|
10
|
+
constructor(claimed, current) {
|
|
11
|
+
super(`StaleOwnerError: runtime claims generation ${claimed} but store is at generation ${current}. ` +
|
|
12
|
+
`This runtime has been superseded and must shut down immediately.`);
|
|
13
|
+
this.name = "StaleOwnerError";
|
|
14
|
+
this.claimedGeneration = claimed;
|
|
15
|
+
this.currentGeneration = current;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.StaleOwnerError = StaleOwnerError;
|
|
19
|
+
function ensureOwnerEpochTable(db) {
|
|
20
|
+
db.exec(`
|
|
21
|
+
CREATE TABLE IF NOT EXISTS owner_epoch (
|
|
22
|
+
scope TEXT PRIMARY KEY DEFAULT 'runtime',
|
|
23
|
+
owner_generation INTEGER NOT NULL
|
|
24
|
+
)
|
|
25
|
+
`);
|
|
26
|
+
}
|
|
27
|
+
function claimDbOwnerEpoch(db, generation) {
|
|
28
|
+
ensureOwnerEpochTable(db);
|
|
29
|
+
const result = db
|
|
30
|
+
.prepare(`INSERT INTO owner_epoch (scope, owner_generation) VALUES ('runtime', ?)
|
|
31
|
+
ON CONFLICT(scope) DO UPDATE SET owner_generation = excluded.owner_generation
|
|
32
|
+
WHERE excluded.owner_generation > owner_epoch.owner_generation`)
|
|
33
|
+
.run(generation);
|
|
34
|
+
if (result.changes === 0) {
|
|
35
|
+
const row = db
|
|
36
|
+
.prepare("SELECT owner_generation FROM owner_epoch WHERE scope = 'runtime'")
|
|
37
|
+
.get();
|
|
38
|
+
const current = row?.owner_generation ?? 0;
|
|
39
|
+
if (current > generation) {
|
|
40
|
+
throw new StaleOwnerError(generation, current);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=db-owner-fencing.js.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DerpRelay — DERP relay client for NAT traversal.
|
|
3
|
+
*
|
|
4
|
+
* When both peers are behind symmetric NAT, direct WireGuard tunnels fail.
|
|
5
|
+
* DerpRelay connects to a relay server via WebSocket and forwards encrypted
|
|
6
|
+
* WireGuard packets through it. The relay CANNOT read the content (WireGuard
|
|
7
|
+
* encryption is end-to-end).
|
|
8
|
+
*
|
|
9
|
+
* Implements the same { send(data: Buffer): void } interface as tunnel transports,
|
|
10
|
+
* so it plugs into Mailbox.registerTunnel() transparently.
|
|
11
|
+
*
|
|
12
|
+
* Authentication: Ed25519 signature challenge on connect to prevent impersonation.
|
|
13
|
+
* Auto-reconnect: Exponential backoff, matching ResilientTunnel's pattern.
|
|
14
|
+
*/
|
|
15
|
+
import { EventEmitter } from "node:events";
|
|
16
|
+
import { type NodeId } from "@aria-cli/tools";
|
|
17
|
+
export interface DerpRelayOptions {
|
|
18
|
+
/** Relay server URL (e.g., wss://relay.example.com/api/v1/relay) */
|
|
19
|
+
relayUrl: string;
|
|
20
|
+
/** Our durable node identity for relay auth */
|
|
21
|
+
nodeId: NodeId;
|
|
22
|
+
/** Optional display snapshot for local logs/debug output */
|
|
23
|
+
displayNameSnapshot?: string;
|
|
24
|
+
/** Ed25519 private key (base64 PKCS#8 DER) for authentication */
|
|
25
|
+
signingPrivateKey: string;
|
|
26
|
+
/** Ed25519 public key (base64 SPKI DER) for identification */
|
|
27
|
+
signingPublicKey: string;
|
|
28
|
+
/** Target node identity to communicate with */
|
|
29
|
+
targetNodeId: NodeId;
|
|
30
|
+
/** AbortSignal for clean shutdown */
|
|
31
|
+
signal?: AbortSignal;
|
|
32
|
+
}
|
|
33
|
+
export type DerpRelayState = "disconnected" | "connecting" | "authenticating" | "connected" | "dead";
|
|
34
|
+
/**
|
|
35
|
+
* DERP relay client.
|
|
36
|
+
*
|
|
37
|
+
* Emits: "plaintext" (data: Buffer, fromNodeId: NodeId, displayNameSnapshot?: string), "connected", "disconnected",
|
|
38
|
+
* "dead", "error" (Error), "stateChange" (newState, prevState)
|
|
39
|
+
*/
|
|
40
|
+
export declare class DerpRelay extends EventEmitter {
|
|
41
|
+
private ws;
|
|
42
|
+
private _state;
|
|
43
|
+
private reconnectAttempts;
|
|
44
|
+
private reconnectTimer;
|
|
45
|
+
private stopped;
|
|
46
|
+
private closing;
|
|
47
|
+
private readonly relayUrl;
|
|
48
|
+
private readonly nodeId;
|
|
49
|
+
private readonly displayNameSnapshot?;
|
|
50
|
+
private readonly signingPrivateKey;
|
|
51
|
+
private readonly signingPublicKey;
|
|
52
|
+
private readonly targetNodeId;
|
|
53
|
+
private readonly signal?;
|
|
54
|
+
/** Queued messages while not connected */
|
|
55
|
+
private queue;
|
|
56
|
+
private static readonly MAX_QUEUE;
|
|
57
|
+
constructor(options: DerpRelayOptions);
|
|
58
|
+
/** Connect to the relay server */
|
|
59
|
+
connect(): Promise<void>;
|
|
60
|
+
/** Send data through the relay to the target peer */
|
|
61
|
+
send(data: Buffer): void;
|
|
62
|
+
/** Disconnect from the relay server */
|
|
63
|
+
disconnect(): void;
|
|
64
|
+
/** Get current relay state */
|
|
65
|
+
getState(): DerpRelayState;
|
|
66
|
+
/** Whether the relay is actively connected */
|
|
67
|
+
get isConnected(): boolean;
|
|
68
|
+
private setState;
|
|
69
|
+
private signChallenge;
|
|
70
|
+
private attemptReconnect;
|
|
71
|
+
private clearReconnectTimer;
|
|
72
|
+
private enqueue;
|
|
73
|
+
private flushQueue;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=derp-relay.d.ts.map
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* DerpRelay — DERP relay client for NAT traversal.
|
|
4
|
+
*
|
|
5
|
+
* When both peers are behind symmetric NAT, direct WireGuard tunnels fail.
|
|
6
|
+
* DerpRelay connects to a relay server via WebSocket and forwards encrypted
|
|
7
|
+
* WireGuard packets through it. The relay CANNOT read the content (WireGuard
|
|
8
|
+
* encryption is end-to-end).
|
|
9
|
+
*
|
|
10
|
+
* Implements the same { send(data: Buffer): void } interface as tunnel transports,
|
|
11
|
+
* so it plugs into Mailbox.registerTunnel() transparently.
|
|
12
|
+
*
|
|
13
|
+
* Authentication: Ed25519 signature challenge on connect to prevent impersonation.
|
|
14
|
+
* Auto-reconnect: Exponential backoff, matching ResilientTunnel's pattern.
|
|
15
|
+
*/
|
|
16
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
19
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
20
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
21
|
+
}
|
|
22
|
+
Object.defineProperty(o, k2, desc);
|
|
23
|
+
}) : (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
o[k2] = m[k];
|
|
26
|
+
}));
|
|
27
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
28
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
29
|
+
}) : function(o, v) {
|
|
30
|
+
o["default"] = v;
|
|
31
|
+
});
|
|
32
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
33
|
+
var ownKeys = function(o) {
|
|
34
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
35
|
+
var ar = [];
|
|
36
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
37
|
+
return ar;
|
|
38
|
+
};
|
|
39
|
+
return ownKeys(o);
|
|
40
|
+
};
|
|
41
|
+
return function (mod) {
|
|
42
|
+
if (mod && mod.__esModule) return mod;
|
|
43
|
+
var result = {};
|
|
44
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
45
|
+
__setModuleDefault(result, mod);
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
})();
|
|
49
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
50
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
51
|
+
};
|
|
52
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
|
+
exports.DerpRelay = void 0;
|
|
54
|
+
const node_events_1 = require("node:events");
|
|
55
|
+
const crypto = __importStar(require("node:crypto"));
|
|
56
|
+
const ws_1 = __importDefault(require("ws"));
|
|
57
|
+
const tools_1 = require("@aria-cli/tools");
|
|
58
|
+
/** Max payload size matching WireGuard MTU */
|
|
59
|
+
const MAX_PAYLOAD_BYTES = 65535;
|
|
60
|
+
/** Max reconnection attempts before declaring dead */
|
|
61
|
+
const MAX_RECONNECT_ATTEMPTS = 10;
|
|
62
|
+
/** Base backoff interval */
|
|
63
|
+
const BASE_BACKOFF_MS = 1_000;
|
|
64
|
+
/** Max backoff interval */
|
|
65
|
+
const MAX_BACKOFF_MS = 60_000;
|
|
66
|
+
/**
|
|
67
|
+
* DERP relay client.
|
|
68
|
+
*
|
|
69
|
+
* Emits: "plaintext" (data: Buffer, fromNodeId: NodeId, displayNameSnapshot?: string), "connected", "disconnected",
|
|
70
|
+
* "dead", "error" (Error), "stateChange" (newState, prevState)
|
|
71
|
+
*/
|
|
72
|
+
class DerpRelay extends node_events_1.EventEmitter {
|
|
73
|
+
ws = null;
|
|
74
|
+
_state = "disconnected";
|
|
75
|
+
reconnectAttempts = 0;
|
|
76
|
+
reconnectTimer = null;
|
|
77
|
+
stopped = false;
|
|
78
|
+
closing = false;
|
|
79
|
+
relayUrl;
|
|
80
|
+
nodeId;
|
|
81
|
+
displayNameSnapshot;
|
|
82
|
+
signingPrivateKey;
|
|
83
|
+
signingPublicKey;
|
|
84
|
+
targetNodeId;
|
|
85
|
+
signal;
|
|
86
|
+
/** Queued messages while not connected */
|
|
87
|
+
queue = [];
|
|
88
|
+
static MAX_QUEUE = 200;
|
|
89
|
+
constructor(options) {
|
|
90
|
+
super();
|
|
91
|
+
this.relayUrl = options.relayUrl;
|
|
92
|
+
this.nodeId = options.nodeId;
|
|
93
|
+
this.displayNameSnapshot = options.displayNameSnapshot;
|
|
94
|
+
this.signingPrivateKey = options.signingPrivateKey;
|
|
95
|
+
this.signingPublicKey = options.signingPublicKey;
|
|
96
|
+
this.targetNodeId = options.targetNodeId;
|
|
97
|
+
this.signal = options.signal;
|
|
98
|
+
if (this.signal) {
|
|
99
|
+
this.signal.addEventListener("abort", () => this.disconnect(), { once: true });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** Connect to the relay server */
|
|
103
|
+
async connect() {
|
|
104
|
+
if (this.stopped || this._state === "connected" || this._state === "connecting")
|
|
105
|
+
return;
|
|
106
|
+
this.closing = false;
|
|
107
|
+
this.setState("connecting");
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
try {
|
|
110
|
+
const ws = new ws_1.default(this.relayUrl);
|
|
111
|
+
this.ws = ws;
|
|
112
|
+
const timeout = setTimeout(() => {
|
|
113
|
+
ws.close();
|
|
114
|
+
reject(new Error("Relay connection timeout"));
|
|
115
|
+
}, 15_000);
|
|
116
|
+
ws.on("open", () => {
|
|
117
|
+
clearTimeout(timeout);
|
|
118
|
+
if (this.closing) {
|
|
119
|
+
ws.close();
|
|
120
|
+
reject(new Error("Relay connection aborted: disconnect() called during connect"));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
this.setState("authenticating");
|
|
124
|
+
// Send auth message with our identity
|
|
125
|
+
ws.send(JSON.stringify({
|
|
126
|
+
type: "auth",
|
|
127
|
+
nodeId: this.nodeId,
|
|
128
|
+
signingPublicKey: this.signingPublicKey,
|
|
129
|
+
}));
|
|
130
|
+
});
|
|
131
|
+
ws.on("message", (rawData) => {
|
|
132
|
+
try {
|
|
133
|
+
const data = typeof rawData === "string" ? rawData : rawData.toString();
|
|
134
|
+
const msg = JSON.parse(data);
|
|
135
|
+
if (msg.type === "challenge" && this._state === "authenticating") {
|
|
136
|
+
// Sign the challenge to prove identity
|
|
137
|
+
const signature = this.signChallenge(msg.nonce);
|
|
138
|
+
ws.send(JSON.stringify({ type: "challenge_response", signature }));
|
|
139
|
+
}
|
|
140
|
+
else if (msg.type === "auth_ok") {
|
|
141
|
+
if (this.closing) {
|
|
142
|
+
ws.close();
|
|
143
|
+
reject(new Error("Relay connection aborted: disconnect() called during auth"));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
this.setState("connected");
|
|
147
|
+
this.reconnectAttempts = 0;
|
|
148
|
+
this.flushQueue();
|
|
149
|
+
this.emit("connected");
|
|
150
|
+
resolve();
|
|
151
|
+
}
|
|
152
|
+
else if (msg.type === "auth_error") {
|
|
153
|
+
ws.close();
|
|
154
|
+
reject(new Error(`Relay auth failed: ${msg.error}`));
|
|
155
|
+
}
|
|
156
|
+
else if (msg.type === "relay" && this._state === "connected") {
|
|
157
|
+
// Incoming relayed data from another peer
|
|
158
|
+
const fromNodeId = tools_1.NodeIdSchema.parse(msg.fromNodeId);
|
|
159
|
+
const payload = Buffer.from(msg.data, "base64");
|
|
160
|
+
this.emit("plaintext", payload, fromNodeId, msg.displayNameSnapshot);
|
|
161
|
+
}
|
|
162
|
+
else if (msg.type === "peer_offline") {
|
|
163
|
+
// Target peer is not connected to relay
|
|
164
|
+
const peerLabel = typeof msg.displayNameSnapshot === "string" && msg.displayNameSnapshot.length > 0
|
|
165
|
+
? msg.displayNameSnapshot
|
|
166
|
+
: typeof msg.nodeId === "string" && msg.nodeId.length > 0
|
|
167
|
+
? msg.nodeId
|
|
168
|
+
: this.targetNodeId;
|
|
169
|
+
this.emit("error", new Error(`Peer ${peerLabel} is offline on relay`));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
ws.on("close", () => {
|
|
177
|
+
clearTimeout(timeout);
|
|
178
|
+
const wasConnecting = this._state === "connecting" || this._state === "authenticating";
|
|
179
|
+
this.setState("disconnected");
|
|
180
|
+
if (this.stopped || this.closing) {
|
|
181
|
+
// Reject the pending connect promise so callers don't hang
|
|
182
|
+
if (wasConnecting) {
|
|
183
|
+
reject(new Error("Relay connection closed during setup"));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
this.emit("disconnected");
|
|
188
|
+
this.attemptReconnect();
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
ws.on("error", (err) => {
|
|
192
|
+
clearTimeout(timeout);
|
|
193
|
+
this.emit("error", err);
|
|
194
|
+
if (this._state === "connecting" || this._state === "authenticating") {
|
|
195
|
+
reject(err);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
this.setState("disconnected");
|
|
201
|
+
reject(err);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
/** Send data through the relay to the target peer */
|
|
206
|
+
send(data) {
|
|
207
|
+
if (this.stopped)
|
|
208
|
+
throw new Error("Relay is stopped");
|
|
209
|
+
if (data.length > MAX_PAYLOAD_BYTES) {
|
|
210
|
+
throw new Error(`Payload exceeds max size (${data.length} > ${MAX_PAYLOAD_BYTES})`);
|
|
211
|
+
}
|
|
212
|
+
if (this._state !== "connected" || !this.ws) {
|
|
213
|
+
this.enqueue(data);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
this.ws.send(JSON.stringify({
|
|
217
|
+
type: "relay",
|
|
218
|
+
toNodeId: this.targetNodeId,
|
|
219
|
+
data: data.toString("base64"),
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
/** Disconnect from the relay server */
|
|
223
|
+
disconnect() {
|
|
224
|
+
this.closing = true;
|
|
225
|
+
this.stopped = true;
|
|
226
|
+
this.clearReconnectTimer();
|
|
227
|
+
if (this.ws) {
|
|
228
|
+
try {
|
|
229
|
+
this.ws.close();
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
// ignore close errors
|
|
233
|
+
}
|
|
234
|
+
this.ws = null;
|
|
235
|
+
}
|
|
236
|
+
this.queue = [];
|
|
237
|
+
this.setState("disconnected");
|
|
238
|
+
}
|
|
239
|
+
/** Get current relay state */
|
|
240
|
+
getState() {
|
|
241
|
+
return this._state;
|
|
242
|
+
}
|
|
243
|
+
/** Whether the relay is actively connected */
|
|
244
|
+
get isConnected() {
|
|
245
|
+
return this._state === "connected";
|
|
246
|
+
}
|
|
247
|
+
// ── Internal ─────────────────────────────────────────────────────
|
|
248
|
+
setState(newState) {
|
|
249
|
+
const prev = this._state;
|
|
250
|
+
if (prev === newState)
|
|
251
|
+
return;
|
|
252
|
+
this._state = newState;
|
|
253
|
+
this.emit("stateChange", newState, prev);
|
|
254
|
+
}
|
|
255
|
+
signChallenge(nonce) {
|
|
256
|
+
const keyDer = Buffer.from(this.signingPrivateKey, "base64");
|
|
257
|
+
const privateKey = crypto.createPrivateKey({
|
|
258
|
+
key: keyDer,
|
|
259
|
+
format: "der",
|
|
260
|
+
type: "pkcs8",
|
|
261
|
+
});
|
|
262
|
+
const signature = crypto.sign(null, Buffer.from(nonce), privateKey);
|
|
263
|
+
return signature.toString("base64");
|
|
264
|
+
}
|
|
265
|
+
attemptReconnect() {
|
|
266
|
+
if (this.stopped)
|
|
267
|
+
return;
|
|
268
|
+
if (this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
269
|
+
this.setState("dead");
|
|
270
|
+
this.emit("dead");
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const backoff = Math.min(BASE_BACKOFF_MS * Math.pow(2, this.reconnectAttempts), MAX_BACKOFF_MS);
|
|
274
|
+
this.reconnectAttempts++;
|
|
275
|
+
this.reconnectTimer = setTimeout(() => {
|
|
276
|
+
if (this.stopped)
|
|
277
|
+
return;
|
|
278
|
+
void this.connect().catch((err) => {
|
|
279
|
+
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
280
|
+
});
|
|
281
|
+
}, backoff);
|
|
282
|
+
}
|
|
283
|
+
clearReconnectTimer() {
|
|
284
|
+
if (this.reconnectTimer) {
|
|
285
|
+
clearTimeout(this.reconnectTimer);
|
|
286
|
+
this.reconnectTimer = null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
enqueue(data) {
|
|
290
|
+
if (this.queue.length >= DerpRelay.MAX_QUEUE) {
|
|
291
|
+
// Drop oldest to make room
|
|
292
|
+
this.queue.shift();
|
|
293
|
+
}
|
|
294
|
+
this.queue.push(data);
|
|
295
|
+
}
|
|
296
|
+
flushQueue() {
|
|
297
|
+
if (this._state !== "connected" || !this.ws)
|
|
298
|
+
return;
|
|
299
|
+
const pending = this.queue.splice(0);
|
|
300
|
+
for (const data of pending) {
|
|
301
|
+
try {
|
|
302
|
+
this.send(data);
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
// Drop on failure during flush
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
exports.DerpRelay = DerpRelay;
|
|
311
|
+
//# sourceMappingURL=derp-relay.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @aria/wireguard — WireGuard native addon for ARIA secure networking.
|
|
3
|
+
*
|
|
4
|
+
* Wraps Cloudflare's boringtun (BSD-3) via napi-rs for userspace
|
|
5
|
+
* encrypted tunnels. Three hot-path functions: encrypt, decrypt, tick.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
export interface WireGuardResult {
|
|
10
|
+
/** "done" | "write_to_network" | "write_to_tunnel" | "error" */
|
|
11
|
+
op: string;
|
|
12
|
+
/** Output data (if any) */
|
|
13
|
+
data?: Buffer;
|
|
14
|
+
}
|
|
15
|
+
export interface KeyPair {
|
|
16
|
+
publicKey: string;
|
|
17
|
+
privateKey: string;
|
|
18
|
+
}
|
|
19
|
+
export interface WireGuardTunnelOptions {
|
|
20
|
+
/** Base64-encoded X25519 private key */
|
|
21
|
+
privateKey: string;
|
|
22
|
+
/** Base64-encoded X25519 peer public key */
|
|
23
|
+
peerPublicKey: string;
|
|
24
|
+
/** Optional base64-encoded preshared key */
|
|
25
|
+
presharedKey?: string;
|
|
26
|
+
/** Persistent keepalive interval in seconds (0 = disabled) */
|
|
27
|
+
keepalive?: number;
|
|
28
|
+
/** Tunnel index for session disambiguation (random if not provided) */
|
|
29
|
+
index?: number;
|
|
30
|
+
}
|
|
31
|
+
/** Validate that the native addon can be loaded in the current runtime. */
|
|
32
|
+
export declare function assertNativeAddonAvailable(): void;
|
|
33
|
+
/** Create a new WireGuard tunnel */
|
|
34
|
+
export declare function createTunnel(options: WireGuardTunnelOptions): {
|
|
35
|
+
encrypt(src: Buffer): WireGuardResult;
|
|
36
|
+
decrypt(src: Buffer, srcAddr?: string | undefined | null): WireGuardResult;
|
|
37
|
+
tick(): WireGuardResult;
|
|
38
|
+
};
|
|
39
|
+
/** Generate a new X25519 keypair for WireGuard */
|
|
40
|
+
export declare function generateKeypair(): KeyPair;
|
|
41
|
+
export { SecureTunnel } from "./tunnel.js";
|
|
42
|
+
export type { SecureTunnelOptions, TunnelStats } from "./tunnel.js";
|
|
43
|
+
export { ResilientTunnel } from "./resilient-tunnel.js";
|
|
44
|
+
export type { TunnelState, ResilientTunnelStats } from "./resilient-tunnel.js";
|
|
45
|
+
export { StunClient, discoverEndpoint, detectNatType } from "./nat.js";
|
|
46
|
+
export type { StunResult, NatType } from "./nat.js";
|
|
47
|
+
export { NetworkManager, PeerRegistry, generateKeyPair, generateSigningKeypair, createInviteToken, decodeInviteToken, ensureSecureNetwork, } from "./network.js";
|
|
48
|
+
export type { NetworkConfig, PeerInfo, InviteToken } from "./network.js";
|
|
49
|
+
export { PeerDiscoveryService } from "./peer-discovery.js";
|
|
50
|
+
export type { PeerDiscoveryOptions, DiscoveredPeer, PeerDiscoveryNetworkManager, } from "./peer-discovery.js";
|
|
51
|
+
export { DerpRelay } from "./derp-relay.js";
|
|
52
|
+
export type { DerpRelayOptions, DerpRelayState } from "./derp-relay.js";
|
|
53
|
+
//# sourceMappingURL=index.d.ts.map
|