@antseed/node 0.1.0 → 0.1.2

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.
Files changed (140) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +7 -5
  3. package/dist/discovery/http-metadata-resolver.d.ts +6 -0
  4. package/dist/discovery/http-metadata-resolver.d.ts.map +1 -1
  5. package/dist/discovery/http-metadata-resolver.js +32 -4
  6. package/dist/discovery/http-metadata-resolver.js.map +1 -1
  7. package/dist/discovery/peer-lookup.d.ts +1 -0
  8. package/dist/discovery/peer-lookup.d.ts.map +1 -1
  9. package/dist/discovery/peer-lookup.js +10 -25
  10. package/dist/discovery/peer-lookup.js.map +1 -1
  11. package/dist/index.d.ts +2 -2
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/interfaces/seller-provider.d.ts +13 -1
  16. package/dist/interfaces/seller-provider.d.ts.map +1 -1
  17. package/dist/node.d.ts +13 -3
  18. package/dist/node.d.ts.map +1 -1
  19. package/dist/node.js +146 -21
  20. package/dist/node.js.map +1 -1
  21. package/dist/proxy/proxy-mux.d.ts +3 -1
  22. package/dist/proxy/proxy-mux.d.ts.map +1 -1
  23. package/dist/proxy/proxy-mux.js +9 -5
  24. package/dist/proxy/proxy-mux.js.map +1 -1
  25. package/dist/types/http.d.ts +1 -0
  26. package/dist/types/http.d.ts.map +1 -1
  27. package/dist/types/http.js +1 -1
  28. package/dist/types/http.js.map +1 -1
  29. package/package.json +14 -10
  30. package/contracts/AntseedEscrow.sol +0 -310
  31. package/contracts/MockUSDC.sol +0 -64
  32. package/contracts/README.md +0 -102
  33. package/src/config/encryption.test.ts +0 -49
  34. package/src/config/encryption.ts +0 -53
  35. package/src/config/plugin-config-manager.test.ts +0 -92
  36. package/src/config/plugin-config-manager.ts +0 -153
  37. package/src/config/plugin-loader.ts +0 -90
  38. package/src/discovery/announcer.ts +0 -169
  39. package/src/discovery/bootstrap.ts +0 -57
  40. package/src/discovery/default-metadata-resolver.ts +0 -18
  41. package/src/discovery/dht-health.ts +0 -136
  42. package/src/discovery/dht-node.ts +0 -191
  43. package/src/discovery/http-metadata-resolver.ts +0 -47
  44. package/src/discovery/index.ts +0 -15
  45. package/src/discovery/metadata-codec.ts +0 -453
  46. package/src/discovery/metadata-resolver.ts +0 -7
  47. package/src/discovery/metadata-server.ts +0 -73
  48. package/src/discovery/metadata-validator.ts +0 -172
  49. package/src/discovery/peer-lookup.ts +0 -122
  50. package/src/discovery/peer-metadata.ts +0 -34
  51. package/src/discovery/peer-selector.ts +0 -134
  52. package/src/discovery/profile-manager.ts +0 -131
  53. package/src/discovery/profile-search.ts +0 -100
  54. package/src/discovery/reputation-verifier.ts +0 -54
  55. package/src/index.ts +0 -61
  56. package/src/interfaces/buyer-router.ts +0 -21
  57. package/src/interfaces/plugin.ts +0 -36
  58. package/src/interfaces/seller-provider.ts +0 -81
  59. package/src/metering/index.ts +0 -6
  60. package/src/metering/receipt-generator.ts +0 -105
  61. package/src/metering/receipt-verifier.ts +0 -102
  62. package/src/metering/session-tracker.ts +0 -145
  63. package/src/metering/storage.ts +0 -600
  64. package/src/metering/token-counter.ts +0 -127
  65. package/src/metering/usage-aggregator.ts +0 -236
  66. package/src/node.ts +0 -1698
  67. package/src/p2p/connection-auth.ts +0 -152
  68. package/src/p2p/connection-manager.ts +0 -916
  69. package/src/p2p/handshake.ts +0 -162
  70. package/src/p2p/ice-config.ts +0 -59
  71. package/src/p2p/identity.ts +0 -110
  72. package/src/p2p/index.ts +0 -11
  73. package/src/p2p/keepalive.ts +0 -118
  74. package/src/p2p/message-protocol.ts +0 -171
  75. package/src/p2p/nat-traversal.ts +0 -169
  76. package/src/p2p/payment-codec.ts +0 -165
  77. package/src/p2p/payment-mux.ts +0 -153
  78. package/src/p2p/reconnect.ts +0 -117
  79. package/src/payments/balance-manager.ts +0 -77
  80. package/src/payments/buyer-payment-manager.ts +0 -414
  81. package/src/payments/disputes.ts +0 -72
  82. package/src/payments/evm/escrow-client.ts +0 -263
  83. package/src/payments/evm/keypair.ts +0 -31
  84. package/src/payments/evm/signatures.ts +0 -103
  85. package/src/payments/evm/wallet.ts +0 -42
  86. package/src/payments/index.ts +0 -50
  87. package/src/payments/settlement.ts +0 -40
  88. package/src/payments/types.ts +0 -79
  89. package/src/proxy/index.ts +0 -3
  90. package/src/proxy/provider-detection.ts +0 -78
  91. package/src/proxy/proxy-mux.ts +0 -173
  92. package/src/proxy/request-codec.ts +0 -294
  93. package/src/reputation/index.ts +0 -6
  94. package/src/reputation/rating-manager.ts +0 -118
  95. package/src/reputation/report-manager.ts +0 -91
  96. package/src/reputation/trust-engine.ts +0 -120
  97. package/src/reputation/trust-score.ts +0 -74
  98. package/src/reputation/uptime-tracker.ts +0 -155
  99. package/src/routing/default-router.ts +0 -75
  100. package/src/types/bittorrent-dht.d.ts +0 -19
  101. package/src/types/buyer.ts +0 -37
  102. package/src/types/capability.ts +0 -34
  103. package/src/types/connection.ts +0 -29
  104. package/src/types/http.ts +0 -20
  105. package/src/types/index.ts +0 -14
  106. package/src/types/metering.ts +0 -175
  107. package/src/types/nat-api.d.ts +0 -29
  108. package/src/types/peer-profile.ts +0 -25
  109. package/src/types/peer.ts +0 -62
  110. package/src/types/plugin-config.ts +0 -31
  111. package/src/types/protocol.ts +0 -162
  112. package/src/types/provider.ts +0 -40
  113. package/src/types/rating.ts +0 -23
  114. package/src/types/report.ts +0 -30
  115. package/src/types/seller.ts +0 -38
  116. package/src/types/staking.ts +0 -23
  117. package/src/utils/debug.ts +0 -30
  118. package/src/utils/hex.ts +0 -14
  119. package/tests/balance-manager.test.ts +0 -156
  120. package/tests/bootstrap.test.ts +0 -108
  121. package/tests/buyer-payment-manager.test.ts +0 -358
  122. package/tests/connection-auth.test.ts +0 -87
  123. package/tests/default-router.test.ts +0 -148
  124. package/tests/evm-keypair.test.ts +0 -173
  125. package/tests/identity.test.ts +0 -133
  126. package/tests/message-protocol.test.ts +0 -212
  127. package/tests/metadata-codec.test.ts +0 -165
  128. package/tests/metadata-validator.test.ts +0 -261
  129. package/tests/metering-storage.test.ts +0 -244
  130. package/tests/payment-codec.test.ts +0 -95
  131. package/tests/payment-mux.test.ts +0 -191
  132. package/tests/peer-selector.test.ts +0 -184
  133. package/tests/provider-detection.test.ts +0 -107
  134. package/tests/proxy-mux-security.test.ts +0 -38
  135. package/tests/receipt.test.ts +0 -215
  136. package/tests/reputation-integration.test.ts +0 -195
  137. package/tests/request-codec.test.ts +0 -144
  138. package/tests/token-counter.test.ts +0 -122
  139. package/tsconfig.json +0 -9
  140. package/vitest.config.ts +0 -7
