@k256/sdk 0.2.0 → 0.3.0
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/index.cjs +505 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +500 -5
- package/dist/index.js.map +1 -1
- package/dist/leader-ws/index.cjs +527 -0
- package/dist/leader-ws/index.cjs.map +1 -0
- package/dist/leader-ws/index.d.cts +337 -0
- package/dist/leader-ws/index.d.ts +337 -0
- package/dist/leader-ws/index.js +520 -0
- package/dist/leader-ws/index.js.map +1 -0
- package/dist/ws/index.cjs +5 -4
- package/dist/ws/index.cjs.map +1 -1
- package/dist/ws/index.d.cts +1 -1
- package/dist/ws/index.d.ts +1 -1
- package/dist/ws/index.js +5 -4
- package/dist/ws/index.js.map +1 -1
- package/package.json +11 -1
- package/src/index.ts +32 -1
- package/src/leader-ws/client.ts +377 -0
- package/src/leader-ws/decoder.ts +314 -0
- package/src/leader-ws/index.ts +58 -0
- package/src/leader-ws/types.ts +212 -0
- package/src/ws/client.ts +2 -2
- package/src/ws/decoder.ts +7 -3
package/dist/index.js
CHANGED
|
@@ -186,10 +186,10 @@ function decodeMessage(data) {
|
|
|
186
186
|
});
|
|
187
187
|
offset += 92;
|
|
188
188
|
}
|
|
189
|
-
const recentBlocksCount = Number(payloadView.getBigUint64(offset, true));
|
|
189
|
+
const recentBlocksCount = offset + 8 <= payload.byteLength ? Number(payloadView.getBigUint64(offset, true)) : 0;
|
|
190
190
|
offset += 8;
|
|
191
191
|
const recentBlocks = [];
|
|
192
|
-
for (let i = 0; i < recentBlocksCount; i++) {
|
|
192
|
+
for (let i = 0; i < recentBlocksCount && offset + 32 <= payload.byteLength; i++) {
|
|
193
193
|
const rbSlot = Number(payloadView.getBigUint64(offset, true));
|
|
194
194
|
offset += 8;
|
|
195
195
|
const rbCuConsumed = Number(payloadView.getBigUint64(offset, true));
|
|
@@ -202,7 +202,7 @@ function decodeMessage(data) {
|
|
|
202
202
|
offset += 8;
|
|
203
203
|
recentBlocks.push({ slot: rbSlot, cuConsumed: rbCuConsumed, txCount: rbTxCount, utilizationPct: rbUtilizationPct, avgCuPrice: rbAvgCuPrice });
|
|
204
204
|
}
|
|
205
|
-
const trendByte = payloadView.getUint8(offset);
|
|
205
|
+
const trendByte = offset < payload.byteLength ? payloadView.getUint8(offset) : 2;
|
|
206
206
|
offset += 1;
|
|
207
207
|
const trend = trendByte === 0 ? "rising" : trendByte === 1 ? "falling" : "stable";
|
|
208
208
|
return {
|
|
@@ -560,7 +560,8 @@ var K256WebSocketClient = class {
|
|
|
560
560
|
maxReconnectAttempts: Infinity,
|
|
561
561
|
pingIntervalMs: 3e4,
|
|
562
562
|
pongTimeoutMs: 1e4,
|
|
563
|
-
heartbeatTimeoutMs:
|
|
563
|
+
heartbeatTimeoutMs: 45e3,
|
|
564
|
+
// K2 sends heartbeats every 30s; allow 45s before warning
|
|
564
565
|
...config
|
|
565
566
|
};
|
|
566
567
|
}
|
|
@@ -976,6 +977,500 @@ var K256WebSocketClient = class {
|
|
|
976
977
|
}
|
|
977
978
|
};
|
|
978
979
|
|
|
980
|
+
// src/leader-ws/decoder.ts
|
|
981
|
+
var LeaderMessageTag = {
|
|
982
|
+
Subscribe: 1,
|
|
983
|
+
Subscribed: 2,
|
|
984
|
+
LeaderSchedule: 16,
|
|
985
|
+
GossipSnapshot: 17,
|
|
986
|
+
GossipDiff: 18,
|
|
987
|
+
SlotUpdate: 19,
|
|
988
|
+
RoutingHealth: 20,
|
|
989
|
+
SkipEvent: 21,
|
|
990
|
+
IpChange: 22,
|
|
991
|
+
Heartbeat: 253,
|
|
992
|
+
Ping: 254,
|
|
993
|
+
Error: 255
|
|
994
|
+
};
|
|
995
|
+
function readU64(view, o) {
|
|
996
|
+
const val = Number(view.getBigUint64(o.v, true));
|
|
997
|
+
o.v += 8;
|
|
998
|
+
return val;
|
|
999
|
+
}
|
|
1000
|
+
function readU32(view, o) {
|
|
1001
|
+
const val = view.getUint32(o.v, true);
|
|
1002
|
+
o.v += 4;
|
|
1003
|
+
return val;
|
|
1004
|
+
}
|
|
1005
|
+
function readU16(view, o) {
|
|
1006
|
+
const val = view.getUint16(o.v, true);
|
|
1007
|
+
o.v += 2;
|
|
1008
|
+
return val;
|
|
1009
|
+
}
|
|
1010
|
+
function readU8(view, o) {
|
|
1011
|
+
const val = view.getUint8(o.v);
|
|
1012
|
+
o.v += 1;
|
|
1013
|
+
return val;
|
|
1014
|
+
}
|
|
1015
|
+
function readBool(view, o) {
|
|
1016
|
+
return readU8(view, o) !== 0;
|
|
1017
|
+
}
|
|
1018
|
+
function readPubkey(data, o) {
|
|
1019
|
+
const bytes = new Uint8Array(data, o.v, 32);
|
|
1020
|
+
o.v += 32;
|
|
1021
|
+
return base58Encode(bytes);
|
|
1022
|
+
}
|
|
1023
|
+
function readVecU8AsString(view, data, o) {
|
|
1024
|
+
const len = readU64(view, o);
|
|
1025
|
+
const bytes = new Uint8Array(data, o.v, len);
|
|
1026
|
+
o.v += len;
|
|
1027
|
+
return new TextDecoder().decode(bytes);
|
|
1028
|
+
}
|
|
1029
|
+
function readOptSocketAddr(view, o) {
|
|
1030
|
+
const tag = readU8(view, o);
|
|
1031
|
+
if (tag === 0) return null;
|
|
1032
|
+
const ipBytes = new Uint8Array(view.buffer, view.byteOffset + o.v, 16);
|
|
1033
|
+
o.v += 16;
|
|
1034
|
+
const port = readU16(view, o);
|
|
1035
|
+
const isIpv4 = readBool(view, o);
|
|
1036
|
+
if (isIpv4) {
|
|
1037
|
+
return `${ipBytes[12]}.${ipBytes[13]}.${ipBytes[14]}.${ipBytes[15]}:${port}`;
|
|
1038
|
+
}
|
|
1039
|
+
return `[ipv6]:${port}`;
|
|
1040
|
+
}
|
|
1041
|
+
function readPubkeyVec(view, data, o) {
|
|
1042
|
+
const count = readU64(view, o);
|
|
1043
|
+
const keys = [];
|
|
1044
|
+
for (let i = 0; i < count; i++) {
|
|
1045
|
+
keys.push(readPubkey(data, o));
|
|
1046
|
+
}
|
|
1047
|
+
return keys;
|
|
1048
|
+
}
|
|
1049
|
+
function readGossipPeer(view, data, o) {
|
|
1050
|
+
return {
|
|
1051
|
+
identity: readPubkey(data, o),
|
|
1052
|
+
tpuQuic: readOptSocketAddr(view, o),
|
|
1053
|
+
tpuUdp: readOptSocketAddr(view, o),
|
|
1054
|
+
tpuForwardsQuic: readOptSocketAddr(view, o),
|
|
1055
|
+
tpuForwardsUdp: readOptSocketAddr(view, o),
|
|
1056
|
+
tpuVote: readOptSocketAddr(view, o),
|
|
1057
|
+
tpuVoteQuic: readOptSocketAddr(view, o),
|
|
1058
|
+
gossipAddr: readOptSocketAddr(view, o),
|
|
1059
|
+
shredVersion: readU16(view, o),
|
|
1060
|
+
version: readVecU8AsString(view, data, o),
|
|
1061
|
+
activatedStake: readU64(view, o),
|
|
1062
|
+
commission: readU8(view, o),
|
|
1063
|
+
isDelinquent: readBool(view, o),
|
|
1064
|
+
votePubkey: readPubkey(data, o),
|
|
1065
|
+
lastVote: readU64(view, o),
|
|
1066
|
+
rootSlot: readU64(view, o),
|
|
1067
|
+
wallclock: readU64(view, o)
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
function readGossipPeerVec(view, data, o) {
|
|
1071
|
+
const count = readU64(view, o);
|
|
1072
|
+
const peers = [];
|
|
1073
|
+
for (let i = 0; i < count; i++) {
|
|
1074
|
+
peers.push(readGossipPeer(view, data, o));
|
|
1075
|
+
}
|
|
1076
|
+
return peers;
|
|
1077
|
+
}
|
|
1078
|
+
function decodeLeaderMessage(data) {
|
|
1079
|
+
const view = new DataView(data);
|
|
1080
|
+
if (data.byteLength < 1) return null;
|
|
1081
|
+
const msgType = view.getUint8(0);
|
|
1082
|
+
const payload = data.slice(1);
|
|
1083
|
+
const pv = new DataView(payload);
|
|
1084
|
+
switch (msgType) {
|
|
1085
|
+
case LeaderMessageTag.Subscribed: {
|
|
1086
|
+
const text = new TextDecoder().decode(payload);
|
|
1087
|
+
try {
|
|
1088
|
+
return { type: "subscribed", data: JSON.parse(text) };
|
|
1089
|
+
} catch {
|
|
1090
|
+
return null;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
case LeaderMessageTag.Error: {
|
|
1094
|
+
return { type: "error", data: { message: new TextDecoder().decode(payload) } };
|
|
1095
|
+
}
|
|
1096
|
+
case LeaderMessageTag.SlotUpdate: {
|
|
1097
|
+
if (payload.byteLength < 48) return null;
|
|
1098
|
+
const o = { v: 0 };
|
|
1099
|
+
return {
|
|
1100
|
+
type: "slot_update",
|
|
1101
|
+
kind: "snapshot",
|
|
1102
|
+
data: {
|
|
1103
|
+
slot: readU64(pv, o),
|
|
1104
|
+
leader: readPubkey(payload, o),
|
|
1105
|
+
blockHeight: readU64(pv, o)
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
case LeaderMessageTag.Heartbeat: {
|
|
1110
|
+
if (payload.byteLength < 24) return null;
|
|
1111
|
+
const o = { v: 0 };
|
|
1112
|
+
return {
|
|
1113
|
+
type: "heartbeat",
|
|
1114
|
+
kind: "snapshot",
|
|
1115
|
+
data: {
|
|
1116
|
+
timestampMs: readU64(pv, o),
|
|
1117
|
+
currentSlot: readU64(pv, o),
|
|
1118
|
+
connectedClients: readU32(pv, o),
|
|
1119
|
+
gossipPeers: readU32(pv, o)
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
case LeaderMessageTag.SkipEvent: {
|
|
1124
|
+
if (payload.byteLength < 48) return null;
|
|
1125
|
+
const o = { v: 0 };
|
|
1126
|
+
return {
|
|
1127
|
+
type: "skip_event",
|
|
1128
|
+
kind: "event",
|
|
1129
|
+
key: "leader",
|
|
1130
|
+
data: {
|
|
1131
|
+
slot: readU64(pv, o),
|
|
1132
|
+
leader: readPubkey(payload, o),
|
|
1133
|
+
assigned: readU32(pv, o),
|
|
1134
|
+
produced: readU32(pv, o)
|
|
1135
|
+
}
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
case LeaderMessageTag.RoutingHealth: {
|
|
1139
|
+
if (payload.byteLength < 8) return null;
|
|
1140
|
+
try {
|
|
1141
|
+
const o = { v: 0 };
|
|
1142
|
+
const leadersTotal = readU32(pv, o);
|
|
1143
|
+
const leadersInGossip = readU32(pv, o);
|
|
1144
|
+
const leadersMissingGossip = readPubkeyVec(pv, payload, o);
|
|
1145
|
+
const leadersWithoutTpuQuic = readPubkeyVec(pv, payload, o);
|
|
1146
|
+
const leadersDelinquent = readPubkeyVec(pv, payload, o);
|
|
1147
|
+
return {
|
|
1148
|
+
type: "routing_health",
|
|
1149
|
+
kind: "snapshot",
|
|
1150
|
+
data: {
|
|
1151
|
+
leadersTotal,
|
|
1152
|
+
leadersInGossip,
|
|
1153
|
+
leadersMissingGossip,
|
|
1154
|
+
leadersWithoutTpuQuic,
|
|
1155
|
+
leadersDelinquent,
|
|
1156
|
+
coverage: `${leadersTotal > 0 ? (leadersInGossip / leadersTotal * 100).toFixed(1) : 0}%`
|
|
1157
|
+
}
|
|
1158
|
+
};
|
|
1159
|
+
} catch {
|
|
1160
|
+
return null;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
case LeaderMessageTag.IpChange: {
|
|
1164
|
+
if (payload.byteLength < 32) return null;
|
|
1165
|
+
try {
|
|
1166
|
+
const o = { v: 0 };
|
|
1167
|
+
const identity = readPubkey(payload, o);
|
|
1168
|
+
const oldIp = readVecU8AsString(pv, payload, o);
|
|
1169
|
+
const newIp = readVecU8AsString(pv, payload, o);
|
|
1170
|
+
const timestampMs = readU64(pv, o);
|
|
1171
|
+
return {
|
|
1172
|
+
type: "ip_change",
|
|
1173
|
+
kind: "event",
|
|
1174
|
+
key: "identity",
|
|
1175
|
+
data: { identity, oldIp, newIp, timestampMs }
|
|
1176
|
+
};
|
|
1177
|
+
} catch {
|
|
1178
|
+
return null;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
case LeaderMessageTag.GossipSnapshot: {
|
|
1182
|
+
if (payload.byteLength < 8) return null;
|
|
1183
|
+
try {
|
|
1184
|
+
const o = { v: 0 };
|
|
1185
|
+
const timestampMs = readU64(pv, o);
|
|
1186
|
+
const peers = readGossipPeerVec(pv, payload, o);
|
|
1187
|
+
return {
|
|
1188
|
+
type: "gossip_snapshot",
|
|
1189
|
+
kind: "snapshot",
|
|
1190
|
+
key: "identity",
|
|
1191
|
+
data: { timestampMs, count: peers.length, peers }
|
|
1192
|
+
};
|
|
1193
|
+
} catch {
|
|
1194
|
+
return null;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
case LeaderMessageTag.GossipDiff: {
|
|
1198
|
+
if (payload.byteLength < 8) return null;
|
|
1199
|
+
try {
|
|
1200
|
+
const o = { v: 0 };
|
|
1201
|
+
const timestampMs = readU64(pv, o);
|
|
1202
|
+
const added = readGossipPeerVec(pv, payload, o);
|
|
1203
|
+
const removed = readPubkeyVec(pv, payload, o);
|
|
1204
|
+
const updated = readGossipPeerVec(pv, payload, o);
|
|
1205
|
+
return {
|
|
1206
|
+
type: "gossip_diff",
|
|
1207
|
+
kind: "diff",
|
|
1208
|
+
key: "identity",
|
|
1209
|
+
data: { timestampMs, added, removed, updated }
|
|
1210
|
+
};
|
|
1211
|
+
} catch {
|
|
1212
|
+
return null;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
case LeaderMessageTag.LeaderSchedule: {
|
|
1216
|
+
if (payload.byteLength < 16) return null;
|
|
1217
|
+
try {
|
|
1218
|
+
const o = { v: 0 };
|
|
1219
|
+
const epoch = readU64(pv, o);
|
|
1220
|
+
const slotsInEpoch = readU64(pv, o);
|
|
1221
|
+
const validatorCount = readU64(pv, o);
|
|
1222
|
+
const schedule = [];
|
|
1223
|
+
for (let i = 0; i < validatorCount; i++) {
|
|
1224
|
+
const identity = readPubkey(payload, o);
|
|
1225
|
+
const slotCount = readU64(pv, o);
|
|
1226
|
+
const slotIndices = [];
|
|
1227
|
+
for (let j = 0; j < slotCount; j++) {
|
|
1228
|
+
slotIndices.push(readU32(pv, o));
|
|
1229
|
+
}
|
|
1230
|
+
schedule.push({ identity, slots: slotIndices.length, slotIndices });
|
|
1231
|
+
}
|
|
1232
|
+
return {
|
|
1233
|
+
type: "leader_schedule",
|
|
1234
|
+
kind: "snapshot",
|
|
1235
|
+
data: { epoch, slotsInEpoch, validators: schedule.length, schedule }
|
|
1236
|
+
};
|
|
1237
|
+
} catch {
|
|
1238
|
+
return null;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
default:
|
|
1242
|
+
return null;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// src/leader-ws/types.ts
|
|
1247
|
+
var LeaderChannel = {
|
|
1248
|
+
/** Full epoch leader schedule (on connect + epoch change) */
|
|
1249
|
+
LeaderSchedule: "leader_schedule",
|
|
1250
|
+
/** Gossip peers (snapshot on connect, then diffs) */
|
|
1251
|
+
Gossip: "gossip",
|
|
1252
|
+
/** Real-time slot updates with current leader */
|
|
1253
|
+
Slots: "slots",
|
|
1254
|
+
/** Skip events, IP changes, routing health */
|
|
1255
|
+
Alerts: "alerts"
|
|
1256
|
+
};
|
|
1257
|
+
var ALL_LEADER_CHANNELS = [
|
|
1258
|
+
LeaderChannel.LeaderSchedule,
|
|
1259
|
+
LeaderChannel.Gossip,
|
|
1260
|
+
LeaderChannel.Slots,
|
|
1261
|
+
LeaderChannel.Alerts
|
|
1262
|
+
];
|
|
1263
|
+
|
|
1264
|
+
// src/leader-ws/client.ts
|
|
1265
|
+
var LeaderWebSocketError = class extends Error {
|
|
1266
|
+
constructor(code, message, closeCode, closeReason) {
|
|
1267
|
+
super(message);
|
|
1268
|
+
this.code = code;
|
|
1269
|
+
this.closeCode = closeCode;
|
|
1270
|
+
this.closeReason = closeReason;
|
|
1271
|
+
this.name = "LeaderWebSocketError";
|
|
1272
|
+
}
|
|
1273
|
+
get isRecoverable() {
|
|
1274
|
+
return this.code !== "AUTH_FAILED";
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
var LeaderWebSocketClient = class {
|
|
1278
|
+
ws = null;
|
|
1279
|
+
config;
|
|
1280
|
+
_state = "disconnected";
|
|
1281
|
+
reconnectAttempts = 0;
|
|
1282
|
+
reconnectTimer = null;
|
|
1283
|
+
isIntentionallyClosed = false;
|
|
1284
|
+
/** Current connection state */
|
|
1285
|
+
get state() {
|
|
1286
|
+
return this._state;
|
|
1287
|
+
}
|
|
1288
|
+
/** Whether currently connected */
|
|
1289
|
+
get isConnected() {
|
|
1290
|
+
return this._state === "connected" && this.ws?.readyState === WebSocket.OPEN;
|
|
1291
|
+
}
|
|
1292
|
+
constructor(config) {
|
|
1293
|
+
this.config = {
|
|
1294
|
+
url: "wss://gateway.k256.xyz/v1/leader-ws",
|
|
1295
|
+
mode: "binary",
|
|
1296
|
+
channels: ALL_LEADER_CHANNELS,
|
|
1297
|
+
autoReconnect: true,
|
|
1298
|
+
reconnectDelayMs: 1e3,
|
|
1299
|
+
maxReconnectDelayMs: 3e4,
|
|
1300
|
+
maxReconnectAttempts: Infinity,
|
|
1301
|
+
...config
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Connect to the leader-schedule WebSocket
|
|
1306
|
+
*/
|
|
1307
|
+
async connect() {
|
|
1308
|
+
if (this._state === "connected" || this._state === "connecting") return;
|
|
1309
|
+
this.isIntentionallyClosed = false;
|
|
1310
|
+
this.setState("connecting");
|
|
1311
|
+
return new Promise((resolve, reject) => {
|
|
1312
|
+
try {
|
|
1313
|
+
const url = `${this.config.url}?apiKey=${encodeURIComponent(this.config.apiKey)}`;
|
|
1314
|
+
this.ws = new WebSocket(url);
|
|
1315
|
+
if (this.config.mode === "binary") {
|
|
1316
|
+
this.ws.binaryType = "arraybuffer";
|
|
1317
|
+
}
|
|
1318
|
+
this.ws.onopen = () => {
|
|
1319
|
+
this.setState("connected");
|
|
1320
|
+
this.reconnectAttempts = 0;
|
|
1321
|
+
if (this.config.mode === "binary") {
|
|
1322
|
+
const payload = JSON.stringify({ channels: this.config.channels });
|
|
1323
|
+
const bytes = new TextEncoder().encode(payload);
|
|
1324
|
+
const msg = new Uint8Array(1 + bytes.length);
|
|
1325
|
+
msg[0] = 1;
|
|
1326
|
+
msg.set(bytes, 1);
|
|
1327
|
+
this.ws.send(msg.buffer);
|
|
1328
|
+
} else {
|
|
1329
|
+
this.ws.send(JSON.stringify({
|
|
1330
|
+
type: "subscribe",
|
|
1331
|
+
channels: this.config.channels,
|
|
1332
|
+
format: "json"
|
|
1333
|
+
}));
|
|
1334
|
+
}
|
|
1335
|
+
this.config.onConnect?.();
|
|
1336
|
+
resolve();
|
|
1337
|
+
};
|
|
1338
|
+
this.ws.onmessage = (event) => {
|
|
1339
|
+
if (this.config.mode === "binary" && event.data instanceof ArrayBuffer) {
|
|
1340
|
+
const decoded = decodeLeaderMessage(event.data);
|
|
1341
|
+
if (decoded) {
|
|
1342
|
+
this.dispatchMessage(decoded);
|
|
1343
|
+
}
|
|
1344
|
+
} else if (typeof event.data === "string") {
|
|
1345
|
+
this.handleJsonMessage(event.data);
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1348
|
+
this.ws.onclose = (event) => {
|
|
1349
|
+
const wasConnected = this._state === "connected";
|
|
1350
|
+
this.ws = null;
|
|
1351
|
+
if (this.isIntentionallyClosed) {
|
|
1352
|
+
this.setState("closed");
|
|
1353
|
+
this.config.onDisconnect?.(event.code, event.reason, event.wasClean);
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
this.config.onDisconnect?.(event.code, event.reason, event.wasClean);
|
|
1357
|
+
if (event.code === 1008 || event.code === 4001 || event.code === 4003) {
|
|
1358
|
+
this.setState("closed");
|
|
1359
|
+
this.config.onError?.(new LeaderWebSocketError(
|
|
1360
|
+
"AUTH_FAILED",
|
|
1361
|
+
`Authentication failed: ${event.reason}`,
|
|
1362
|
+
event.code,
|
|
1363
|
+
event.reason
|
|
1364
|
+
));
|
|
1365
|
+
if (!wasConnected) reject(new LeaderWebSocketError("AUTH_FAILED", event.reason, event.code));
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
if (this.config.autoReconnect && this.reconnectAttempts < this.config.maxReconnectAttempts) {
|
|
1369
|
+
this.scheduleReconnect();
|
|
1370
|
+
} else {
|
|
1371
|
+
this.setState("disconnected");
|
|
1372
|
+
}
|
|
1373
|
+
if (!wasConnected) reject(new LeaderWebSocketError("CONNECTION_FAILED", "WebSocket closed before connect"));
|
|
1374
|
+
};
|
|
1375
|
+
this.ws.onerror = () => {
|
|
1376
|
+
this.config.onError?.(new LeaderWebSocketError("CONNECTION_FAILED", "WebSocket connection error"));
|
|
1377
|
+
};
|
|
1378
|
+
} catch (err) {
|
|
1379
|
+
this.setState("disconnected");
|
|
1380
|
+
reject(err);
|
|
1381
|
+
}
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Disconnect from the WebSocket
|
|
1386
|
+
*/
|
|
1387
|
+
disconnect() {
|
|
1388
|
+
this.isIntentionallyClosed = true;
|
|
1389
|
+
this.clearTimers();
|
|
1390
|
+
if (this.ws) {
|
|
1391
|
+
if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {
|
|
1392
|
+
this.ws.close(1e3, "Client disconnect");
|
|
1393
|
+
}
|
|
1394
|
+
this.ws = null;
|
|
1395
|
+
}
|
|
1396
|
+
this.setState("closed");
|
|
1397
|
+
}
|
|
1398
|
+
// ── Private ──
|
|
1399
|
+
/** Handle JSON text frame (from gateway JSON mode) */
|
|
1400
|
+
handleJsonMessage(raw) {
|
|
1401
|
+
try {
|
|
1402
|
+
const msg = JSON.parse(raw);
|
|
1403
|
+
this.dispatchMessage(msg);
|
|
1404
|
+
} catch {
|
|
1405
|
+
this.config.onError?.(new LeaderWebSocketError("INVALID_MESSAGE", "Failed to parse message"));
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
/** Dispatch a decoded message to typed callbacks */
|
|
1409
|
+
dispatchMessage(msg) {
|
|
1410
|
+
switch (msg.type) {
|
|
1411
|
+
case "subscribed":
|
|
1412
|
+
this.config.onSubscribed?.(msg);
|
|
1413
|
+
break;
|
|
1414
|
+
case "leader_schedule":
|
|
1415
|
+
this.config.onLeaderSchedule?.(msg);
|
|
1416
|
+
break;
|
|
1417
|
+
case "gossip_snapshot":
|
|
1418
|
+
this.config.onGossipSnapshot?.(msg);
|
|
1419
|
+
break;
|
|
1420
|
+
case "gossip_diff":
|
|
1421
|
+
this.config.onGossipDiff?.(msg);
|
|
1422
|
+
break;
|
|
1423
|
+
case "slot_update":
|
|
1424
|
+
this.config.onSlotUpdate?.(msg);
|
|
1425
|
+
break;
|
|
1426
|
+
case "routing_health":
|
|
1427
|
+
this.config.onRoutingHealth?.(msg);
|
|
1428
|
+
break;
|
|
1429
|
+
case "skip_event":
|
|
1430
|
+
this.config.onSkipEvent?.(msg);
|
|
1431
|
+
break;
|
|
1432
|
+
case "ip_change":
|
|
1433
|
+
this.config.onIpChange?.(msg);
|
|
1434
|
+
break;
|
|
1435
|
+
case "heartbeat":
|
|
1436
|
+
this.config.onHeartbeat?.(msg);
|
|
1437
|
+
break;
|
|
1438
|
+
case "error":
|
|
1439
|
+
this.config.onError?.(new LeaderWebSocketError(
|
|
1440
|
+
"SERVER_ERROR",
|
|
1441
|
+
msg.data.message
|
|
1442
|
+
));
|
|
1443
|
+
break;
|
|
1444
|
+
}
|
|
1445
|
+
this.config.onMessage?.(msg);
|
|
1446
|
+
}
|
|
1447
|
+
setState(state) {
|
|
1448
|
+
const prev = this._state;
|
|
1449
|
+
if (prev === state) return;
|
|
1450
|
+
this._state = state;
|
|
1451
|
+
this.config.onStateChange?.(state, prev);
|
|
1452
|
+
}
|
|
1453
|
+
scheduleReconnect() {
|
|
1454
|
+
this.setState("reconnecting");
|
|
1455
|
+
this.reconnectAttempts++;
|
|
1456
|
+
const delay = Math.min(
|
|
1457
|
+
this.config.reconnectDelayMs * Math.pow(2, this.reconnectAttempts - 1),
|
|
1458
|
+
this.config.maxReconnectDelayMs
|
|
1459
|
+
);
|
|
1460
|
+
this.config.onReconnecting?.(this.reconnectAttempts, delay);
|
|
1461
|
+
this.reconnectTimer = setTimeout(() => {
|
|
1462
|
+
this.connect().catch(() => {
|
|
1463
|
+
});
|
|
1464
|
+
}, delay);
|
|
1465
|
+
}
|
|
1466
|
+
clearTimers() {
|
|
1467
|
+
if (this.reconnectTimer) {
|
|
1468
|
+
clearTimeout(this.reconnectTimer);
|
|
1469
|
+
this.reconnectTimer = null;
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
};
|
|
1473
|
+
|
|
979
1474
|
// src/types/index.ts
|
|
980
1475
|
var NetworkState = /* @__PURE__ */ ((NetworkState2) => {
|
|
981
1476
|
NetworkState2[NetworkState2["Low"] = 0] = "Low";
|
|
@@ -985,6 +1480,6 @@ var NetworkState = /* @__PURE__ */ ((NetworkState2) => {
|
|
|
985
1480
|
return NetworkState2;
|
|
986
1481
|
})(NetworkState || {});
|
|
987
1482
|
|
|
988
|
-
export { CloseCode, K256WebSocketClient, K256WebSocketError, MessageType, NetworkState, base58Decode, base58Encode, decodeMessage, decodePoolUpdateBatch, isValidPubkey };
|
|
1483
|
+
export { ALL_LEADER_CHANNELS, CloseCode, K256WebSocketClient, K256WebSocketError, LeaderChannel, LeaderMessageTag, LeaderWebSocketClient, LeaderWebSocketError, MessageType, NetworkState, base58Decode, base58Encode, decodeLeaderMessage, decodeMessage, decodePoolUpdateBatch, isValidPubkey };
|
|
989
1484
|
//# sourceMappingURL=index.js.map
|
|
990
1485
|
//# sourceMappingURL=index.js.map
|