@ghostlytools/cli 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +9 -3
  2. package/dist/assets/{api-BiaX6l6f.js → api-SYPJgFRU.js} +2 -2
  3. package/dist/assets/{api-CrxVxtY1.js → api-aup3Amud.js} +141 -40
  4. package/dist/assets/api-aup3Amud.js.map +1 -0
  5. package/dist/assets/{apiKit-CajiDxdK.js → apiKit-DYkVEs6r.js} +30 -30
  6. package/dist/assets/apiKit-DYkVEs6r.js.map +1 -0
  7. package/dist/assets/{apps-t23gkc28.js → apps-DcZKu29s.js} +1 -1
  8. package/dist/assets/{apps-CbfiCSIw.js → apps-Rbk2HHXb.js} +3 -4
  9. package/dist/assets/{apps-CbfiCSIw.js.map → apps-Rbk2HHXb.js.map} +1 -1
  10. package/dist/assets/{arkade-D4BVJFEQ.js → arkade-CuWlNnCz.js} +4 -4
  11. package/dist/assets/arkade-CuWlNnCz.js.map +1 -0
  12. package/dist/assets/{backup-BAMi2lAq.js → backup-CJWLDHuS.js} +8 -8
  13. package/dist/assets/backup-CJWLDHuS.js.map +1 -0
  14. package/dist/assets/{backup-Cg52WeQi.js → backup-CSMw2GHv.js} +1 -1
  15. package/dist/assets/bark_ffi_wasm_bg-EY2uc0Bx.js.map +1 -1
  16. package/dist/assets/bitcoind-CZchoh43.js +349 -0
  17. package/dist/assets/bitcoind-CZchoh43.js.map +1 -0
  18. package/dist/assets/bitcoindProvider-DGw3xBSa.js +97 -0
  19. package/dist/assets/bitcoindProvider-DGw3xBSa.js.map +1 -0
  20. package/dist/assets/bitcoindevkit_bg-B-iLWrmX.js.map +1 -1
  21. package/dist/assets/buttons-CGRqZ7z0.js +2 -0
  22. package/dist/assets/{buttons-CX1pKB0j.js → buttons-QEnAGWoC.js} +4 -5
  23. package/dist/assets/{buttons-CX1pKB0j.js.map → buttons-QEnAGWoC.js.map} +1 -1
  24. package/dist/assets/{endpoint-Dn7H8qE1.js → endpoint-BOTFMpfS.js} +44 -12
  25. package/dist/assets/endpoint-BOTFMpfS.js.map +1 -0
  26. package/dist/assets/{envelope-BvU0fLEt.js → envelope-K27mzoyu.js} +2 -2
  27. package/dist/assets/{envelope-BvU0fLEt.js.map → envelope-K27mzoyu.js.map} +1 -1
  28. package/dist/assets/{errors-my6VJR_0.js → errors-DO5z6h_l.js} +29 -1
  29. package/dist/assets/errors-DO5z6h_l.js.map +1 -0
  30. package/dist/assets/fedimint_client_wasm_bg-BtHHlFpp.js.map +1 -1
  31. package/dist/assets/groupFiles-DHTrTA4I.js +3229 -0
  32. package/dist/assets/groupFiles-DHTrTA4I.js.map +1 -0
  33. package/dist/assets/{host-Cmd205Dc.js → host-C7hA-Za7.js} +135 -42
  34. package/dist/assets/host-C7hA-Za7.js.map +1 -0
  35. package/dist/assets/{hyperdhtRelay-CEmBpT0Q.js → hyperdhtRelay-BVoWY2ua.js} +2 -3
  36. package/dist/assets/{hyperdhtRelay-CEmBpT0Q.js.map → hyperdhtRelay-BVoWY2ua.js.map} +1 -1
  37. package/dist/assets/{irohWeb--YyEA-hX.js → irohWeb-BJPk9bMn.js} +2 -3
  38. package/dist/assets/{irohWeb--YyEA-hX.js.map → irohWeb-BJPk9bMn.js.map} +1 -1
  39. package/dist/assets/{main-BzNN6bkx.js → main-DC8cUkDx.js} +142 -35
  40. package/dist/assets/main-DC8cUkDx.js.map +1 -0
  41. package/dist/assets/{mainline-D5D5C-jT.js → mainline-CxbxpoU4.js} +2 -2
  42. package/dist/assets/{mainline-D5D5C-jT.js.map → mainline-CxbxpoU4.js.map} +1 -1
  43. package/dist/assets/{networkReason-DxZWHw6v.js → networkReason-D42iNuUB.js} +2 -2
  44. package/dist/assets/{networkReason-DxZWHw6v.js.map → networkReason-D42iNuUB.js.map} +1 -1
  45. package/dist/assets/nwc-Cm4sYgLv.js +612 -0
  46. package/dist/assets/nwc-Cm4sYgLv.js.map +1 -0
  47. package/dist/assets/nwcProvider-0D7vdPDd.js +77 -0
  48. package/dist/assets/nwcProvider-0D7vdPDd.js.map +1 -0
  49. package/dist/assets/{profiles-B6F4oa9K.js → profiles-BHc9pEYg.js} +2 -2
  50. package/dist/assets/{profiles-B6F4oa9K.js.map → profiles-BHc9pEYg.js.map} +1 -1
  51. package/dist/assets/registry-CLolR0kM.js +2 -0
  52. package/dist/assets/{groupFiles-BNTSDaoq.js → registry-CVdSZW5l.js} +19 -3911
  53. package/dist/assets/registry-CVdSZW5l.js.map +1 -0
  54. package/dist/assets/resolve-BnlEajYz.js +591 -0
  55. package/dist/assets/resolve-BnlEajYz.js.map +1 -0
  56. package/dist/assets/{server-CBCEYKMN.js → server-CJNEKxzE.js} +284 -1202
  57. package/dist/assets/server-CJNEKxzE.js.map +1 -0
  58. package/dist/assets/{src-DhfGgAUu.js → src-yMUQvdyL.js} +2264 -71
  59. package/dist/assets/src-yMUQvdyL.js.map +1 -0
  60. package/dist/assets/{turnRefs-ChEfgbSX.js → turnRefs-B2a8to9q.js} +2 -2
  61. package/dist/assets/{turnRefs-ChEfgbSX.js.map → turnRefs-B2a8to9q.js.map} +1 -1
  62. package/dist/assets/types-C4_KafL_.js +122 -0
  63. package/dist/assets/types-C4_KafL_.js.map +1 -0
  64. package/dist/assets/{usdt-C1Xao2xH.js → usdt-DesFeDiG.js} +3 -3
  65. package/dist/assets/{usdt-C1Xao2xH.js.map → usdt-DesFeDiG.js.map} +1 -1
  66. package/dist/ghostly.mjs +1 -1
  67. package/package.json +1 -1
  68. package/dist/assets/api-CrxVxtY1.js.map +0 -1
  69. package/dist/assets/apiKit-CajiDxdK.js.map +0 -1
  70. package/dist/assets/arkade-D4BVJFEQ.js.map +0 -1
  71. package/dist/assets/backup-BAMi2lAq.js.map +0 -1
  72. package/dist/assets/buttons-CWl2yNJu.js +0 -2
  73. package/dist/assets/endpoint-Dn7H8qE1.js.map +0 -1
  74. package/dist/assets/errors-my6VJR_0.js.map +0 -1
  75. package/dist/assets/groupFiles-BNTSDaoq.js.map +0 -1
  76. package/dist/assets/groupFiles-DBbjOLuD.js +0 -2088
  77. package/dist/assets/groupFiles-DBbjOLuD.js.map +0 -1
  78. package/dist/assets/host-Cmd205Dc.js.map +0 -1
  79. package/dist/assets/main-BzNN6bkx.js.map +0 -1
  80. package/dist/assets/server-CBCEYKMN.js.map +0 -1
  81. package/dist/assets/src-DhfGgAUu.js.map +0 -1
