@secondlayer/sdk 6.9.1 → 6.10.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.d.ts +67 -1
- package/dist/index.js +173 -12
- package/dist/index.js.map +9 -8
- package/dist/streams/index.d.ts +36 -1
- package/dist/streams/index.js +166 -12
- package/dist/streams/index.js.map +7 -6
- package/dist/subgraphs/index.d.ts +30 -0
- package/dist/subgraphs/index.js +166 -12
- package/dist/subgraphs/index.js.map +7 -6
- package/package.json +3 -3
package/dist/subgraphs/index.js
CHANGED
|
@@ -891,7 +891,7 @@ class Index extends BaseClient {
|
|
|
891
891
|
}
|
|
892
892
|
|
|
893
893
|
// src/streams/client.ts
|
|
894
|
-
import { ed25519 } from "@secondlayer/shared";
|
|
894
|
+
import { ed25519 as ed255192 } from "@secondlayer/shared";
|
|
895
895
|
|
|
896
896
|
// src/streams/errors.ts
|
|
897
897
|
class AuthError extends Error {
|
|
@@ -1078,6 +1078,7 @@ async function* streamStreamsEvents(opts) {
|
|
|
1078
1078
|
|
|
1079
1079
|
// src/streams/dumps.ts
|
|
1080
1080
|
import { createHash } from "node:crypto";
|
|
1081
|
+
import { verifyStreamsBulkManifestSignature } from "@secondlayer/shared/streams-bulk-manifest";
|
|
1081
1082
|
function createStreamsDumps(opts) {
|
|
1082
1083
|
const baseUrl = opts.baseUrl?.replace(/\/+$/, "");
|
|
1083
1084
|
function requireBaseUrl() {
|
|
@@ -1095,7 +1096,17 @@ function createStreamsDumps(opts) {
|
|
|
1095
1096
|
if (!res.ok) {
|
|
1096
1097
|
throw new StreamsServerError(`Could not fetch dumps manifest (${res.status}).`, res.status);
|
|
1097
1098
|
}
|
|
1098
|
-
|
|
1099
|
+
const manifest = await res.json();
|
|
1100
|
+
if (opts.verifyManifest) {
|
|
1101
|
+
if (!opts.loadPublicKeyPem) {
|
|
1102
|
+
throw new StreamsSignatureError("Manifest verification is on but no signing key source is configured.");
|
|
1103
|
+
}
|
|
1104
|
+
const publicKeyPem = await opts.loadPublicKeyPem();
|
|
1105
|
+
if (!verifyStreamsBulkManifestSignature(manifest, publicKeyPem)) {
|
|
1106
|
+
throw new StreamsSignatureError("Dumps manifest signature is missing or invalid.");
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
return manifest;
|
|
1099
1110
|
}
|
|
1100
1111
|
async function download(file) {
|
|
1101
1112
|
const res = await opts.fetchImpl(fileUrl(file));
|
|
@@ -1112,6 +1123,135 @@ function createStreamsDumps(opts) {
|
|
|
1112
1123
|
return { list, fileUrl, download };
|
|
1113
1124
|
}
|
|
1114
1125
|
|
|
1126
|
+
// src/streams/subscribe.ts
|
|
1127
|
+
import { ed25519 } from "@secondlayer/shared";
|
|
1128
|
+
function subscribeStreamsEvents(opts) {
|
|
1129
|
+
const { params } = opts;
|
|
1130
|
+
const controller = new AbortController;
|
|
1131
|
+
const external = params.signal;
|
|
1132
|
+
if (external) {
|
|
1133
|
+
if (external.aborted)
|
|
1134
|
+
controller.abort();
|
|
1135
|
+
else
|
|
1136
|
+
external.addEventListener("abort", () => controller.abort(), {
|
|
1137
|
+
once: true
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
let cursor = params.fromCursor ?? null;
|
|
1141
|
+
const reconnectDelayMs = opts.reconnectDelayMs ?? 1000;
|
|
1142
|
+
const run = async () => {
|
|
1143
|
+
while (!controller.signal.aborted) {
|
|
1144
|
+
try {
|
|
1145
|
+
const url = `${opts.baseUrl}/v1/streams/events/stream${buildQuery({
|
|
1146
|
+
from_cursor: cursor ?? undefined,
|
|
1147
|
+
types: params.types,
|
|
1148
|
+
not_types: params.notTypes,
|
|
1149
|
+
contract_id: params.contractId,
|
|
1150
|
+
sender: params.sender,
|
|
1151
|
+
recipient: params.recipient,
|
|
1152
|
+
asset_identifier: params.assetIdentifier
|
|
1153
|
+
})}`;
|
|
1154
|
+
const res = await opts.fetchImpl(url, {
|
|
1155
|
+
headers: {
|
|
1156
|
+
Authorization: `Bearer ${opts.apiKey}`,
|
|
1157
|
+
Accept: "text/event-stream"
|
|
1158
|
+
},
|
|
1159
|
+
signal: controller.signal
|
|
1160
|
+
});
|
|
1161
|
+
if (!res.ok) {
|
|
1162
|
+
throw new StreamsServerError(`Streams SSE returned ${res.status}.`, res.status);
|
|
1163
|
+
}
|
|
1164
|
+
if (!res.body) {
|
|
1165
|
+
throw new StreamsServerError("Streams SSE response has no body.", 0);
|
|
1166
|
+
}
|
|
1167
|
+
for await (const frame of parseSseFrames(res.body, controller.signal)) {
|
|
1168
|
+
if (frame.event === "ping" || !frame.data)
|
|
1169
|
+
continue;
|
|
1170
|
+
let parsed;
|
|
1171
|
+
try {
|
|
1172
|
+
parsed = JSON.parse(frame.data);
|
|
1173
|
+
} catch {
|
|
1174
|
+
continue;
|
|
1175
|
+
}
|
|
1176
|
+
if (!parsed.event)
|
|
1177
|
+
continue;
|
|
1178
|
+
if (opts.verify) {
|
|
1179
|
+
const key = await opts.loadKey();
|
|
1180
|
+
if (!parsed.sig || !ed25519.verifyEd25519(JSON.stringify(parsed.event), parsed.sig, key.publicKey)) {
|
|
1181
|
+
throw new StreamsSignatureError("Streams SSE frame signature is missing or invalid.");
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
cursor = parsed.event.cursor ?? cursor;
|
|
1185
|
+
await params.onEvent(parsed.event);
|
|
1186
|
+
}
|
|
1187
|
+
} catch (err) {
|
|
1188
|
+
if (controller.signal.aborted)
|
|
1189
|
+
return;
|
|
1190
|
+
params.onError?.(err);
|
|
1191
|
+
await sleep(reconnectDelayMs, controller.signal);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
};
|
|
1195
|
+
run();
|
|
1196
|
+
return () => controller.abort();
|
|
1197
|
+
}
|
|
1198
|
+
function sleep(ms, signal) {
|
|
1199
|
+
return new Promise((resolve) => {
|
|
1200
|
+
if (signal.aborted)
|
|
1201
|
+
return resolve();
|
|
1202
|
+
const onAbort = () => {
|
|
1203
|
+
clearTimeout(timer);
|
|
1204
|
+
resolve();
|
|
1205
|
+
};
|
|
1206
|
+
const timer = setTimeout(() => {
|
|
1207
|
+
signal.removeEventListener("abort", onAbort);
|
|
1208
|
+
resolve();
|
|
1209
|
+
}, ms);
|
|
1210
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
async function* parseSseFrames(body, signal) {
|
|
1214
|
+
const reader = body.getReader();
|
|
1215
|
+
const decoder = new TextDecoder;
|
|
1216
|
+
let buffer = "";
|
|
1217
|
+
try {
|
|
1218
|
+
while (!signal.aborted) {
|
|
1219
|
+
const { value, done } = await reader.read();
|
|
1220
|
+
if (done)
|
|
1221
|
+
break;
|
|
1222
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1223
|
+
let sep = buffer.indexOf(`
|
|
1224
|
+
|
|
1225
|
+
`);
|
|
1226
|
+
while (sep !== -1) {
|
|
1227
|
+
yield parseFrame(buffer.slice(0, sep));
|
|
1228
|
+
buffer = buffer.slice(sep + 2);
|
|
1229
|
+
sep = buffer.indexOf(`
|
|
1230
|
+
|
|
1231
|
+
`);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
} finally {
|
|
1235
|
+
try {
|
|
1236
|
+
await reader.cancel();
|
|
1237
|
+
} catch {}
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
function parseFrame(raw) {
|
|
1241
|
+
let event;
|
|
1242
|
+
const data = [];
|
|
1243
|
+
for (const line of raw.split(`
|
|
1244
|
+
`)) {
|
|
1245
|
+
if (line.startsWith("data:")) {
|
|
1246
|
+
data.push(line.slice(line.startsWith("data: ") ? 6 : 5));
|
|
1247
|
+
} else if (line.startsWith("event:")) {
|
|
1248
|
+
event = line.slice(line.startsWith("event: ") ? 7 : 6).trim();
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
return { event, data: data.length > 0 ? data.join(`
|
|
1252
|
+
`) : undefined };
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1115
1255
|
// src/streams/client.ts
|
|
1116
1256
|
function cursorTuple(cursor) {
|
|
1117
1257
|
if (!cursor)
|
|
@@ -1171,10 +1311,6 @@ function createStreamsClient(options) {
|
|
|
1171
1311
|
const baseUrl = normalizeBaseUrl(options.baseUrl ?? DEFAULT_STREAMS_BASE_URL);
|
|
1172
1312
|
const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
|
|
1173
1313
|
const verify = options.verify ?? false;
|
|
1174
|
-
const dumps = createStreamsDumps({
|
|
1175
|
-
baseUrl: options.dumpsBaseUrl,
|
|
1176
|
-
fetchImpl
|
|
1177
|
-
});
|
|
1178
1314
|
let keyPromise = null;
|
|
1179
1315
|
function loadKey() {
|
|
1180
1316
|
if (keyPromise)
|
|
@@ -1182,8 +1318,9 @@ function createStreamsClient(options) {
|
|
|
1182
1318
|
keyPromise = (async () => {
|
|
1183
1319
|
if (typeof verify === "object") {
|
|
1184
1320
|
return {
|
|
1185
|
-
keyId:
|
|
1186
|
-
|
|
1321
|
+
keyId: ed255192.ed25519KeyId(verify.publicKey),
|
|
1322
|
+
publicKeyPem: verify.publicKey,
|
|
1323
|
+
publicKey: ed255192.loadEd25519PublicKey(verify.publicKey)
|
|
1187
1324
|
};
|
|
1188
1325
|
}
|
|
1189
1326
|
const res = await fetchImpl(`${baseUrl}/public/streams/signing-key`);
|
|
@@ -1195,12 +1332,19 @@ function createStreamsClient(options) {
|
|
|
1195
1332
|
throw new StreamsSignatureError("Signing key response missing key.");
|
|
1196
1333
|
}
|
|
1197
1334
|
return {
|
|
1198
|
-
keyId: body.key_id ??
|
|
1199
|
-
|
|
1335
|
+
keyId: body.key_id ?? ed255192.ed25519KeyId(body.public_key_pem),
|
|
1336
|
+
publicKeyPem: body.public_key_pem,
|
|
1337
|
+
publicKey: ed255192.loadEd25519PublicKey(body.public_key_pem)
|
|
1200
1338
|
};
|
|
1201
1339
|
})();
|
|
1202
1340
|
return keyPromise;
|
|
1203
1341
|
}
|
|
1342
|
+
const dumps = createStreamsDumps({
|
|
1343
|
+
baseUrl: options.dumpsBaseUrl,
|
|
1344
|
+
fetchImpl,
|
|
1345
|
+
verifyManifest: options.verifyDumpsManifest ?? false,
|
|
1346
|
+
loadPublicKeyPem: async () => (await loadKey()).publicKeyPem
|
|
1347
|
+
});
|
|
1204
1348
|
async function request(path) {
|
|
1205
1349
|
const response = await fetchImpl(`${baseUrl}${path}`, {
|
|
1206
1350
|
headers: { Authorization: `Bearer ${options.apiKey}` }
|
|
@@ -1225,7 +1369,7 @@ function createStreamsClient(options) {
|
|
|
1225
1369
|
throw new StreamsSignatureError(`Response signed with key '${responseKeyId}' not served by the signing-key endpoint.`);
|
|
1226
1370
|
}
|
|
1227
1371
|
}
|
|
1228
|
-
if (!
|
|
1372
|
+
if (!ed255192.verifyEd25519(text, signature, key.publicKey)) {
|
|
1229
1373
|
throw new StreamsSignatureError;
|
|
1230
1374
|
}
|
|
1231
1375
|
}
|
|
@@ -1310,6 +1454,16 @@ function createStreamsClient(options) {
|
|
|
1310
1454
|
fetchEvents
|
|
1311
1455
|
});
|
|
1312
1456
|
},
|
|
1457
|
+
subscribe(params) {
|
|
1458
|
+
return subscribeStreamsEvents({
|
|
1459
|
+
baseUrl,
|
|
1460
|
+
apiKey: options.apiKey,
|
|
1461
|
+
fetchImpl,
|
|
1462
|
+
verify: Boolean(verify),
|
|
1463
|
+
loadKey,
|
|
1464
|
+
params
|
|
1465
|
+
});
|
|
1466
|
+
},
|
|
1313
1467
|
async replay(params) {
|
|
1314
1468
|
const fromCursor = params.from === "genesis" ? null : params.from ?? null;
|
|
1315
1469
|
const fromBlock = fromCursor ? cursorTuple(fromCursor)[0] : 0;
|
|
@@ -1484,5 +1638,5 @@ export {
|
|
|
1484
1638
|
Subgraphs
|
|
1485
1639
|
};
|
|
1486
1640
|
|
|
1487
|
-
//# debugId=
|
|
1641
|
+
//# debugId=B6D6E26DCF0C5E6064756E2164756E21
|
|
1488
1642
|
//# sourceMappingURL=index.js.map
|