@inversealtruism/cairn-sdk 0.6.0 → 0.8.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/README.md +13 -17
- package/dist/{chunk-K5MGEYEU.js → chunk-CI7YODPP.js} +0 -12
- package/dist/{chunk-QEYBWBXC.js → chunk-DTPADO7N.js} +17 -27
- package/dist/{chunk-MLNW2RQB.js → chunk-JXBO4LV6.js} +1 -1
- package/dist/{chunk-2A4AJBO5.js → chunk-UAPWZZQK.js} +1 -1
- package/dist/{chunk-BMH2MACB.js → chunk-WWO3FXHG.js} +1 -1
- package/dist/connect.cjs +1 -1
- package/dist/connect.js +2 -2
- package/dist/content.cjs +17 -27
- package/dist/content.d.cts +7 -13
- package/dist/content.d.ts +7 -13
- package/dist/content.js +2 -2
- package/dist/controller.cjs +1 -1
- package/dist/controller.js +3 -3
- package/dist/index.cjs +47 -115
- package/dist/index.d.cts +20 -20
- package/dist/index.d.ts +20 -20
- package/dist/index.js +204 -36
- package/dist/indexer.cjs +0 -12
- package/dist/indexer.d.cts +0 -4
- package/dist/indexer.d.ts +0 -4
- package/dist/indexer.js +1 -1
- package/dist/react.cjs +1 -1
- package/dist/react.js +3 -3
- package/package.json +22 -25
- package/dist/chunk-67QBDZPW.js +0 -254
- package/dist/registry.cjs +0 -326
- package/dist/registry.d.cts +0 -44
- package/dist/registry.d.ts +0 -44
- package/dist/registry.js +0 -27
package/dist/chunk-67QBDZPW.js
DELETED
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
HttpError
|
|
3
|
-
} from "./chunk-2A4AJBO5.js";
|
|
4
|
-
|
|
5
|
-
// src/registry.ts
|
|
6
|
-
import { fromRecords } from "@inversealtruism/csd-registry";
|
|
7
|
-
|
|
8
|
-
// src/http.ts
|
|
9
|
-
var DEFAULT_MAX_BYTES = 16 * 1024 * 1024;
|
|
10
|
-
var ERR_TEXT_CAP = 8 * 1024;
|
|
11
|
-
var MAX_RETRY_AFTER_MS = 6e4;
|
|
12
|
-
function retryAfterMs(res) {
|
|
13
|
-
const secs = Number(res.headers.get("retry-after"));
|
|
14
|
-
return Number.isFinite(secs) && secs > 0 ? Math.min(secs * 1e3, MAX_RETRY_AFTER_MS) : 0;
|
|
15
|
-
}
|
|
16
|
-
var RetryWaitBudget = class {
|
|
17
|
-
left;
|
|
18
|
-
constructor(totalMs) {
|
|
19
|
-
this.left = totalMs;
|
|
20
|
-
}
|
|
21
|
-
/** The ms to sleep before the next attempt (`ownMs` is the caller's backoff, clipped to what is left), or
|
|
22
|
-
* null when the server's hint exceeds the remaining budget. */
|
|
23
|
-
take(ownMs, serverMs) {
|
|
24
|
-
if (serverMs > this.left) return null;
|
|
25
|
-
const ms = Math.min(Math.max(ownMs, serverMs), this.left);
|
|
26
|
-
this.left -= ms;
|
|
27
|
-
return ms;
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
var HTTP_RETRY_WAIT_BUDGET_MS = 3e4;
|
|
31
|
-
async function readCapped(res, maxBytes) {
|
|
32
|
-
const cl = Number(res.headers.get("content-length"));
|
|
33
|
-
if (Number.isFinite(cl) && cl > maxBytes) throw new HttpError(res.status, res.url, `response too large (content-length ${cl} > ${maxBytes})`);
|
|
34
|
-
const body = res.body;
|
|
35
|
-
if (!body || typeof body.getReader !== "function") {
|
|
36
|
-
const buf = new Uint8Array(await res.arrayBuffer());
|
|
37
|
-
if (buf.byteLength > maxBytes) throw new HttpError(res.status, res.url, `response too large (${buf.byteLength} > ${maxBytes})`);
|
|
38
|
-
return buf;
|
|
39
|
-
}
|
|
40
|
-
const reader = body.getReader();
|
|
41
|
-
const chunks = [];
|
|
42
|
-
let total = 0;
|
|
43
|
-
for (; ; ) {
|
|
44
|
-
const { done, value } = await reader.read();
|
|
45
|
-
if (done) break;
|
|
46
|
-
if (value) {
|
|
47
|
-
total += value.byteLength;
|
|
48
|
-
if (total > maxBytes) {
|
|
49
|
-
try {
|
|
50
|
-
await reader.cancel();
|
|
51
|
-
} catch {
|
|
52
|
-
}
|
|
53
|
-
throw new HttpError(res.status, res.url, `response exceeded ${maxBytes} bytes`);
|
|
54
|
-
}
|
|
55
|
-
chunks.push(value);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
const out = new Uint8Array(total);
|
|
59
|
-
let off = 0;
|
|
60
|
-
for (const c of chunks) {
|
|
61
|
-
out.set(c, off);
|
|
62
|
-
off += c.byteLength;
|
|
63
|
-
}
|
|
64
|
-
return out;
|
|
65
|
-
}
|
|
66
|
-
var Http = class {
|
|
67
|
-
baseUrl;
|
|
68
|
-
_fetch;
|
|
69
|
-
timeoutMs;
|
|
70
|
-
maxBytes;
|
|
71
|
-
headers;
|
|
72
|
-
retries;
|
|
73
|
-
constructor(opts) {
|
|
74
|
-
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
75
|
-
const f = opts.fetch ?? globalThis.fetch;
|
|
76
|
-
if (!f) throw new Error("No fetch available \u2014 pass `fetch` in the SDK options.");
|
|
77
|
-
this._fetch = opts.fetch ? opts.fetch : f.bind(globalThis);
|
|
78
|
-
this.timeoutMs = opts.timeoutMs ?? 15e3;
|
|
79
|
-
this.maxBytes = Number.isFinite(opts.maxBytes) && opts.maxBytes >= 1 ? Math.floor(opts.maxBytes) : DEFAULT_MAX_BYTES;
|
|
80
|
-
this.headers = opts.headers ?? {};
|
|
81
|
-
const retries = Number(opts.retries ?? 0);
|
|
82
|
-
this.retries = Number.isNaN(retries) ? 0 : Math.min(10, Math.max(0, Math.floor(retries)));
|
|
83
|
-
}
|
|
84
|
-
/** Build an absolute URL from a path + optional query params. */
|
|
85
|
-
url(path, query) {
|
|
86
|
-
const p = path.startsWith("/") ? path : `/${path}`;
|
|
87
|
-
const u = new URL(this.baseUrl + p);
|
|
88
|
-
const basePath = new URL(this.baseUrl + "/").pathname;
|
|
89
|
-
if (basePath !== "/" && !(u.pathname + "/").startsWith(basePath)) {
|
|
90
|
-
throw new Error(`illegal path '${path}' \u2014 escapes base ${basePath}`);
|
|
91
|
-
}
|
|
92
|
-
if (query) {
|
|
93
|
-
for (const [k, v] of Object.entries(query)) {
|
|
94
|
-
if (v !== void 0 && v !== null) u.searchParams.set(k, String(v));
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return u.toString();
|
|
98
|
-
}
|
|
99
|
-
async raw(method, path, opts) {
|
|
100
|
-
const url = this.url(path, opts?.query);
|
|
101
|
-
const ctrl = new AbortController();
|
|
102
|
-
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
103
|
-
try {
|
|
104
|
-
const res = await this._fetch(url, {
|
|
105
|
-
method,
|
|
106
|
-
signal: ctrl.signal,
|
|
107
|
-
headers: {
|
|
108
|
-
...opts?.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
109
|
-
...this.headers,
|
|
110
|
-
...opts?.headers ?? {}
|
|
111
|
-
},
|
|
112
|
-
body: opts?.body !== void 0 ? JSON.stringify(opts.body) : void 0
|
|
113
|
-
});
|
|
114
|
-
const body = await readCapped(res, this.maxBytes);
|
|
115
|
-
return { res, body };
|
|
116
|
-
} finally {
|
|
117
|
-
clearTimeout(timer);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
// A malformed 2xx body must surface as a TYPED HttpError, not a leaked SyntaxError (B10: dApp
|
|
121
|
-
// code branches on `instanceof CairnError`; a raw parse error escaped that contract).
|
|
122
|
-
parseJson(res, text, url) {
|
|
123
|
-
try {
|
|
124
|
-
return JSON.parse(text);
|
|
125
|
-
} catch (e) {
|
|
126
|
-
throw new HttpError(res.status, url, `invalid JSON body: ${text.slice(0, 120)}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
async getJson(path, query) {
|
|
130
|
-
const url = this.url(path, query);
|
|
131
|
-
const budget = new RetryWaitBudget(HTTP_RETRY_WAIT_BUDGET_MS);
|
|
132
|
-
for (let attempt = 0; ; attempt++) {
|
|
133
|
-
let serverWaitMs = 0;
|
|
134
|
-
let last;
|
|
135
|
-
try {
|
|
136
|
-
const { res, body } = await this.raw("GET", path, { query });
|
|
137
|
-
if (!res.ok) {
|
|
138
|
-
last = new HttpError(res.status, url, errText(body));
|
|
139
|
-
if (!(res.status >= 500 && attempt < this.retries)) throw last;
|
|
140
|
-
serverWaitMs = retryAfterMs(res);
|
|
141
|
-
} else {
|
|
142
|
-
return this.parseJson(res, new TextDecoder().decode(body), url);
|
|
143
|
-
}
|
|
144
|
-
} catch (e) {
|
|
145
|
-
if (e instanceof HttpError && e.status < 500) throw e;
|
|
146
|
-
if (attempt >= this.retries) throw e;
|
|
147
|
-
}
|
|
148
|
-
const jitterMs = Math.floor(Math.random() * Math.min(4e3, 250 * 2 ** attempt));
|
|
149
|
-
const waitMs = budget.take(jitterMs, serverWaitMs);
|
|
150
|
-
if (waitMs === null) throw last ?? new HttpError(0, url, "retry wait budget exhausted");
|
|
151
|
-
await new Promise((r) => setTimeout(r, waitMs));
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
async postJson(path, body, headers) {
|
|
155
|
-
const url = this.url(path);
|
|
156
|
-
const { res, body: respBody } = await this.raw("POST", path, { body, headers });
|
|
157
|
-
if (!res.ok) throw new HttpError(res.status, url, errText(respBody));
|
|
158
|
-
return this.parseJson(res, new TextDecoder().decode(respBody), url);
|
|
159
|
-
}
|
|
160
|
-
/** GET raw bytes (for content retrieval). Returns null on 404. */
|
|
161
|
-
async getBytes(path) {
|
|
162
|
-
const { res, body } = await this.raw("GET", path);
|
|
163
|
-
if (res.status === 404) return null;
|
|
164
|
-
if (!res.ok) throw new HttpError(res.status, this.url(path), errText(body));
|
|
165
|
-
return { bytes: body, headers: res.headers };
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
function errText(body) {
|
|
169
|
-
try {
|
|
170
|
-
return new TextDecoder().decode(body).slice(0, ERR_TEXT_CAP);
|
|
171
|
-
} catch {
|
|
172
|
-
return "";
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// src/registry.ts
|
|
177
|
-
import {
|
|
178
|
-
fromRecords as fromRecords2,
|
|
179
|
-
verifyPeer,
|
|
180
|
-
verifyGateway,
|
|
181
|
-
verifyIdentitySig,
|
|
182
|
-
DOMAINS,
|
|
183
|
-
buildPeerRecord,
|
|
184
|
-
buildGatewayRecord,
|
|
185
|
-
buildIdentityCommit,
|
|
186
|
-
buildIdentityReveal,
|
|
187
|
-
epochOf
|
|
188
|
-
} from "@inversealtruism/csd-registry";
|
|
189
|
-
var RegistryClient = class {
|
|
190
|
-
http;
|
|
191
|
-
/** Accepts the SDK's hardened Http directly (the Cairn facade path) or the legacy
|
|
192
|
-
* { baseUrl, fetch } options (kept for direct constructors; they now get the same
|
|
193
|
-
* hardened transport, built internally). */
|
|
194
|
-
constructor(src) {
|
|
195
|
-
this.http = src instanceof Http ? src : new Http({ baseUrl: src.baseUrl, fetch: src.fetch });
|
|
196
|
-
}
|
|
197
|
-
/** Discover ranked content/pin gateways (GET /registry/gateways). */
|
|
198
|
-
gateways() {
|
|
199
|
-
return this.http.getJson("/registry/gateways");
|
|
200
|
-
}
|
|
201
|
-
/** Discover ranked libp2p peers (GET /registry/peers). */
|
|
202
|
-
peers() {
|
|
203
|
-
return this.http.getJson("/registry/peers");
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Resolve a handle → identity (GET /identity/:handle). Returns null if unresolved.
|
|
207
|
-
*
|
|
208
|
-
* ⚠ TRUST (CAIRN-SDK-RESOLVE-VERIFIED-FOOTGUN): the returned `verified` flag is the INDEXER's assertion,
|
|
209
|
-
* NOT a client-side proof. A hostile/compromised indexer (or a MITM of the read proxy) can return
|
|
210
|
-
* `verified:true` for a wrong address. Do NOT use this directly as a payee/login target without either (a)
|
|
211
|
-
* recomputing from raw records via `fromRecords.name(...)`, or (b) cross-checking a second independent
|
|
212
|
-
* source (the wallet's namespv union cure). Treat `verified` here as a hint, not a guarantee.
|
|
213
|
-
*/
|
|
214
|
-
resolveName(handle) {
|
|
215
|
-
return this.getOr404Null(`/identity/${encodeURIComponent(handle)}`);
|
|
216
|
-
}
|
|
217
|
-
/** Reverse-resolve an address → identity (GET /address/:addr/identity).
|
|
218
|
-
* ⚠ Same indexer-trust caveat as resolveName — `verified` is the indexer's claim, not a client proof. */
|
|
219
|
-
reverseName(address) {
|
|
220
|
-
return this.getOr404Null(`/address/${encodeURIComponent(address)}/identity`);
|
|
221
|
-
}
|
|
222
|
-
// MF-12: 404 → null (the preserved csd-registry contract for both name resolvers); every other
|
|
223
|
-
// failure rethrows the typed HttpError/CairnError the hardened Http raises.
|
|
224
|
-
async getOr404Null(path) {
|
|
225
|
-
try {
|
|
226
|
-
return await this.http.getJson(path);
|
|
227
|
-
} catch (e) {
|
|
228
|
-
if (e instanceof HttpError && e.status === 404) return null;
|
|
229
|
-
return Promise.reject(e);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
/** The deterministic, client-side resolvers (recompute from raw ChainRecord[]). */
|
|
233
|
-
get fromRecords() {
|
|
234
|
-
return fromRecords;
|
|
235
|
-
}
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
export {
|
|
239
|
-
retryAfterMs,
|
|
240
|
-
RetryWaitBudget,
|
|
241
|
-
readCapped,
|
|
242
|
-
Http,
|
|
243
|
-
RegistryClient,
|
|
244
|
-
fromRecords2 as fromRecords,
|
|
245
|
-
verifyPeer,
|
|
246
|
-
verifyGateway,
|
|
247
|
-
verifyIdentitySig,
|
|
248
|
-
DOMAINS,
|
|
249
|
-
buildPeerRecord,
|
|
250
|
-
buildGatewayRecord,
|
|
251
|
-
buildIdentityCommit,
|
|
252
|
-
buildIdentityReveal,
|
|
253
|
-
epochOf
|
|
254
|
-
};
|
package/dist/registry.cjs
DELETED
|
@@ -1,326 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/registry.ts
|
|
21
|
-
var registry_exports = {};
|
|
22
|
-
__export(registry_exports, {
|
|
23
|
-
DOMAINS: () => import_csd_registry2.DOMAINS,
|
|
24
|
-
RegistryClient: () => RegistryClient,
|
|
25
|
-
buildGatewayRecord: () => import_csd_registry2.buildGatewayRecord,
|
|
26
|
-
buildIdentityCommit: () => import_csd_registry2.buildIdentityCommit,
|
|
27
|
-
buildIdentityReveal: () => import_csd_registry2.buildIdentityReveal,
|
|
28
|
-
buildPeerRecord: () => import_csd_registry2.buildPeerRecord,
|
|
29
|
-
epochOf: () => import_csd_registry2.epochOf,
|
|
30
|
-
fromRecords: () => import_csd_registry2.fromRecords,
|
|
31
|
-
verifyGateway: () => import_csd_registry2.verifyGateway,
|
|
32
|
-
verifyIdentitySig: () => import_csd_registry2.verifyIdentitySig,
|
|
33
|
-
verifyPeer: () => import_csd_registry2.verifyPeer
|
|
34
|
-
});
|
|
35
|
-
module.exports = __toCommonJS(registry_exports);
|
|
36
|
-
var import_csd_registry = require("@inversealtruism/csd-registry");
|
|
37
|
-
|
|
38
|
-
// src/errors.ts
|
|
39
|
-
var SDK_VERSION = "0.6.0";
|
|
40
|
-
var DOCS_BASE = "https://cairn-substrate.com/docs/sdk";
|
|
41
|
-
var CairnError = class extends Error {
|
|
42
|
-
/** Stable machine code for programmatic branching. */
|
|
43
|
-
code;
|
|
44
|
-
/** The human message without the appended docs/version footer. */
|
|
45
|
-
shortMessage;
|
|
46
|
-
/** Relative docs path (under the SDK docs base), if any. */
|
|
47
|
-
docsPath;
|
|
48
|
-
/** The cairn-sdk version that produced this error. */
|
|
49
|
-
version = SDK_VERSION;
|
|
50
|
-
/**
|
|
51
|
-
* Whether a dApp may safely AUTO-retry this failure. `false` for fund-safety refusals and user-intent
|
|
52
|
-
* declines (the default); `true` only for provably transient infra faults where nothing was signed.
|
|
53
|
-
* A `SubmitInFlightError` is `false` too: reconcile its `txid`, never blind-retry.
|
|
54
|
-
*/
|
|
55
|
-
retryable;
|
|
56
|
-
constructor(shortMessage, opts = {}) {
|
|
57
|
-
const docs = opts.docsPath;
|
|
58
|
-
super(
|
|
59
|
-
[shortMessage, docs ? `Docs: ${DOCS_BASE}${docs}` : "", `Version: cairn-sdk@${SDK_VERSION}`].filter(Boolean).join("\n"),
|
|
60
|
-
{ cause: opts.cause }
|
|
61
|
-
);
|
|
62
|
-
this.name = new.target.name;
|
|
63
|
-
this.code = opts.code ?? "UNKNOWN";
|
|
64
|
-
this.shortMessage = shortMessage;
|
|
65
|
-
this.docsPath = docs;
|
|
66
|
-
this.retryable = opts.retryable ?? false;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Walk the `cause` chain. With a predicate, returns the FIRST matching error (or null). Without one,
|
|
70
|
-
* returns the DEEPEST cause (the root). Mirrors viem's `BaseError.walk()`.
|
|
71
|
-
*/
|
|
72
|
-
walk(fn) {
|
|
73
|
-
let current = this;
|
|
74
|
-
let last = this;
|
|
75
|
-
while (current != null) {
|
|
76
|
-
if (fn && fn(current)) return current;
|
|
77
|
-
last = current;
|
|
78
|
-
current = current?.cause;
|
|
79
|
-
}
|
|
80
|
-
return fn ? null : last;
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
var HttpError = class extends CairnError {
|
|
84
|
-
status;
|
|
85
|
-
url;
|
|
86
|
-
body;
|
|
87
|
-
constructor(status, url, body) {
|
|
88
|
-
super(`HTTP ${status} for ${url}${body ? `: ${String(body).slice(0, 200)}` : ""}`, { code: "HTTP_ERROR" });
|
|
89
|
-
this.status = status;
|
|
90
|
-
this.url = url;
|
|
91
|
-
this.body = body;
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
// src/http.ts
|
|
96
|
-
var DEFAULT_MAX_BYTES = 16 * 1024 * 1024;
|
|
97
|
-
var ERR_TEXT_CAP = 8 * 1024;
|
|
98
|
-
var MAX_RETRY_AFTER_MS = 6e4;
|
|
99
|
-
function retryAfterMs(res) {
|
|
100
|
-
const secs = Number(res.headers.get("retry-after"));
|
|
101
|
-
return Number.isFinite(secs) && secs > 0 ? Math.min(secs * 1e3, MAX_RETRY_AFTER_MS) : 0;
|
|
102
|
-
}
|
|
103
|
-
var RetryWaitBudget = class {
|
|
104
|
-
left;
|
|
105
|
-
constructor(totalMs) {
|
|
106
|
-
this.left = totalMs;
|
|
107
|
-
}
|
|
108
|
-
/** The ms to sleep before the next attempt (`ownMs` is the caller's backoff, clipped to what is left), or
|
|
109
|
-
* null when the server's hint exceeds the remaining budget. */
|
|
110
|
-
take(ownMs, serverMs) {
|
|
111
|
-
if (serverMs > this.left) return null;
|
|
112
|
-
const ms = Math.min(Math.max(ownMs, serverMs), this.left);
|
|
113
|
-
this.left -= ms;
|
|
114
|
-
return ms;
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
var HTTP_RETRY_WAIT_BUDGET_MS = 3e4;
|
|
118
|
-
async function readCapped(res, maxBytes) {
|
|
119
|
-
const cl = Number(res.headers.get("content-length"));
|
|
120
|
-
if (Number.isFinite(cl) && cl > maxBytes) throw new HttpError(res.status, res.url, `response too large (content-length ${cl} > ${maxBytes})`);
|
|
121
|
-
const body = res.body;
|
|
122
|
-
if (!body || typeof body.getReader !== "function") {
|
|
123
|
-
const buf = new Uint8Array(await res.arrayBuffer());
|
|
124
|
-
if (buf.byteLength > maxBytes) throw new HttpError(res.status, res.url, `response too large (${buf.byteLength} > ${maxBytes})`);
|
|
125
|
-
return buf;
|
|
126
|
-
}
|
|
127
|
-
const reader = body.getReader();
|
|
128
|
-
const chunks = [];
|
|
129
|
-
let total = 0;
|
|
130
|
-
for (; ; ) {
|
|
131
|
-
const { done, value } = await reader.read();
|
|
132
|
-
if (done) break;
|
|
133
|
-
if (value) {
|
|
134
|
-
total += value.byteLength;
|
|
135
|
-
if (total > maxBytes) {
|
|
136
|
-
try {
|
|
137
|
-
await reader.cancel();
|
|
138
|
-
} catch {
|
|
139
|
-
}
|
|
140
|
-
throw new HttpError(res.status, res.url, `response exceeded ${maxBytes} bytes`);
|
|
141
|
-
}
|
|
142
|
-
chunks.push(value);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
const out = new Uint8Array(total);
|
|
146
|
-
let off = 0;
|
|
147
|
-
for (const c of chunks) {
|
|
148
|
-
out.set(c, off);
|
|
149
|
-
off += c.byteLength;
|
|
150
|
-
}
|
|
151
|
-
return out;
|
|
152
|
-
}
|
|
153
|
-
var Http = class {
|
|
154
|
-
baseUrl;
|
|
155
|
-
_fetch;
|
|
156
|
-
timeoutMs;
|
|
157
|
-
maxBytes;
|
|
158
|
-
headers;
|
|
159
|
-
retries;
|
|
160
|
-
constructor(opts) {
|
|
161
|
-
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
162
|
-
const f = opts.fetch ?? globalThis.fetch;
|
|
163
|
-
if (!f) throw new Error("No fetch available \u2014 pass `fetch` in the SDK options.");
|
|
164
|
-
this._fetch = opts.fetch ? opts.fetch : f.bind(globalThis);
|
|
165
|
-
this.timeoutMs = opts.timeoutMs ?? 15e3;
|
|
166
|
-
this.maxBytes = Number.isFinite(opts.maxBytes) && opts.maxBytes >= 1 ? Math.floor(opts.maxBytes) : DEFAULT_MAX_BYTES;
|
|
167
|
-
this.headers = opts.headers ?? {};
|
|
168
|
-
const retries = Number(opts.retries ?? 0);
|
|
169
|
-
this.retries = Number.isNaN(retries) ? 0 : Math.min(10, Math.max(0, Math.floor(retries)));
|
|
170
|
-
}
|
|
171
|
-
/** Build an absolute URL from a path + optional query params. */
|
|
172
|
-
url(path, query) {
|
|
173
|
-
const p = path.startsWith("/") ? path : `/${path}`;
|
|
174
|
-
const u = new URL(this.baseUrl + p);
|
|
175
|
-
const basePath = new URL(this.baseUrl + "/").pathname;
|
|
176
|
-
if (basePath !== "/" && !(u.pathname + "/").startsWith(basePath)) {
|
|
177
|
-
throw new Error(`illegal path '${path}' \u2014 escapes base ${basePath}`);
|
|
178
|
-
}
|
|
179
|
-
if (query) {
|
|
180
|
-
for (const [k, v] of Object.entries(query)) {
|
|
181
|
-
if (v !== void 0 && v !== null) u.searchParams.set(k, String(v));
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
return u.toString();
|
|
185
|
-
}
|
|
186
|
-
async raw(method, path, opts) {
|
|
187
|
-
const url = this.url(path, opts?.query);
|
|
188
|
-
const ctrl = new AbortController();
|
|
189
|
-
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
190
|
-
try {
|
|
191
|
-
const res = await this._fetch(url, {
|
|
192
|
-
method,
|
|
193
|
-
signal: ctrl.signal,
|
|
194
|
-
headers: {
|
|
195
|
-
...opts?.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
196
|
-
...this.headers,
|
|
197
|
-
...opts?.headers ?? {}
|
|
198
|
-
},
|
|
199
|
-
body: opts?.body !== void 0 ? JSON.stringify(opts.body) : void 0
|
|
200
|
-
});
|
|
201
|
-
const body = await readCapped(res, this.maxBytes);
|
|
202
|
-
return { res, body };
|
|
203
|
-
} finally {
|
|
204
|
-
clearTimeout(timer);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
// A malformed 2xx body must surface as a TYPED HttpError, not a leaked SyntaxError (B10: dApp
|
|
208
|
-
// code branches on `instanceof CairnError`; a raw parse error escaped that contract).
|
|
209
|
-
parseJson(res, text, url) {
|
|
210
|
-
try {
|
|
211
|
-
return JSON.parse(text);
|
|
212
|
-
} catch (e) {
|
|
213
|
-
throw new HttpError(res.status, url, `invalid JSON body: ${text.slice(0, 120)}`);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
async getJson(path, query) {
|
|
217
|
-
const url = this.url(path, query);
|
|
218
|
-
const budget = new RetryWaitBudget(HTTP_RETRY_WAIT_BUDGET_MS);
|
|
219
|
-
for (let attempt = 0; ; attempt++) {
|
|
220
|
-
let serverWaitMs = 0;
|
|
221
|
-
let last;
|
|
222
|
-
try {
|
|
223
|
-
const { res, body } = await this.raw("GET", path, { query });
|
|
224
|
-
if (!res.ok) {
|
|
225
|
-
last = new HttpError(res.status, url, errText(body));
|
|
226
|
-
if (!(res.status >= 500 && attempt < this.retries)) throw last;
|
|
227
|
-
serverWaitMs = retryAfterMs(res);
|
|
228
|
-
} else {
|
|
229
|
-
return this.parseJson(res, new TextDecoder().decode(body), url);
|
|
230
|
-
}
|
|
231
|
-
} catch (e) {
|
|
232
|
-
if (e instanceof HttpError && e.status < 500) throw e;
|
|
233
|
-
if (attempt >= this.retries) throw e;
|
|
234
|
-
}
|
|
235
|
-
const jitterMs = Math.floor(Math.random() * Math.min(4e3, 250 * 2 ** attempt));
|
|
236
|
-
const waitMs = budget.take(jitterMs, serverWaitMs);
|
|
237
|
-
if (waitMs === null) throw last ?? new HttpError(0, url, "retry wait budget exhausted");
|
|
238
|
-
await new Promise((r) => setTimeout(r, waitMs));
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
async postJson(path, body, headers) {
|
|
242
|
-
const url = this.url(path);
|
|
243
|
-
const { res, body: respBody } = await this.raw("POST", path, { body, headers });
|
|
244
|
-
if (!res.ok) throw new HttpError(res.status, url, errText(respBody));
|
|
245
|
-
return this.parseJson(res, new TextDecoder().decode(respBody), url);
|
|
246
|
-
}
|
|
247
|
-
/** GET raw bytes (for content retrieval). Returns null on 404. */
|
|
248
|
-
async getBytes(path) {
|
|
249
|
-
const { res, body } = await this.raw("GET", path);
|
|
250
|
-
if (res.status === 404) return null;
|
|
251
|
-
if (!res.ok) throw new HttpError(res.status, this.url(path), errText(body));
|
|
252
|
-
return { bytes: body, headers: res.headers };
|
|
253
|
-
}
|
|
254
|
-
};
|
|
255
|
-
function errText(body) {
|
|
256
|
-
try {
|
|
257
|
-
return new TextDecoder().decode(body).slice(0, ERR_TEXT_CAP);
|
|
258
|
-
} catch {
|
|
259
|
-
return "";
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// src/registry.ts
|
|
264
|
-
var import_csd_registry2 = require("@inversealtruism/csd-registry");
|
|
265
|
-
var RegistryClient = class {
|
|
266
|
-
http;
|
|
267
|
-
/** Accepts the SDK's hardened Http directly (the Cairn facade path) or the legacy
|
|
268
|
-
* { baseUrl, fetch } options (kept for direct constructors; they now get the same
|
|
269
|
-
* hardened transport, built internally). */
|
|
270
|
-
constructor(src) {
|
|
271
|
-
this.http = src instanceof Http ? src : new Http({ baseUrl: src.baseUrl, fetch: src.fetch });
|
|
272
|
-
}
|
|
273
|
-
/** Discover ranked content/pin gateways (GET /registry/gateways). */
|
|
274
|
-
gateways() {
|
|
275
|
-
return this.http.getJson("/registry/gateways");
|
|
276
|
-
}
|
|
277
|
-
/** Discover ranked libp2p peers (GET /registry/peers). */
|
|
278
|
-
peers() {
|
|
279
|
-
return this.http.getJson("/registry/peers");
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* Resolve a handle → identity (GET /identity/:handle). Returns null if unresolved.
|
|
283
|
-
*
|
|
284
|
-
* ⚠ TRUST (CAIRN-SDK-RESOLVE-VERIFIED-FOOTGUN): the returned `verified` flag is the INDEXER's assertion,
|
|
285
|
-
* NOT a client-side proof. A hostile/compromised indexer (or a MITM of the read proxy) can return
|
|
286
|
-
* `verified:true` for a wrong address. Do NOT use this directly as a payee/login target without either (a)
|
|
287
|
-
* recomputing from raw records via `fromRecords.name(...)`, or (b) cross-checking a second independent
|
|
288
|
-
* source (the wallet's namespv union cure). Treat `verified` here as a hint, not a guarantee.
|
|
289
|
-
*/
|
|
290
|
-
resolveName(handle) {
|
|
291
|
-
return this.getOr404Null(`/identity/${encodeURIComponent(handle)}`);
|
|
292
|
-
}
|
|
293
|
-
/** Reverse-resolve an address → identity (GET /address/:addr/identity).
|
|
294
|
-
* ⚠ Same indexer-trust caveat as resolveName — `verified` is the indexer's claim, not a client proof. */
|
|
295
|
-
reverseName(address) {
|
|
296
|
-
return this.getOr404Null(`/address/${encodeURIComponent(address)}/identity`);
|
|
297
|
-
}
|
|
298
|
-
// MF-12: 404 → null (the preserved csd-registry contract for both name resolvers); every other
|
|
299
|
-
// failure rethrows the typed HttpError/CairnError the hardened Http raises.
|
|
300
|
-
async getOr404Null(path) {
|
|
301
|
-
try {
|
|
302
|
-
return await this.http.getJson(path);
|
|
303
|
-
} catch (e) {
|
|
304
|
-
if (e instanceof HttpError && e.status === 404) return null;
|
|
305
|
-
return Promise.reject(e);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
/** The deterministic, client-side resolvers (recompute from raw ChainRecord[]). */
|
|
309
|
-
get fromRecords() {
|
|
310
|
-
return import_csd_registry.fromRecords;
|
|
311
|
-
}
|
|
312
|
-
};
|
|
313
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
314
|
-
0 && (module.exports = {
|
|
315
|
-
DOMAINS,
|
|
316
|
-
RegistryClient,
|
|
317
|
-
buildGatewayRecord,
|
|
318
|
-
buildIdentityCommit,
|
|
319
|
-
buildIdentityReveal,
|
|
320
|
-
buildPeerRecord,
|
|
321
|
-
epochOf,
|
|
322
|
-
fromRecords,
|
|
323
|
-
verifyGateway,
|
|
324
|
-
verifyIdentitySig,
|
|
325
|
-
verifyPeer
|
|
326
|
-
});
|
package/dist/registry.d.cts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import * as _inversealtruism_csd_registry from '@inversealtruism/csd-registry';
|
|
2
|
-
import { RankedGateway, RankedPeer, ResolvedIdentity } from '@inversealtruism/csd-registry';
|
|
3
|
-
export { BuiltRecord, ChainRecord, DOMAINS, RankedGateway, RankedPeer, ResolveOpts, ResolvedIdentity, buildGatewayRecord, buildIdentityCommit, buildIdentityReveal, buildPeerRecord, epochOf, fromRecords, verifyGateway, verifyIdentitySig, verifyPeer } from '@inversealtruism/csd-registry';
|
|
4
|
-
import { H as Http, F as FetchLike } from './http-C9C4L4hz.cjs';
|
|
5
|
-
|
|
6
|
-
interface RegistryClientOptions {
|
|
7
|
-
/** Indexer base URL exposing /registry/* + /identity/* (e.g. https://cairn-substrate.com/indexer). */
|
|
8
|
-
baseUrl: string;
|
|
9
|
-
fetch?: FetchLike;
|
|
10
|
-
}
|
|
11
|
-
declare class RegistryClient {
|
|
12
|
-
private readonly http;
|
|
13
|
-
/** Accepts the SDK's hardened Http directly (the Cairn facade path) or the legacy
|
|
14
|
-
* { baseUrl, fetch } options (kept for direct constructors; they now get the same
|
|
15
|
-
* hardened transport, built internally). */
|
|
16
|
-
constructor(src: Http | RegistryClientOptions);
|
|
17
|
-
/** Discover ranked content/pin gateways (GET /registry/gateways). */
|
|
18
|
-
gateways(): Promise<RankedGateway[]>;
|
|
19
|
-
/** Discover ranked libp2p peers (GET /registry/peers). */
|
|
20
|
-
peers(): Promise<RankedPeer[]>;
|
|
21
|
-
/**
|
|
22
|
-
* Resolve a handle → identity (GET /identity/:handle). Returns null if unresolved.
|
|
23
|
-
*
|
|
24
|
-
* ⚠ TRUST (CAIRN-SDK-RESOLVE-VERIFIED-FOOTGUN): the returned `verified` flag is the INDEXER's assertion,
|
|
25
|
-
* NOT a client-side proof. A hostile/compromised indexer (or a MITM of the read proxy) can return
|
|
26
|
-
* `verified:true` for a wrong address. Do NOT use this directly as a payee/login target without either (a)
|
|
27
|
-
* recomputing from raw records via `fromRecords.name(...)`, or (b) cross-checking a second independent
|
|
28
|
-
* source (the wallet's namespv union cure). Treat `verified` here as a hint, not a guarantee.
|
|
29
|
-
*/
|
|
30
|
-
resolveName(handle: string): Promise<ResolvedIdentity | null>;
|
|
31
|
-
/** Reverse-resolve an address → identity (GET /address/:addr/identity).
|
|
32
|
-
* ⚠ Same indexer-trust caveat as resolveName — `verified` is the indexer's claim, not a client proof. */
|
|
33
|
-
reverseName(address: string): Promise<ResolvedIdentity | null>;
|
|
34
|
-
private getOr404Null;
|
|
35
|
-
/** The deterministic, client-side resolvers (recompute from raw ChainRecord[]). */
|
|
36
|
-
get fromRecords(): {
|
|
37
|
-
peers: (records: _inversealtruism_csd_registry.ChainRecord[], opts: _inversealtruism_csd_registry.ResolveOpts) => RankedPeer[];
|
|
38
|
-
gateways: (records: _inversealtruism_csd_registry.ChainRecord[], opts: _inversealtruism_csd_registry.ResolveOpts) => RankedGateway[];
|
|
39
|
-
name: (records: _inversealtruism_csd_registry.ChainRecord[], handle: string, opts: _inversealtruism_csd_registry.ResolveOpts) => ResolvedIdentity | null;
|
|
40
|
-
reverse: (records: _inversealtruism_csd_registry.ChainRecord[], address: string, opts: _inversealtruism_csd_registry.ResolveOpts) => ResolvedIdentity | null;
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export { RegistryClient, type RegistryClientOptions };
|
package/dist/registry.d.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import * as _inversealtruism_csd_registry from '@inversealtruism/csd-registry';
|
|
2
|
-
import { RankedGateway, RankedPeer, ResolvedIdentity } from '@inversealtruism/csd-registry';
|
|
3
|
-
export { BuiltRecord, ChainRecord, DOMAINS, RankedGateway, RankedPeer, ResolveOpts, ResolvedIdentity, buildGatewayRecord, buildIdentityCommit, buildIdentityReveal, buildPeerRecord, epochOf, fromRecords, verifyGateway, verifyIdentitySig, verifyPeer } from '@inversealtruism/csd-registry';
|
|
4
|
-
import { H as Http, F as FetchLike } from './http-C9C4L4hz.js';
|
|
5
|
-
|
|
6
|
-
interface RegistryClientOptions {
|
|
7
|
-
/** Indexer base URL exposing /registry/* + /identity/* (e.g. https://cairn-substrate.com/indexer). */
|
|
8
|
-
baseUrl: string;
|
|
9
|
-
fetch?: FetchLike;
|
|
10
|
-
}
|
|
11
|
-
declare class RegistryClient {
|
|
12
|
-
private readonly http;
|
|
13
|
-
/** Accepts the SDK's hardened Http directly (the Cairn facade path) or the legacy
|
|
14
|
-
* { baseUrl, fetch } options (kept for direct constructors; they now get the same
|
|
15
|
-
* hardened transport, built internally). */
|
|
16
|
-
constructor(src: Http | RegistryClientOptions);
|
|
17
|
-
/** Discover ranked content/pin gateways (GET /registry/gateways). */
|
|
18
|
-
gateways(): Promise<RankedGateway[]>;
|
|
19
|
-
/** Discover ranked libp2p peers (GET /registry/peers). */
|
|
20
|
-
peers(): Promise<RankedPeer[]>;
|
|
21
|
-
/**
|
|
22
|
-
* Resolve a handle → identity (GET /identity/:handle). Returns null if unresolved.
|
|
23
|
-
*
|
|
24
|
-
* ⚠ TRUST (CAIRN-SDK-RESOLVE-VERIFIED-FOOTGUN): the returned `verified` flag is the INDEXER's assertion,
|
|
25
|
-
* NOT a client-side proof. A hostile/compromised indexer (or a MITM of the read proxy) can return
|
|
26
|
-
* `verified:true` for a wrong address. Do NOT use this directly as a payee/login target without either (a)
|
|
27
|
-
* recomputing from raw records via `fromRecords.name(...)`, or (b) cross-checking a second independent
|
|
28
|
-
* source (the wallet's namespv union cure). Treat `verified` here as a hint, not a guarantee.
|
|
29
|
-
*/
|
|
30
|
-
resolveName(handle: string): Promise<ResolvedIdentity | null>;
|
|
31
|
-
/** Reverse-resolve an address → identity (GET /address/:addr/identity).
|
|
32
|
-
* ⚠ Same indexer-trust caveat as resolveName — `verified` is the indexer's claim, not a client proof. */
|
|
33
|
-
reverseName(address: string): Promise<ResolvedIdentity | null>;
|
|
34
|
-
private getOr404Null;
|
|
35
|
-
/** The deterministic, client-side resolvers (recompute from raw ChainRecord[]). */
|
|
36
|
-
get fromRecords(): {
|
|
37
|
-
peers: (records: _inversealtruism_csd_registry.ChainRecord[], opts: _inversealtruism_csd_registry.ResolveOpts) => RankedPeer[];
|
|
38
|
-
gateways: (records: _inversealtruism_csd_registry.ChainRecord[], opts: _inversealtruism_csd_registry.ResolveOpts) => RankedGateway[];
|
|
39
|
-
name: (records: _inversealtruism_csd_registry.ChainRecord[], handle: string, opts: _inversealtruism_csd_registry.ResolveOpts) => ResolvedIdentity | null;
|
|
40
|
-
reverse: (records: _inversealtruism_csd_registry.ChainRecord[], address: string, opts: _inversealtruism_csd_registry.ResolveOpts) => ResolvedIdentity | null;
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export { RegistryClient, type RegistryClientOptions };
|