@@ -0,0 +1,591 @@
1
+ import { Ai as nip05RootKey, Ci as DOMAIN_FILE_MAX_BYTES, Dt as parseAtprotoDidDocument, Fi as txtValue, Ii as wellKnownUrl, Ki as boundVerifiedIdentity, Mi as recordsFromTxt, Ni as recordsFromWellKnown, Oi as encodeDnsQuery, Pi as recordsName, Ti as dnsProofName, Tt as isAtprotoDid, ji as normalizeDomain, ki as isPublicAddress, wi as decodeDnsResponse, xl as toBase64Url, xt as atprotoDidMethod } from "./src-yMUQvdyL.js";
2
+ import { l as providerDescriptorProblems } from "./types-C4_KafL_.js";
3
+ //#region ../browser/src/proofs/verify.ts
4
+ var DAY = 86400;
5
+ /**
6
+ * The checks every proof goes through, whatever its provider: known provider, validity within the
7
+ * provider's limit, bounded evidence, strict parsing, the provider's own check, and the category rule
8
+ * (a self-custodied proof proves exactly the subject it names; an attested one says who attests it).
9
+ * The engine verifies a contact's proof, and the person's own proof before saving it, only through this.
10
+ */
11
+ async function verifyIdentity(providers, statement, evidence, ctx) {
12
+ const { binding } = statement;
13
+ const provider = providers.find((p) => p.id === binding.provider);
14
+ if (!provider) throw new Error("This app cannot verify this kind of identity");
15
+ if (binding.expiresAt - binding.issuedAt > Math.min(provider.validity.maxDays * DAY, 3456e4)) throw new Error("The proof lasts longer than this kind of identity allows");
16
+ if (JSON.stringify(evidence ?? null).length > 16384) throw new Error("Evidence too large");
17
+ const parsed = provider.parseEvidence(evidence);
18
+ const verified = boundVerifiedIdentity(await provider.verify(statement, parsed, ctx));
19
+ if (provider.category === "self-custodied" && verified.subject !== binding.subject) throw new Error("The evidence is for another identity");
20
+ if (provider.category === "provider-attested" && !verified.attester) throw new Error("The evidence does not say who attests it");
21
+ return verified;
22
+ }
23
+ /** Problems with a provider's descriptor, empty when it is well-formed. The registry and the contract suite use it. */
24
+ function descriptorProblems(p) {
25
+ const problems = [];
26
+ if (!/^[a-z][a-z0-9-]{0,31}$/.test(p.id)) problems.push("id must match ^[a-z][a-z0-9-]{0,31}$");
27
+ if (!p.label?.trim()) problems.push("label is empty");
28
+ if (!p.description?.trim()) problems.push("description is empty");
29
+ if (!p.privacy?.trim()) problems.push("privacy is empty: say what verify contacts");
30
+ if (p.category !== "self-custodied" && p.category !== "provider-attested") problems.push("category is unknown");
31
+ if (!p.platforms.length) problems.push("no platforms");
32
+ const { defaultDays, maxDays } = p.validity;
33
+ if (!(Number.isInteger(defaultDays) && Number.isInteger(maxDays) && defaultDays >= 1 && defaultDays <= maxDays && maxDays * DAY <= 3456e4)) problems.push("validity must be whole days, 1 ≤ default ≤ max ≤ 400");
34
+ if (p.recheck && !(Number.isInteger(p.recheck.afterSeconds) && p.recheck.afterSeconds >= 60)) problems.push("recheck.afterSeconds must be at least 60");
35
+ if (!p.signers.length) problems.push("no signers");
36
+ const ids = p.signers.map((s) => s.id);
37
+ if (new Set(ids).size !== ids.length) problems.push("signer ids are not unique");
38
+ for (const s of p.signers) {
39
+ if (![
40
+ "in-app",
41
+ "external-tool",
42
+ "redirect",
43
+ "publish"
44
+ ].includes(s.kind)) problems.push(`signer ${s.id} has an unknown kind`);
45
+ if (s.kind === "in-app" && typeof s.run !== "function") problems.push(`signer ${s.id} has no run()`);
46
+ if (s.kind === "external-tool" && (typeof s.instructions !== "function" || typeof s.parse !== "function")) problems.push(`signer ${s.id} needs instructions() and parse()`);
47
+ if (s.kind === "redirect" && typeof s.start !== "function") problems.push(`signer ${s.id} has no start()`);
48
+ if (s.kind === "publish" && (typeof s.instructions !== "function" || typeof s.evidence !== "function")) problems.push(`signer ${s.id} needs instructions() and evidence()`);
49
+ }
50
+ return problems;
51
+ }
52
+ /**
53
+ * The engine's `ctx.fetch`: HTTPS GET only, no credentials or referrer, a time-out, a size cap,
54
+ * redirects refused unless asked for.
55
+ * `online` false refuses everything (the person turned the network off).
56
+ */
57
+ function boundedIdentityFetch(options = { online: () => true }) {
58
+ return async (url, { headers, maxBytes = 65536, signal, redirect = "error" } = {}) => {
59
+ if (!options.online()) throw new Error("Offline: this identity cannot be checked now");
60
+ const target = new URL(url);
61
+ if (target.protocol !== "https:" || target.username || target.password) throw new Error("Identity checks use HTTPS only");
62
+ const timeout = AbortSignal.timeout(options.timeoutMs ?? 1e4);
63
+ const response = await (options.fetcher ?? fetch)(target.href, {
64
+ headers,
65
+ signal: signal ? AbortSignal.any([signal, timeout]) : timeout,
66
+ credentials: "omit",
67
+ referrerPolicy: "no-referrer",
68
+ cache: "no-store",
69
+ redirect
70
+ });
71
+ if (Number(response.headers.get("content-length")) > maxBytes) throw new Error("Identity check response too large");
72
+ const reader = response.body?.getReader();
73
+ const parts = [];
74
+ let length = 0;
75
+ if (reader) try {
76
+ for (;;) {
77
+ const { done, value } = await reader.read();
78
+ if (done) break;
79
+ length += value.length;
80
+ if (length > maxBytes) throw new Error("Identity check response too large");
81
+ parts.push(value);
82
+ }
83
+ } finally {
84
+ await reader.cancel().catch(() => {});
85
+ }
86
+ const bytes = new Uint8Array(length);
87
+ let offset = 0;
88
+ for (const part of parts) {
89
+ bytes.set(part, offset);
90
+ offset += part.length;
91
+ }
92
+ return {
93
+ status: response.status,
94
+ contentType: response.headers.get("content-type") ?? "",
95
+ text: new TextDecoder().decode(bytes),
96
+ bytes
97
+ };
98
+ };
99
+ }
100
+ //#endregion
101
+ //#region ../browser/src/plugins/bundled.ts
102
+ /**
103
+ * The adapter plugins compiled into this build. Empty unless the build lists some: `GHOSTLY_PLUGINS`
104
+ * names their modules (comma-separated paths from the repository root, each default-exporting a
105
+ * `GhostlyAdapterPlugin`), and the Vite plugin in `packages/browser/vite-plugin.ts` swaps this module
106
+ * for one that imports them. Release builds never set it; the e2e build carries the SDK example.
107
+ */
108
+ var BUNDLED_PLUGINS = [];
109
+ var PLUGIN_ID = /^[a-z][a-z0-9-]{0,63}$/;
110
+ var KEY = Symbol.for("ghostly.adapters/1");
111
+ function store() {
112
+ const g = globalThis;
113
+ return g[KEY] ??= {
114
+ plugins: /* @__PURE__ */ new Map(),
115
+ reserved: {
116
+ lightning: /* @__PURE__ */ new Set(),
117
+ onchain: /* @__PURE__ */ new Set(),
118
+ identity: /* @__PURE__ */ new Set()
119
+ },
120
+ listeners: /* @__PURE__ */ new Set(),
121
+ seeded: false
122
+ };
123
+ }
124
+ var ids = (kind, plugin) => {
125
+ const list = kind === "identity" ? plugin.identities : kind === "lightning" ? plugin.lightning : plugin.onchain;
126
+ return Array.isArray(list) ? list.map((a) => a?.id) : [];
127
+ };
128
+ var KINDS = [
129
+ "lightning",
130
+ "onchain",
131
+ "identity"
132
+ ];
133
+ /** Problems with a plugin, empty when it can be registered. Does not look at what is already registered. */
134
+ function pluginProblems(plugin) {
135
+ const problems = [];
136
+ if (!plugin || typeof plugin !== "object") return ["not a plugin object"];
137
+ if (!PLUGIN_ID.test(plugin.id ?? "")) problems.push("plugin id must match ^[a-z][a-z0-9-]{0,63}$");
138
+ if (plugin.sdk !== 1) problems.push(`plugin was written for SDK API ${String(plugin.sdk)}; this app speaks 1`);
139
+ if (plugin.version !== void 0 && (typeof plugin.version !== "string" || plugin.version.length > 64)) problems.push("version must be a short string");
140
+ for (const kind of ["lightning", "onchain"]) {
141
+ const list = plugin[kind];
142
+ if (list === void 0) continue;
143
+ if (!Array.isArray(list)) {
144
+ problems.push(`${kind} must be a list of descriptors`);
145
+ continue;
146
+ }
147
+ for (const d of list) {
148
+ if (!d || typeof d !== "object") {
149
+ problems.push(`${kind} ${String(d)}: not a descriptor`);
150
+ continue;
151
+ }
152
+ const own = providerDescriptorProblems(d);
153
+ if (d?.kind && d.kind !== kind) own.push(`is listed under ${kind} but says kind ${String(d.kind)}`);
154
+ problems.push(...own.map((p) => `${kind} ${String(d?.id)}: ${p}`));
155
+ }
156
+ }
157
+ if (plugin.identities !== void 0) {
158
+ if (!Array.isArray(plugin.identities)) problems.push("identities must be a list of providers");
159
+ else for (const p of plugin.identities) if (!p || typeof p !== "object") problems.push(`identity ${String(p)}: not a provider`);
160
+ else problems.push(...descriptorProblems(p).map((m) => `identity ${String(p.id)}: ${m}`));
161
+ }
162
+ for (const kind of KINDS) {
163
+ const list = ids(kind, plugin);
164
+ if (new Set(list).size !== list.length) problems.push(`${kind} ids are not unique within the plugin`);
165
+ }
166
+ if (!KINDS.some((kind) => ids(kind, plugin).length)) problems.push("the plugin brings no adapter");
167
+ return problems;
168
+ }
169
+ /** The built-in registries say which ids are theirs. A plugin using one is refused. */
170
+ function reserveAdapterIds(kind, list) {
171
+ const s = store();
172
+ for (const id of list) s.reserved[kind].add(id);
173
+ }
174
+ /**
175
+ * Registers a plugin's adapters. Throws, registering nothing, when the plugin is malformed, its id or
176
+ * one of its adapters' ids is taken (by a built-in or by another plugin), or its contract generation
177
+ * is another one. Returns the function that unregisters it.
178
+ */
179
+ function registerAdapters(plugin) {
180
+ const problems = pluginProblems(plugin);
181
+ const s = store();
182
+ if (!problems.length) {
183
+ if (s.plugins.has(plugin.id)) problems.push(`a plugin with id ${plugin.id} is already registered`);
184
+ for (const kind of KINDS) for (const id of ids(kind, plugin)) {
185
+ if (s.reserved[kind].has(id)) problems.push(`${kind} ${id} is a built-in of this app`);
186
+ for (const other of s.plugins.values()) if (ids(kind, other).includes(id)) problems.push(`${kind} ${id} is already registered by plugin ${other.id}`);
187
+ }
188
+ }
189
+ if (problems.length) throw new Error(`Cannot register plugin ${String(plugin?.id)}: ${problems.join("; ")}`);
190
+ s.plugins.set(plugin.id, plugin);
191
+ notify(s);
192
+ return () => {
193
+ if (s.plugins.get(plugin.id) === plugin) {
194
+ s.plugins.delete(plugin.id);
195
+ notify(s);
196
+ }
197
+ };
198
+ }
199
+ function notify(s) {
200
+ for (const listener of s.listeners) try {
201
+ listener();
202
+ } catch {}
203
+ }
204
+ /** Called when a plugin registers or unregisters: the engine re-reads the lists. */
205
+ function onAdaptersChanged(listener) {
206
+ const s = store();
207
+ s.listeners.add(listener);
208
+ return () => {
209
+ s.listeners.delete(listener);
210
+ };
211
+ }
212
+ /** Every registered plugin, in registration order (bundled ones first). */
213
+ function registeredPlugins() {
214
+ seed();
215
+ return [...store().plugins.values()];
216
+ }
217
+ function registeredLightningProviders() {
218
+ return registeredPlugins().flatMap((p) => p.lightning ?? []);
219
+ }
220
+ function registeredOnchainProviders() {
221
+ return registeredPlugins().flatMap((p) => p.onchain ?? []);
222
+ }
223
+ function registeredIdentityProviders() {
224
+ return registeredPlugins().flatMap((p) => p.identities ?? []);
225
+ }
226
+ /**
227
+ * The plugins compiled into this build register once, when they are first asked for. A broken one is
228
+ * reported and skipped rather than taking the app down with it; the e2e that expects it fails instead.
229
+ */
230
+ function seed() {
231
+ const s = store();
232
+ if (s.seeded) return;
233
+ s.seeded = true;
234
+ for (const plugin of BUNDLED_PLUGINS) try {
235
+ registerAdapters(plugin);
236
+ } catch (error) {
237
+ console.error(`[ghostly] a bundled adapter plugin was not loaded: ${error instanceof Error ? error.message : String(error)}`);
238
+ }
239
+ }
240
+ //#endregion
241
+ //#region ../browser/src/proofs/ownDid.ts
242
+ /**
243
+ * The profile's own Ghostly did:dht, which the DID provider refuses as an external identity. The UI and the engine each
244
+ * set where to read it. Here, not in the provider, so setting it does not load the providers.
245
+ */
246
+ var source = () => void 0;
247
+ function setOwnDidSource(read) {
248
+ source = read;
249
+ }
250
+ var ownDid = () => source();
251
+ //#endregion
252
+ //#region ../browser/src/proofs/domain.ts
253
+ /** Public resolvers that answer RFC 8484 wire-format GETs with CORS (checked 2026-09-23). */
254
+ var DOH_RESOLVERS = [
255
+ {
256
+ id: "quad9",
257
+ name: "Quad9",
258
+ url: "https://dns.quad9.net/dns-query"
259
+ },
260
+ {
261
+ id: "cloudflare",
262
+ name: "Cloudflare",
263
+ url: "https://cloudflare-dns.com/dns-query"
264
+ },
265
+ {
266
+ id: "google",
267
+ name: "Google",
268
+ url: "https://dns.google/dns-query"
269
+ }
270
+ ];
271
+ var resolverById = (id) => DOH_RESOLVERS.find((r) => r.id === id) ?? DOH_RESOLVERS.find((r) => r.id === "quad9");
272
+ /** The person's resolver choice, per browser profile (UI and engine share the origin). */
273
+ var RESOLVER_SETTING = "ghostly-doh-resolver";
274
+ function chosenResolver() {
275
+ try {
276
+ return resolverById(globalThis.localStorage?.getItem(RESOLVER_SETTING));
277
+ } catch {
278
+ return resolverById(void 0);
279
+ }
280
+ }
281
+ var DNS_MAX_BYTES = 16384;
282
+ /**
283
+ * Lookups are shared between checks for the DNS TTL, capped: never staler than the resolver itself would
284
+ * be, so a re-check sees a removal as soon as DNS does. Files have no TTL and are only shared in flight.
285
+ */
286
+ var CACHE_MAX_MS = 3e4;
287
+ var CACHE_FILE_MS = 0;
288
+ /** A check that did not confirm the record, with a message for people. */
289
+ var DomainCheckError = class extends Error {};
290
+ /** A resolver that gave no DNS answer at all: not reached, or it refused the request. The next one may answer. */
291
+ var ResolverUnavailable = class extends DomainCheckError {};
292
+ /** One RFC 8484 GET to one resolver. The query has ID 0 and no client subnet, so it carries only the name. */
293
+ async function askResolver(resolver, name, type, options) {
294
+ let response;
295
+ try {
296
+ response = await options.fetch(`${resolver.url}?dns=${toBase64Url(encodeDnsQuery(name, type))}`, {
297
+ headers: { accept: "application/dns-message" },
298
+ maxBytes: DNS_MAX_BYTES,
299
+ signal: options.signal,
300
+ redirect: "error"
301
+ });
302
+ } catch (e) {
303
+ throw /too large/i.test(String(e)) ? new DomainCheckError(`${resolver.name} sent an answer that is too large`) : new ResolverUnavailable(`${resolver.name} could not be reached`);
304
+ }
305
+ if (response.status !== 200 || !/^application\/dns-message\b/i.test(response.contentType)) throw new ResolverUnavailable(`${resolver.name} refused the lookup`);
306
+ let answer;
307
+ try {
308
+ answer = decodeDnsResponse(response.bytes, name, type);
309
+ } catch {
310
+ throw new DomainCheckError(`${resolver.name} sent an unusable answer`);
311
+ }
312
+ if (answer.rcode !== 0 && answer.rcode !== 3) throw new DomainCheckError(`${resolver.name} could not resolve ${name}`);
313
+ return answer;
314
+ }
315
+ /**
316
+ * A lookup through the chosen resolver, and through the others in turn (in list order) only when it gives no DNS
317
+ * answer at all. Quad9, for one, cannot be read from WebKit (the Mac desktop app, Safari): its HTTP/3 answers carry
318
+ * no CORS header. An answer, even "no such name" or a failure, is never asked again elsewhere. `resolver`: who answered.
319
+ */
320
+ async function dohQuery(name, type, options) {
321
+ const chosen = resolverById(options.resolver);
322
+ let first;
323
+ for (const resolver of [chosen, ...DOH_RESOLVERS.filter((r) => r !== chosen)]) try {
324
+ return {
325
+ ...await askResolver(resolver, name, type, options),
326
+ resolver: resolver.id
327
+ };
328
+ } catch (e) {
329
+ if (!(e instanceof ResolverUnavailable) || options.signal?.aborted) throw e;
330
+ first ??= e;
331
+ }
332
+ throw first;
333
+ }
334
+ /**
335
+ * Refuses a domain whose addresses are not all public before its web server is
336
+ * contacted, so a proof cannot make a contact's app probe their own network.
337
+ * The browser resolves again when it fetches; a name that changes its answer in
338
+ * between (rebinding) is not fully excluded by this check — see the draft.
339
+ */
340
+ async function assertPublicHost(domain, options) {
341
+ const [v4, v6] = await Promise.all([dohQuery(domain, "A", options), dohQuery(domain, "AAAA", options)]);
342
+ const addresses = [...v4.answers, ...v6.answers].map((a) => a.data);
343
+ if (!addresses.length) throw new DomainCheckError(`${domain} has no address`);
344
+ if (!addresses.every(isPublicAddress)) throw new DomainCheckError(`${domain} points to a private network address, so it was not contacted`);
345
+ }
346
+ /** A well-known file: https only, no redirects, bounded. `undefined` when the server says it is not there. */
347
+ async function fetchWellKnown(domain, method, options) {
348
+ await assertPublicHost(domain, options);
349
+ const url = wellKnownUrl(domain, method);
350
+ let response;
351
+ try {
352
+ response = await options.fetch(url, {
353
+ headers: { accept: "application/json" },
354
+ maxBytes: DOMAIN_FILE_MAX_BYTES,
355
+ signal: options.signal,
356
+ redirect: "error"
357
+ });
358
+ } catch (e) {
359
+ throw new DomainCheckError(/too large/i.test(String(e)) ? `${url} is too large` : `${domain} could not be reached. The file must be served at exactly this address, without a redirect, with the header Access-Control-Allow-Origin: *`);
360
+ }
361
+ if (response.status === 404 || response.status === 410) return;
362
+ if (response.status >= 300 && response.status < 400) throw new DomainCheckError(`${domain} answered with a redirect; the file must be served at exactly ${url}`);
363
+ if (response.status !== 200) throw new DomainCheckError(`${domain} answered ${response.status}`);
364
+ try {
365
+ return new TextDecoder("utf-8", { fatal: true }).decode(response.bytes);
366
+ } catch {
367
+ throw new DomainCheckError(`${url} is not text`);
368
+ }
369
+ }
370
+ async function lookup(domain, method, options) {
371
+ const resolver = resolverById(options.resolver).id;
372
+ if (method === "dns") {
373
+ const answer = await dohQuery(dnsProofName(domain), "TXT", options);
374
+ const values = [];
375
+ for (const record of answer.answers.slice(0, 32)) try {
376
+ values.push(txtValue(record.data));
377
+ } catch {}
378
+ const ttlMs = answer.answers.length ? Math.min(...answer.answers.map((a) => a.ttl)) * 1e3 : 0;
379
+ return {
380
+ domain,
381
+ method,
382
+ resolver: answer.resolver,
383
+ records: recordsFromTxt(values),
384
+ found: answer.answers.length > 0,
385
+ dnssec: answer.authenticated,
386
+ ttlMs
387
+ };
388
+ }
389
+ const text = await fetchWellKnown(domain, method, options);
390
+ const base = {
391
+ domain,
392
+ method,
393
+ resolver,
394
+ dnssec: false,
395
+ records: [],
396
+ ttlMs: CACHE_FILE_MS
397
+ };
398
+ if (text === void 0) return {
399
+ ...base,
400
+ found: false
401
+ };
402
+ try {
403
+ return method === "nip05" ? {
404
+ ...base,
405
+ found: true,
406
+ nostrKey: nip05RootKey(text)
407
+ } : {
408
+ ...base,
409
+ found: true,
410
+ records: recordsFromWellKnown(text)
411
+ };
412
+ } catch {
413
+ throw new DomainCheckError(`${wellKnownUrl(domain, method)} is not a valid ${method === "nip05" ? "NIP-05" : "Ghostly"} file`);
414
+ }
415
+ }
416
+ var cache = /* @__PURE__ */ new Map();
417
+ /** What a domain publishes by one method. Concurrent and back-to-back checks share one lookup. */
418
+ async function lookupDomain(domainInput, method, options) {
419
+ const domain = normalizeDomain(domainInput), now = options.now ?? Date.now, resolver = resolverById(options.resolver).id;
420
+ const id = `${resolver}|${method}|${domain}`;
421
+ const hit = cache.get(id);
422
+ if (hit && hit.until > now()) {
423
+ const { ttlMs: _ttl, ...result } = await hit.result;
424
+ return result;
425
+ }
426
+ const started = now();
427
+ const entry = {
428
+ until: started + CACHE_MAX_MS,
429
+ result: lookup(domain, method, {
430
+ ...options,
431
+ resolver
432
+ })
433
+ };
434
+ cache.set(id, entry);
435
+ if (cache.size > 128) cache.delete(cache.keys().next().value);
436
+ try {
437
+ const { ttlMs, ...result } = await entry.result;
438
+ entry.until = started + Math.min(CACHE_MAX_MS, ttlMs);
439
+ return result;
440
+ } catch (e) {
441
+ if (cache.get(id) === entry) cache.delete(id);
442
+ throw e;
443
+ }
444
+ }
445
+ /** Throws unless `domain` publishes `record` by `method` (DNS or HTTPS). Returns what was found. */
446
+ async function assertDomainRecord(domain, method, record, options) {
447
+ const found = await lookupDomain(domain, method, options);
448
+ if (recordsName(found.records, record)) return found;
449
+ const where = method === "dns" ? `The TXT record at ${dnsProofName(domain)}` : wellKnownUrl(domain);
450
+ if (found.records.some((r) => r.key === record.key)) throw new DomainCheckError(`${where} is for another proof of this key`);
451
+ if (found.records.length) throw new DomainCheckError(`${where} names another Ghostly key: this proof was replaced or removed`);
452
+ throw new DomainCheckError(method === "dns" ? `No Ghostly TXT record at ${dnsProofName(domain)}${found.found ? "" : " (DNS changes can take a few minutes)"}` : found.found ? `${wellKnownUrl(domain)} names no Ghostly proof` : `${wellKnownUrl(domain)} was not found`);
453
+ }
454
+ /** Throws unless the domain's NIP-05 root name is `nostrKey`. */
455
+ async function assertNip05(domain, nostrKey, options) {
456
+ const found = await lookupDomain(domain, "nip05", options);
457
+ if (found.nostrKey === nostrKey) return found;
458
+ const url = wellKnownUrl(domain, "nip05");
459
+ throw new DomainCheckError(!found.found ? `${url} was not found` : found.nostrKey ? `${url} names another Nostr key for ${domain}` : `${url} has no root name "_" for ${domain}`);
460
+ }
461
+ //#endregion
462
+ //#region ../browser/src/proofs/atproto/resolve.ts
463
+ /**
464
+ * AT Protocol identity resolution, the network half: a DID to its document (the PLC directory for
465
+ * did:plc, the host's `/.well-known/did.json` for did:web), and a handle to a DID (DNS TXT
466
+ * `_atproto.<handle>` through the person's DNS-over-HTTPS resolver, then
467
+ * `https://<handle>/.well-known/atproto-did`). Every request goes through the contract's bounded
468
+ * fetch: HTTPS only, no redirects, a size cap, a time-out. URLs are built here from the DID or the
469
+ * handle, never taken from a proof.
470
+ */
471
+ var PLC_DIRECTORY = "https://plc.directory";
472
+ /** DID documents are small; a large answer is not one. */
473
+ var DID_DOCUMENT_MAX_BYTES = 32768;
474
+ var HANDLE_DID_MAX_BYTES = 2048;
475
+ /**
476
+ * The PLC directory. A build made for the e2e suite with VITE_ATPROTO_TEST_PLC may point it at a
477
+ * `.test` host (a name reserved for testing); a release build always uses plc.directory.
478
+ */
479
+ function plcDirectory() {
480
+ try {
481
+ return PLC_DIRECTORY;
482
+ } catch {
483
+ return PLC_DIRECTORY;
484
+ }
485
+ }
486
+ /** Where a DID's document lives. */
487
+ function didDocumentUrl(did, plc = plcDirectory()) {
488
+ const method = atprotoDidMethod(did);
489
+ if (method === "plc") return `${plc}/${did}`;
490
+ if (method === "web") return `https://${did.slice(8)}/.well-known/did.json`;
491
+ throw new Error("Not an AT Protocol DID");
492
+ }
493
+ /**
494
+ * Refuses a host the account chose (a did:web host, the PDS its document names) that points to a private
495
+ * network address: a contact's app never knocks on its own local network for someone else's proof.
496
+ */
497
+ async function assertPublicServer(host, options) {
498
+ try {
499
+ await assertPublicHost(host, {
500
+ fetch: options.fetch,
501
+ signal: options.signal,
502
+ resolver: options.resolver
503
+ });
504
+ } catch (e) {
505
+ throw new Error(`The account's server ${host} was not contacted: ${e instanceof Error ? e.message : String(e)}`);
506
+ }
507
+ }
508
+ /** A DID's document, checked (parseAtprotoDidDocument). Throws a message people can read. */
509
+ async function resolveAtprotoDid(did, options) {
510
+ const url = didDocumentUrl(did, options.plc);
511
+ if (atprotoDidMethod(did) === "web") await assertPublicServer(did.slice(8), options);
512
+ let response;
513
+ try {
514
+ response = await options.fetch(url, {
515
+ headers: { accept: "application/did+ld+json, application/json" },
516
+ maxBytes: DID_DOCUMENT_MAX_BYTES,
517
+ signal: options.signal,
518
+ redirect: "error"
519
+ });
520
+ } catch (e) {
521
+ throw new Error(/too large/i.test(String(e)) ? "The account's DID document is too large" : "The account's DID could not be resolved");
522
+ }
523
+ if (response.status === 404 || response.status === 410) throw new Error("The account's DID is not registered, or was deactivated");
524
+ if (response.status !== 200) throw new Error("The account's DID could not be resolved");
525
+ let doc;
526
+ try {
527
+ doc = JSON.parse(response.text);
528
+ } catch {
529
+ throw new Error("The account's DID document is malformed");
530
+ }
531
+ return parseAtprotoDidDocument(doc, did);
532
+ }
533
+ /**
534
+ * The DID a handle points to, or null when it points nowhere (or to two different DIDs). DNS first,
535
+ * then HTTPS, as the handle spec says. The HTTPS lookup first checks the handle's addresses are public,
536
+ * so a handle cannot make a contact's app knock on its local network.
537
+ */
538
+ async function resolveAtprotoHandle(handle, options) {
539
+ const lookup = {
540
+ fetch: options.fetch,
541
+ signal: options.signal,
542
+ resolver: options.resolver
543
+ };
544
+ try {
545
+ const answer = await dohQuery(`_atproto.${handle}`, "TXT", lookup);
546
+ const dids = /* @__PURE__ */ new Set();
547
+ for (const a of answer.answers) {
548
+ if (a.type !== 16) continue;
549
+ let text;
550
+ try {
551
+ text = txtValue(a.data);
552
+ } catch {
553
+ continue;
554
+ }
555
+ if (text.startsWith("did=")) dids.add(text.slice(4));
556
+ }
557
+ if (dids.size > 1) return null;
558
+ const [did] = dids;
559
+ if (did) return isAtprotoDid(did) ? did : null;
560
+ } catch {}
561
+ try {
562
+ await assertPublicHost(handle, lookup);
563
+ const response = await options.fetch(`https://${handle}/.well-known/atproto-did`, {
564
+ headers: { accept: "text/plain" },
565
+ maxBytes: HANDLE_DID_MAX_BYTES,
566
+ signal: options.signal,
567
+ redirect: "error"
568
+ });
569
+ if (response.status !== 200) return null;
570
+ const did = response.text.trim();
571
+ return isAtprotoDid(did) ? did : null;
572
+ } catch {
573
+ return null;
574
+ }
575
+ }
576
+ /**
577
+ * The handle to show for a DID: the one its document claims, and only if that handle resolves back to
578
+ * the same DID. Otherwise nothing: the card shows the DID. Never throws.
579
+ */
580
+ async function verifiedAtprotoHandle(doc, options) {
581
+ if (!doc.handle) return void 0;
582
+ try {
583
+ return await resolveAtprotoHandle(doc.handle, options) === doc.did ? doc.handle : void 0;
584
+ } catch {
585
+ return;
586
+ }
587
+ }
588
+ //#endregion
589
+ export { reserveAdapterIds as _, DomainCheckError as a, assertPublicHost as c, ownDid as d, setOwnDidSource as f, registeredOnchainProviders as g, registeredLightningProviders as h, verifiedAtprotoHandle as i, chosenResolver as l, registeredIdentityProviders as m, resolveAtprotoDid as n, assertDomainRecord as o, onAdaptersChanged as p, resolveAtprotoHandle as r, assertNip05 as s, assertPublicServer as t, resolverById as u, boundedIdentityFetch as v, verifyIdentity as y };
590
+
591
+ //# sourceMappingURL=resolve-BnlEajYz.js.map