@@ -1,162 +0,0 @@
1
- import { type PeerId, toPeerId } from "../types/peer.js";
2
- import { MessageType } from "../types/protocol.js";
3
- import { ConnectionState } from "../types/connection.js";
4
- import { signData, verifySignature, bytesToHex } from "./identity.js";
5
- import type { PeerConnection } from "./connection-manager.js";
6
-
7
- /** Nonce size in bytes. */
8
- const NONCE_SIZE = 32;
9
-
10
- /** Handshake timeout in milliseconds. */
11
- const HANDSHAKE_TIMEOUT_MS = 10_000;
12
-
13
- export interface HandshakeResult {
14
- remotePeerId: PeerId;
15
- verified: boolean;
16
- }
17
-
18
- /** Generate a random nonce for the handshake challenge. */
19
- function generateNonce(): Uint8Array {
20
- const nonce = new Uint8Array(NONCE_SIZE);
21
- crypto.getRandomValues(nonce);
22
- return nonce;
23
- }
24
-
25
- /**
26
- * Build the HandshakeInit payload:
27
- * [32 bytes pubkey] [32 bytes nonce] [64 bytes signature(nonce)]
28
- */
29
- export async function buildHandshakeInit(
30
- publicKey: Uint8Array,
31
- privateKey: Uint8Array
32
- ): Promise<{ payload: Uint8Array; nonce: Uint8Array }> {
33
- const nonce = generateNonce();
34
- const signature = await signData(privateKey, nonce);
35
-
36
- const payload = new Uint8Array(32 + 32 + 64);
37
- payload.set(publicKey, 0);
38
- payload.set(nonce, 32);
39
- payload.set(signature, 64);
40
-
41
- return { payload, nonce };
42
- }
43
-
44
- /**
45
- * Verify a received HandshakeInit payload.
46
- * Returns the remote peer's public key and nonce if valid.
47
- */
48
- export async function verifyHandshakeInit(
49
- payload: Uint8Array
50
- ): Promise<{ remotePubKey: Uint8Array; nonce: Uint8Array; valid: boolean }> {
51
- if (payload.length !== 128) {
52
- return { remotePubKey: new Uint8Array(32), nonce: new Uint8Array(32), valid: false };
53
- }
54
-
55
- const remotePubKey = payload.slice(0, 32);
56
- const nonce = payload.slice(32, 64);
57
- const signature = payload.slice(64, 128);
58
-
59
- const valid = await verifySignature(remotePubKey, signature, nonce);
60
-
61
- return { remotePubKey, nonce, valid };
62
- }
63
-
64
- /**
65
- * Build a HandshakeAck payload:
66
- * [32 bytes pubkey] [32 bytes echo-nonce] [64 bytes signature(echo-nonce)]
67
- */
68
- export async function buildHandshakeAck(
69
- publicKey: Uint8Array,
70
- privateKey: Uint8Array,
71
- remoteNonce: Uint8Array
72
- ): Promise<Uint8Array> {
73
- const signature = await signData(privateKey, remoteNonce);
74
-
75
- const payload = new Uint8Array(32 + 32 + 64);
76
- payload.set(publicKey, 0);
77
- payload.set(remoteNonce, 32);
78
- payload.set(signature, 64);
79
-
80
- return payload;
81
- }
82
-
83
- /**
84
- * Verify a received HandshakeAck payload against the nonce we originally sent.
85
- */
86
- export async function verifyHandshakeAck(
87
- payload: Uint8Array,
88
- originalNonce: Uint8Array
89
- ): Promise<{ remotePubKey: Uint8Array; valid: boolean }> {
90
- if (payload.length !== 128) {
91
- return { remotePubKey: new Uint8Array(32), valid: false };
92
- }
93
-
94
- const remotePubKey = payload.slice(0, 32);
95
- const echoNonce = payload.slice(32, 64);
96
- const signature = payload.slice(64, 128);
97
-
98
- // Verify that the echoed nonce matches what we sent
99
- const nonceMatches = originalNonce.every((b, i) => b === echoNonce[i]);
100
- if (!nonceMatches) {
101
- return { remotePubKey, valid: false };
102
- }
103
-
104
- const valid = await verifySignature(remotePubKey, signature, echoNonce);
105
- return { remotePubKey, valid };
106
- }
107
-
108
- /**
109
- * Perform the full handshake as the initiator.
110
- * Sends HandshakeInit, waits for HandshakeAck, verifies.
111
- */
112
- export async function performHandshake(
113
- conn: PeerConnection,
114
- localPublicKey: Uint8Array,
115
- localPrivateKey: Uint8Array,
116
- sendFn: (type: MessageType, payload: Uint8Array) => void,
117
- waitForMessage: (type: MessageType, timeoutMs: number) => Promise<Uint8Array>
118
- ): Promise<HandshakeResult> {
119
- const { payload, nonce } = await buildHandshakeInit(localPublicKey, localPrivateKey);
120
- sendFn(MessageType.HandshakeInit, payload);
121
-
122
- const ackPayload = await waitForMessage(MessageType.HandshakeAck, HANDSHAKE_TIMEOUT_MS);
123
- const { remotePubKey, valid } = await verifyHandshakeAck(ackPayload, nonce);
124
-
125
- const remotePeerId = toPeerId(bytesToHex(remotePubKey));
126
-
127
- if (valid) {
128
- conn.setState(ConnectionState.Authenticated);
129
- } else {
130
- conn.setState(ConnectionState.Failed);
131
- }
132
-
133
- return { remotePeerId, verified: valid };
134
- }
135
-
136
- /**
137
- * Respond to a handshake as the responder.
138
- * Waits for HandshakeInit, verifies, sends HandshakeAck.
139
- */
140
- export async function respondToHandshake(
141
- conn: PeerConnection,
142
- localPublicKey: Uint8Array,
143
- localPrivateKey: Uint8Array,
144
- sendFn: (type: MessageType, payload: Uint8Array) => void,
145
- waitForMessage: (type: MessageType, timeoutMs: number) => Promise<Uint8Array>
146
- ): Promise<HandshakeResult> {
147
- const initPayload = await waitForMessage(MessageType.HandshakeInit, HANDSHAKE_TIMEOUT_MS);
148
- const { remotePubKey, nonce, valid } = await verifyHandshakeInit(initPayload);
149
-
150
- if (!valid) {
151
- conn.setState(ConnectionState.Failed);
152
- return { remotePeerId: toPeerId(bytesToHex(remotePubKey)), verified: false };
153
- }
154
-
155
- const ackPayload = await buildHandshakeAck(localPublicKey, localPrivateKey, nonce);
156
- sendFn(MessageType.HandshakeAck, ackPayload);
157
-
158
- const remotePeerId = toPeerId(bytesToHex(remotePubKey));
159
- conn.setState(ConnectionState.Authenticated);
160
-
161
- return { remotePeerId, verified: true };
162
- }
@@ -1,59 +0,0 @@
1
- /** STUN/TURN server configuration. */
2
- export interface IceServer {
3
- urls: string | string[];
4
- username?: string;
5
- credential?: string;
6
- }
7
-
8
- /** Full ICE configuration for a peer connection. */
9
- export interface IceConfig {
10
- iceServers: IceServer[];
11
- iceTransportPolicy?: "all" | "relay";
12
- }
13
-
14
- /** Returns a sensible default ICE configuration using public STUN servers. */
15
- export function getDefaultIceConfig(): IceConfig {
16
- return {
17
- iceServers: [
18
- { urls: "stun:stun.l.google.com:19302" },
19
- { urls: "stun:stun1.l.google.com:19302" },
20
- { urls: "stun:stun2.l.google.com:19302" },
21
- ],
22
- iceTransportPolicy: "all",
23
- };
24
- }
25
-
26
- /** Extract ICE candidate type from an SDP candidate string. */
27
- export function extractCandidateType(
28
- candidate: string
29
- ): "host" | "srflx" | "prflx" | "relay" | "unknown" {
30
- const match = candidate.match(/typ\s+(host|srflx|prflx|relay)/);
31
- if (!match) return "unknown";
32
- return match[1] as "host" | "srflx" | "prflx" | "relay";
33
- }
34
-
35
- /**
36
- * Determine if TURN relay fallback is needed based on gathered candidates.
37
- * Returns true if no srflx (server-reflexive) candidates were gathered,
38
- * which typically indicates a symmetric NAT.
39
- */
40
- export function needsTurnFallback(candidates: string[]): boolean {
41
- const types = candidates.map(extractCandidateType);
42
- const hasSrflx = types.includes("srflx");
43
- const hasRelay = types.includes("relay");
44
- // Need TURN if we have no server-reflexive candidates
45
- // (unless we already have relay candidates working)
46
- return !hasSrflx && !hasRelay;
47
- }
48
-
49
- /**
50
- * Build a complete ICE configuration, optionally adding TURN servers.
51
- * If turnServers are provided, they are appended to the default STUN servers.
52
- */
53
- export function buildIceConfig(turnServers?: IceServer[]): IceConfig {
54
- const config = getDefaultIceConfig();
55
- if (turnServers && turnServers.length > 0) {
56
- config.iceServers.push(...turnServers);
57
- }
58
- return config;
59
- }
@@ -1,110 +0,0 @@
1
- import * as ed from "@noble/ed25519";
2
- import { readFile, writeFile, mkdir } from "node:fs/promises";
3
- import { join } from "node:path";
4
- import { homedir } from "node:os";
5
- import {
6
- createPrivateKey,
7
- createPublicKey,
8
- sign as nodeSign,
9
- verify as nodeVerify,
10
- } from "node:crypto";
11
- import { toPeerId, type PeerId } from "../types/peer.js";
12
- import { hexToBytes, bytesToHex } from "../utils/hex.js";
13
-
14
- export { hexToBytes, bytesToHex };
15
-
16
- /** Directory where identity keys are stored. */
17
- const CONFIG_DIR = join(homedir(), ".antseed");
18
- const PRIVATE_KEY_FILE = "identity.key";
19
- const ED25519_PKCS8_SEED_PREFIX = Buffer.from("302e020100300506032b657004220420", "hex");
20
- const ED25519_SPKI_PUBLIC_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
21
-
22
- export interface Identity {
23
- peerId: PeerId;
24
- privateKey: Uint8Array;
25
- publicKey: Uint8Array;
26
- }
27
-
28
- /**
29
- * Load an existing identity from disk, or create and persist a new one.
30
- * The private key is stored as hex in ~/.antseed/identity.key.
31
- */
32
- export async function loadOrCreateIdentity(configDir?: string): Promise<Identity> {
33
- const dir = configDir ?? CONFIG_DIR;
34
- const keyPath = join(dir, PRIVATE_KEY_FILE);
35
-
36
- try {
37
- const hexKey = (await readFile(keyPath, "utf-8")).trim();
38
- const privateKey = hexToBytes(hexKey);
39
- const publicKey = await ed.getPublicKeyAsync(privateKey);
40
- const peerId = toPeerId(bytesToHex(publicKey));
41
- return { peerId, privateKey, publicKey };
42
- } catch {
43
- // Key doesn't exist — generate a new one.
44
- const privateKey = ed.utils.randomPrivateKey();
45
- const publicKey = await ed.getPublicKeyAsync(privateKey);
46
- const peerId = toPeerId(bytesToHex(publicKey));
47
-
48
- await mkdir(dir, { recursive: true });
49
- await writeFile(keyPath, bytesToHex(privateKey), { mode: 0o600 });
50
-
51
- return { peerId, privateKey, publicKey };
52
- }
53
- }
54
-
55
- /** Sign arbitrary data with the local identity's private key. */
56
- export async function signData(
57
- privateKey: Uint8Array,
58
- data: Uint8Array
59
- ): Promise<Uint8Array> {
60
- return ed.signAsync(data, privateKey);
61
- }
62
-
63
- /** Verify a signature from a remote peer. */
64
- export async function verifySignature(
65
- publicKey: Uint8Array,
66
- signature: Uint8Array,
67
- data: Uint8Array
68
- ): Promise<boolean> {
69
- return ed.verifyAsync(signature, data, publicKey);
70
- }
71
-
72
- /**
73
- * Sign a UTF-8 message and return a hex-encoded Ed25519 signature.
74
- * Uses Node's crypto implementation for synchronous signing.
75
- */
76
- export function signUtf8Ed25519(privateKeySeed: Uint8Array, message: string): string {
77
- const key = createPrivateKey({
78
- key: Buffer.concat([ED25519_PKCS8_SEED_PREFIX, Buffer.from(privateKeySeed)]),
79
- format: "der",
80
- type: "pkcs8",
81
- });
82
- const signature = nodeSign(null, Buffer.from(message, "utf-8"), key);
83
- return signature.toString("hex");
84
- }
85
-
86
- /**
87
- * Verify a UTF-8 message against a hex-encoded Ed25519 signature.
88
- */
89
- export function verifyUtf8Ed25519(
90
- publicKeyHex: string,
91
- message: string,
92
- signatureHex: string
93
- ): boolean {
94
- try {
95
- const publicKeyBytes = hexToBytes(publicKeyHex);
96
- const key = createPublicKey({
97
- key: Buffer.concat([ED25519_SPKI_PUBLIC_PREFIX, Buffer.from(publicKeyBytes)]),
98
- format: "der",
99
- type: "spki",
100
- });
101
- return nodeVerify(
102
- null,
103
- Buffer.from(message, "utf-8"),
104
- key,
105
- Buffer.from(signatureHex, "hex")
106
- );
107
- } catch {
108
- return false;
109
- }
110
- }
package/src/p2p/index.ts DELETED
@@ -1,11 +0,0 @@
1
- export { type Identity, loadOrCreateIdentity, signData, verifySignature, hexToBytes, bytesToHex, signUtf8Ed25519, verifyUtf8Ed25519 } from './identity.js';
2
- export { encodeFrame, decodeFrame, FrameDecoder, MessageMux, type MessageHandler } from './message-protocol.js';
3
- export { ConnectionManager, PeerConnection, type PeerEndpoint } from './connection-manager.js';
4
- export { type IceServer, type IceConfig, getDefaultIceConfig, buildIceConfig, needsTurnFallback, extractCandidateType } from './ice-config.js';
5
- export { KeepaliveManager, buildPongPayload, type KeepaliveConfig, type KeepaliveCallbacks, DEFAULT_PING_INTERVAL_MS, DEFAULT_PONG_TIMEOUT_MS, MAX_MISSED_PONGS } from './keepalive.js';
6
- export { ReconnectManager, type ReconnectConfig, type ReconnectCallbacks } from './reconnect.js';
7
- export { performHandshake, respondToHandshake, buildHandshakeInit, verifyHandshakeInit, buildHandshakeAck, verifyHandshakeAck, type HandshakeResult } from './handshake.js';
8
- export { NatTraversal, type NatMapping, type NatTraversalResult } from './nat-traversal.js';
9
- export { PaymentMux } from './payment-mux.js';
10
- export type { PaymentMessageHandler } from './payment-mux.js';
11
- export * from './payment-codec.js';
@@ -1,118 +0,0 @@
1
- /** Default keepalive interval in milliseconds. */
2
- export const DEFAULT_PING_INTERVAL_MS = 15_000;
3
-
4
- /** Default timeout waiting for a pong response. */
5
- export const DEFAULT_PONG_TIMEOUT_MS = 5_000;
6
-
7
- /** Maximum consecutive missed pongs before declaring connection dead. */
8
- export const MAX_MISSED_PONGS = 3;
9
-
10
- export interface KeepaliveConfig {
11
- pingIntervalMs?: number;
12
- pongTimeoutMs?: number;
13
- maxMissedPongs?: number;
14
- }
15
-
16
- export interface KeepaliveCallbacks {
17
- sendPing: (payload: Uint8Array) => void;
18
- onDead: () => void;
19
- }
20
-
21
- /**
22
- * Manages the keepalive (ping/pong) cycle for a peer connection.
23
- *
24
- * The initiator sends Ping messages at a regular interval. If no Pong
25
- * is received within pongTimeoutMs, it increments the missed counter.
26
- * After maxMissedPongs consecutive misses, the connection is declared dead.
27
- */
28
- export class KeepaliveManager {
29
- private _pingInterval: number;
30
- private _pongTimeout: number;
31
- private _maxMissedPongs: number;
32
- private _missedPongs = 0;
33
- private _intervalHandle: ReturnType<typeof setInterval> | null = null;
34
- private _pongTimeoutHandle: ReturnType<typeof setTimeout> | null = null;
35
- private _callbacks: KeepaliveCallbacks;
36
- private _lastPingTime = 0;
37
- private _latencyMs = 0;
38
- private _running = false;
39
-
40
- constructor(callbacks: KeepaliveCallbacks, config?: KeepaliveConfig) {
41
- this._callbacks = callbacks;
42
- this._pingInterval = config?.pingIntervalMs ?? DEFAULT_PING_INTERVAL_MS;
43
- this._pongTimeout = config?.pongTimeoutMs ?? DEFAULT_PONG_TIMEOUT_MS;
44
- this._maxMissedPongs = config?.maxMissedPongs ?? MAX_MISSED_PONGS;
45
- }
46
-
47
- get missedPongs(): number {
48
- return this._missedPongs;
49
- }
50
-
51
- get latencyMs(): number {
52
- return this._latencyMs;
53
- }
54
-
55
- get isRunning(): boolean {
56
- return this._running;
57
- }
58
-
59
- /** Start the keepalive ping cycle. */
60
- start(): void {
61
- if (this._running) return;
62
- this._running = true;
63
- this._missedPongs = 0;
64
- this._sendPing();
65
- this._intervalHandle = setInterval(() => this._sendPing(), this._pingInterval);
66
- }
67
-
68
- /** Stop the keepalive cycle. */
69
- stop(): void {
70
- this._running = false;
71
- if (this._intervalHandle) {
72
- clearInterval(this._intervalHandle);
73
- this._intervalHandle = null;
74
- }
75
- if (this._pongTimeoutHandle) {
76
- clearTimeout(this._pongTimeoutHandle);
77
- this._pongTimeoutHandle = null;
78
- }
79
- }
80
-
81
- /** Handle a received Pong message. */
82
- handlePong(_payload: Uint8Array): void {
83
- this._missedPongs = 0;
84
- this._latencyMs = Date.now() - this._lastPingTime;
85
-
86
- if (this._pongTimeoutHandle) {
87
- clearTimeout(this._pongTimeoutHandle);
88
- this._pongTimeoutHandle = null;
89
- }
90
- }
91
-
92
- /** Send a ping and set up the pong timeout. */
93
- private _sendPing(): void {
94
- this._lastPingTime = Date.now();
95
-
96
- // Payload is the timestamp as 8 bytes (BigUint64)
97
- const payload = new Uint8Array(8);
98
- const view = new DataView(payload.buffer);
99
- view.setBigUint64(0, BigInt(this._lastPingTime), false);
100
-
101
- this._callbacks.sendPing(payload);
102
-
103
- // Set timeout for pong response
104
- this._pongTimeoutHandle = setTimeout(() => {
105
- this._missedPongs++;
106
- if (this._missedPongs >= this._maxMissedPongs) {
107
- this.stop();
108
- this._callbacks.onDead();
109
- }
110
- }, this._pongTimeout);
111
- }
112
- }
113
-
114
- /** Build a Pong payload echoing the Ping payload. */
115
- export function buildPongPayload(pingPayload: Uint8Array): Uint8Array {
116
- // Echo the same payload back
117
- return new Uint8Array(pingPayload);
118
- }
@@ -1,171 +0,0 @@
1
- import {
2
- MessageType,
3
- type FramedMessage,
4
- FRAME_HEADER_SIZE,
5
- MAX_PAYLOAD_SIZE,
6
- } from "../types/protocol.js";
7
-
8
- /**
9
- * Frame layout (9 bytes header + payload):
10
- * [0] u8 — message type
11
- * [1..4] u32 — message ID (big-endian)
12
- * [5..8] u32 — payload length (big-endian)
13
- * [9..] raw — payload bytes
14
- */
15
-
16
- /** Encode a FramedMessage into a binary buffer. */
17
- export function encodeFrame(msg: FramedMessage): Uint8Array {
18
- if (msg.payload.length > MAX_PAYLOAD_SIZE) {
19
- throw new Error(
20
- `Payload too large: ${msg.payload.length} > ${MAX_PAYLOAD_SIZE}`
21
- );
22
- }
23
-
24
- const frame = new Uint8Array(FRAME_HEADER_SIZE + msg.payload.length);
25
- const view = new DataView(frame.buffer);
26
-
27
- view.setUint8(0, msg.type);
28
- view.setUint32(1, msg.messageId);
29
- view.setUint32(5, msg.payload.length);
30
- frame.set(msg.payload, FRAME_HEADER_SIZE);
31
-
32
- return frame;
33
- }
34
-
35
- const VALID_MESSAGE_TYPES = new Set<number>(
36
- Object.values(MessageType).filter((v): v is number => typeof v === "number"),
37
- );
38
-
39
- function isValidMessageType(value: number): boolean {
40
- return VALID_MESSAGE_TYPES.has(value);
41
- }
42
-
43
- /**
44
- * Decode a single FramedMessage from a binary buffer.
45
- * Returns the message and the number of bytes consumed.
46
- * Returns null if the buffer doesn't contain a complete frame.
47
- */
48
- export function decodeFrame(
49
- data: Uint8Array
50
- ): { message: FramedMessage; bytesConsumed: number } | null {
51
- if (data.length < FRAME_HEADER_SIZE) {
52
- return null;
53
- }
54
-
55
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
56
- const rawType = view.getUint8(0);
57
- if (!isValidMessageType(rawType)) {
58
- return null;
59
- }
60
- const type = rawType as MessageType;
61
- const messageId = view.getUint32(1);
62
- const payloadLength = view.getUint32(5);
63
-
64
- if (payloadLength > MAX_PAYLOAD_SIZE) {
65
- throw new Error(
66
- `Payload length ${payloadLength} exceeds maximum ${MAX_PAYLOAD_SIZE}`
67
- );
68
- }
69
-
70
- const totalLength = FRAME_HEADER_SIZE + payloadLength;
71
- if (data.length < totalLength) {
72
- return null; // incomplete frame
73
- }
74
-
75
- const payload = data.slice(FRAME_HEADER_SIZE, totalLength);
76
-
77
- return {
78
- message: { type, messageId, payload },
79
- bytesConsumed: totalLength,
80
- };
81
- }
82
-
83
- /**
84
- * Streaming frame decoder that handles partial frames across
85
- * multiple data chunks (e.g., from a DataChannel).
86
- */
87
- export class FrameDecoder {
88
- private _buffer: Uint8Array = new Uint8Array(0);
89
-
90
- /** Feed new data and return any complete frames. */
91
- feed(chunk: Uint8Array): FramedMessage[] {
92
- // Append chunk to buffer
93
- const newBuffer = new Uint8Array(this._buffer.length + chunk.length);
94
- newBuffer.set(this._buffer, 0);
95
- newBuffer.set(chunk, this._buffer.length);
96
- this._buffer = newBuffer;
97
-
98
- const messages: FramedMessage[] = [];
99
-
100
- while (true) {
101
- let result: ReturnType<typeof decodeFrame>;
102
- try {
103
- result = decodeFrame(this._buffer);
104
- } catch {
105
- this._buffer = new Uint8Array(0);
106
- break;
107
- }
108
- if (!result) break;
109
-
110
- messages.push(result.message);
111
- this._buffer = this._buffer.slice(result.bytesConsumed);
112
- }
113
-
114
- return messages;
115
- }
116
-
117
- /** Reset the internal buffer. */
118
- reset(): void {
119
- this._buffer = new Uint8Array(0);
120
- }
121
-
122
- /** Number of buffered bytes not yet decoded. */
123
- get bufferedBytes(): number {
124
- return this._buffer.length;
125
- }
126
- }
127
-
128
- /** Handler function for a specific message type. */
129
- export type MessageHandler = (msg: FramedMessage) => void | Promise<void>;
130
-
131
- /**
132
- * Message multiplexer — routes decoded frames to registered handlers.
133
- */
134
- export class MessageMux {
135
- private _handlers = new Map<MessageType, MessageHandler[]>();
136
- private _defaultHandler: MessageHandler | null = null;
137
-
138
- /** Register a handler for a specific message type. */
139
- on(type: MessageType, handler: MessageHandler): void {
140
- const existing = this._handlers.get(type) ?? [];
141
- existing.push(handler);
142
- this._handlers.set(type, existing);
143
- }
144
-
145
- /** Remove a handler for a specific message type. */
146
- off(type: MessageType, handler: MessageHandler): void {
147
- const existing = this._handlers.get(type);
148
- if (!existing) return;
149
- const idx = existing.indexOf(handler);
150
- if (idx !== -1) {
151
- existing.splice(idx, 1);
152
- }
153
- }
154
-
155
- /** Set a default handler for unregistered message types. */
156
- setDefaultHandler(handler: MessageHandler): void {
157
- this._defaultHandler = handler;
158
- }
159
-
160
- /** Dispatch a framed message to registered handlers. */
161
- async dispatch(msg: FramedMessage): Promise<void> {
162
- const handlers = this._handlers.get(msg.type);
163
- if (handlers && handlers.length > 0) {
164
- for (const handler of handlers) {
165
- await handler(msg);
166
- }
167
- } else if (this._defaultHandler) {
168
- await this._defaultHandler(msg);
169
- }
170
- }
171
- }