@antseed/node 0.1.0 → 0.1.1

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 (130) hide show
  1. package/dist/index.d.ts +2 -2
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/interfaces/seller-provider.d.ts +13 -1
  6. package/dist/interfaces/seller-provider.d.ts.map +1 -1
  7. package/dist/node.d.ts +13 -3
  8. package/dist/node.d.ts.map +1 -1
  9. package/dist/node.js +123 -15
  10. package/dist/node.js.map +1 -1
  11. package/dist/proxy/proxy-mux.d.ts +3 -1
  12. package/dist/proxy/proxy-mux.d.ts.map +1 -1
  13. package/dist/proxy/proxy-mux.js +9 -5
  14. package/dist/proxy/proxy-mux.js.map +1 -1
  15. package/dist/types/http.d.ts +1 -0
  16. package/dist/types/http.d.ts.map +1 -1
  17. package/dist/types/http.js +1 -1
  18. package/dist/types/http.js.map +1 -1
  19. package/package.json +14 -10
  20. package/contracts/AntseedEscrow.sol +0 -310
  21. package/contracts/MockUSDC.sol +0 -64
  22. package/contracts/README.md +0 -102
  23. package/src/config/encryption.test.ts +0 -49
  24. package/src/config/encryption.ts +0 -53
  25. package/src/config/plugin-config-manager.test.ts +0 -92
  26. package/src/config/plugin-config-manager.ts +0 -153
  27. package/src/config/plugin-loader.ts +0 -90
  28. package/src/discovery/announcer.ts +0 -169
  29. package/src/discovery/bootstrap.ts +0 -57
  30. package/src/discovery/default-metadata-resolver.ts +0 -18
  31. package/src/discovery/dht-health.ts +0 -136
  32. package/src/discovery/dht-node.ts +0 -191
  33. package/src/discovery/http-metadata-resolver.ts +0 -47
  34. package/src/discovery/index.ts +0 -15
  35. package/src/discovery/metadata-codec.ts +0 -453
  36. package/src/discovery/metadata-resolver.ts +0 -7
  37. package/src/discovery/metadata-server.ts +0 -73
  38. package/src/discovery/metadata-validator.ts +0 -172
  39. package/src/discovery/peer-lookup.ts +0 -122
  40. package/src/discovery/peer-metadata.ts +0 -34
  41. package/src/discovery/peer-selector.ts +0 -134
  42. package/src/discovery/profile-manager.ts +0 -131
  43. package/src/discovery/profile-search.ts +0 -100
  44. package/src/discovery/reputation-verifier.ts +0 -54
  45. package/src/index.ts +0 -61
  46. package/src/interfaces/buyer-router.ts +0 -21
  47. package/src/interfaces/plugin.ts +0 -36
  48. package/src/interfaces/seller-provider.ts +0 -81
  49. package/src/metering/index.ts +0 -6
  50. package/src/metering/receipt-generator.ts +0 -105
  51. package/src/metering/receipt-verifier.ts +0 -102
  52. package/src/metering/session-tracker.ts +0 -145
  53. package/src/metering/storage.ts +0 -600
  54. package/src/metering/token-counter.ts +0 -127
  55. package/src/metering/usage-aggregator.ts +0 -236
  56. package/src/node.ts +0 -1698
  57. package/src/p2p/connection-auth.ts +0 -152
  58. package/src/p2p/connection-manager.ts +0 -916
  59. package/src/p2p/handshake.ts +0 -162
  60. package/src/p2p/ice-config.ts +0 -59
  61. package/src/p2p/identity.ts +0 -110
  62. package/src/p2p/index.ts +0 -11
  63. package/src/p2p/keepalive.ts +0 -118
  64. package/src/p2p/message-protocol.ts +0 -171
  65. package/src/p2p/nat-traversal.ts +0 -169
  66. package/src/p2p/payment-codec.ts +0 -165
  67. package/src/p2p/payment-mux.ts +0 -153
  68. package/src/p2p/reconnect.ts +0 -117
  69. package/src/payments/balance-manager.ts +0 -77
  70. package/src/payments/buyer-payment-manager.ts +0 -414
  71. package/src/payments/disputes.ts +0 -72
  72. package/src/payments/evm/escrow-client.ts +0 -263
  73. package/src/payments/evm/keypair.ts +0 -31
  74. package/src/payments/evm/signatures.ts +0 -103
  75. package/src/payments/evm/wallet.ts +0 -42
  76. package/src/payments/index.ts +0 -50
  77. package/src/payments/settlement.ts +0 -40
  78. package/src/payments/types.ts +0 -79
  79. package/src/proxy/index.ts +0 -3
  80. package/src/proxy/provider-detection.ts +0 -78
  81. package/src/proxy/proxy-mux.ts +0 -173
  82. package/src/proxy/request-codec.ts +0 -294
  83. package/src/reputation/index.ts +0 -6
  84. package/src/reputation/rating-manager.ts +0 -118
  85. package/src/reputation/report-manager.ts +0 -91
  86. package/src/reputation/trust-engine.ts +0 -120
  87. package/src/reputation/trust-score.ts +0 -74
  88. package/src/reputation/uptime-tracker.ts +0 -155
  89. package/src/routing/default-router.ts +0 -75
  90. package/src/types/bittorrent-dht.d.ts +0 -19
  91. package/src/types/buyer.ts +0 -37
  92. package/src/types/capability.ts +0 -34
  93. package/src/types/connection.ts +0 -29
  94. package/src/types/http.ts +0 -20
  95. package/src/types/index.ts +0 -14
  96. package/src/types/metering.ts +0 -175
  97. package/src/types/nat-api.d.ts +0 -29
  98. package/src/types/peer-profile.ts +0 -25
  99. package/src/types/peer.ts +0 -62
  100. package/src/types/plugin-config.ts +0 -31
  101. package/src/types/protocol.ts +0 -162
  102. package/src/types/provider.ts +0 -40
  103. package/src/types/rating.ts +0 -23
  104. package/src/types/report.ts +0 -30
  105. package/src/types/seller.ts +0 -38
  106. package/src/types/staking.ts +0 -23
  107. package/src/utils/debug.ts +0 -30
  108. package/src/utils/hex.ts +0 -14
  109. package/tests/balance-manager.test.ts +0 -156
  110. package/tests/bootstrap.test.ts +0 -108
  111. package/tests/buyer-payment-manager.test.ts +0 -358
  112. package/tests/connection-auth.test.ts +0 -87
  113. package/tests/default-router.test.ts +0 -148
  114. package/tests/evm-keypair.test.ts +0 -173
  115. package/tests/identity.test.ts +0 -133
  116. package/tests/message-protocol.test.ts +0 -212
  117. package/tests/metadata-codec.test.ts +0 -165
  118. package/tests/metadata-validator.test.ts +0 -261
  119. package/tests/metering-storage.test.ts +0 -244
  120. package/tests/payment-codec.test.ts +0 -95
  121. package/tests/payment-mux.test.ts +0 -191
  122. package/tests/peer-selector.test.ts +0 -184
  123. package/tests/provider-detection.test.ts +0 -107
  124. package/tests/proxy-mux-security.test.ts +0 -38
  125. package/tests/receipt.test.ts +0 -215
  126. package/tests/reputation-integration.test.ts +0 -195
  127. package/tests/request-codec.test.ts +0 -144
  128. package/tests/token-counter.test.ts +0 -122
  129. package/tsconfig.json +0 -9
  130. 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
- }