@wovin/core 0.2.2 → 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.
Files changed (98) hide show
  1. package/dist/applog/datom-types.d.ts.map +1 -1
  2. package/dist/applog.js +1 -1
  3. package/dist/blockstore.js +2 -0
  4. package/dist/blockstore.js.map +1 -1
  5. package/dist/{chunk-SHUHRHOT.js → chunk-2OXLPZQI.js} +10 -3
  6. package/dist/chunk-2OXLPZQI.js.map +1 -0
  7. package/dist/{chunk-3SUFNJEZ.js → chunk-2PJFLZRC.js} +7 -2
  8. package/dist/{chunk-3SUFNJEZ.js.map → chunk-2PJFLZRC.js.map} +1 -1
  9. package/dist/chunk-64EJIJAJ.js +17 -0
  10. package/dist/chunk-64EJIJAJ.js.map +1 -0
  11. package/dist/chunk-7QEGHKR4.js +17 -0
  12. package/dist/chunk-7QEGHKR4.js.map +1 -0
  13. package/dist/{chunk-OC6Z6CQW.js → chunk-EHO2BFFY.js} +2 -2
  14. package/dist/chunk-ICBK7NC4.js +27 -0
  15. package/dist/chunk-ICBK7NC4.js.map +1 -0
  16. package/dist/{chunk-22WDFLXO.js → chunk-OKXRRWNS.js} +3 -3
  17. package/dist/{chunk-6ALNRM3J.js → chunk-Q4EMPWA3.js} +15 -8
  18. package/dist/chunk-Q4EMPWA3.js.map +1 -0
  19. package/dist/{chunk-HUIQ54TT.js → chunk-VGIACGWX.js} +3 -3
  20. package/dist/{chunk-BLF5MAWU.js → chunk-WVW4YXB5.js} +2 -2
  21. package/dist/chunk-XF4DWOAE.js +25 -0
  22. package/dist/chunk-XF4DWOAE.js.map +1 -0
  23. package/dist/index.js +7 -7
  24. package/dist/ipfs/car.d.ts.map +1 -1
  25. package/dist/ipfs.js +4 -4
  26. package/dist/ipns/gateway-resolver.d.ts +21 -0
  27. package/dist/ipns/gateway-resolver.d.ts.map +1 -0
  28. package/dist/ipns/ipns-record.d.ts +28 -7
  29. package/dist/ipns/ipns-record.d.ts.map +1 -1
  30. package/dist/ipns/ipns-w3name.d.ts +15 -0
  31. package/dist/ipns/ipns-w3name.d.ts.map +1 -0
  32. package/dist/ipns/ipns-watcher.d.ts +190 -0
  33. package/dist/ipns/ipns-watcher.d.ts.map +1 -0
  34. package/dist/ipns.d.ts +3 -0
  35. package/dist/ipns.d.ts.map +1 -1
  36. package/dist/ipns.js +488 -8
  37. package/dist/ipns.js.map +1 -1
  38. package/dist/pubsub/snap-push.d.ts +2 -2
  39. package/dist/pubsub/snap-push.d.ts.map +1 -1
  40. package/dist/pubsub.js +4 -4
  41. package/dist/query.js +3 -3
  42. package/dist/retrieve.js +4 -4
  43. package/dist/thread.js +1 -1
  44. package/dist/viewmodel/adapters/arktype.d.ts +33 -0
  45. package/dist/viewmodel/adapters/arktype.d.ts.map +1 -0
  46. package/dist/viewmodel/adapters/arktype.js +7 -0
  47. package/dist/viewmodel/adapters/arktype.js.map +1 -0
  48. package/dist/viewmodel/adapters/typebox.d.ts +35 -0
  49. package/dist/viewmodel/adapters/typebox.d.ts.map +1 -0
  50. package/dist/viewmodel/adapters/typebox.js +7 -0
  51. package/dist/viewmodel/adapters/typebox.js.map +1 -0
  52. package/dist/viewmodel/adapters/typia.d.ts +40 -0
  53. package/dist/viewmodel/adapters/typia.d.ts.map +1 -0
  54. package/dist/viewmodel/adapters/typia.js +7 -0
  55. package/dist/viewmodel/adapters/typia.js.map +1 -0
  56. package/dist/viewmodel/adapters/zod.d.ts +30 -0
  57. package/dist/viewmodel/adapters/zod.d.ts.map +1 -0
  58. package/dist/viewmodel/adapters/zod.js +7 -0
  59. package/dist/viewmodel/adapters/zod.js.map +1 -0
  60. package/dist/viewmodel/builder.d.ts +40 -0
  61. package/dist/viewmodel/builder.d.ts.map +1 -0
  62. package/dist/viewmodel/examples/all-adapters.d.ts +26 -0
  63. package/dist/viewmodel/examples/all-adapters.d.ts.map +1 -0
  64. package/dist/viewmodel/factory.d.ts +38 -0
  65. package/dist/viewmodel/factory.d.ts.map +1 -0
  66. package/dist/viewmodel/index.d.ts +10 -0
  67. package/dist/viewmodel/index.d.ts.map +1 -0
  68. package/dist/viewmodel/index.js +313 -0
  69. package/dist/viewmodel/index.js.map +1 -0
  70. package/dist/viewmodel/schema-adapter.d.ts +16 -0
  71. package/dist/viewmodel/schema-adapter.d.ts.map +1 -0
  72. package/dist/viewmodel/types.d.ts +97 -0
  73. package/dist/viewmodel/types.d.ts.map +1 -0
  74. package/package.json +29 -3
  75. package/src/applog/datom-types.ts +2 -2
  76. package/src/ipfs/car.ts +8 -2
  77. package/src/ipns/gateway-resolver.ts +63 -0
  78. package/src/ipns/ipns-record.ts +68 -17
  79. package/src/ipns/ipns-w3name.ts +103 -0
  80. package/src/ipns/ipns-watcher.ts +607 -0
  81. package/src/ipns.ts +3 -0
  82. package/src/pubsub/snap-push.ts +6 -5
  83. package/src/viewmodel/adapters/arktype.ts +44 -0
  84. package/src/viewmodel/adapters/typebox.ts +59 -0
  85. package/src/viewmodel/adapters/typia.ts +50 -0
  86. package/src/viewmodel/adapters/zod.ts +55 -0
  87. package/src/viewmodel/builder.ts +71 -0
  88. package/src/viewmodel/examples/all-adapters.ts +206 -0
  89. package/src/viewmodel/factory.ts +330 -0
  90. package/src/viewmodel/index.ts +22 -0
  91. package/src/viewmodel/schema-adapter.ts +27 -0
  92. package/src/viewmodel/types.ts +152 -0
  93. package/dist/chunk-6ALNRM3J.js.map +0 -1
  94. package/dist/chunk-SHUHRHOT.js.map +0 -1
  95. /package/dist/{chunk-OC6Z6CQW.js.map → chunk-EHO2BFFY.js.map} +0 -0
  96. /package/dist/{chunk-22WDFLXO.js.map → chunk-OKXRRWNS.js.map} +0 -0
  97. /package/dist/{chunk-HUIQ54TT.js.map → chunk-VGIACGWX.js.map} +0 -0
  98. /package/dist/{chunk-BLF5MAWU.js.map → chunk-WVW4YXB5.js.map} +0 -0
package/dist/ipns.js CHANGED
@@ -1,3 +1,46 @@
1
+ // src/ipns/gateway-resolver.ts
2
+ import { CID } from "multiformats/cid";
3
+ import { Logger } from "besonders-logger";
4
+ var { WARN, LOG, DEBUG } = Logger.setup(Logger.INFO);
5
+ async function resolveIPNSViaGateway(ipns, gateways) {
6
+ if (!ipns.startsWith("k51")) return null;
7
+ if (!gateways?.length) return null;
8
+ for (const rawGateway of gateways) {
9
+ const gateway = rawGateway.replace(/\/+$/, "");
10
+ const url = `${gateway}/ipns/${ipns}/`;
11
+ try {
12
+ DEBUG(`[resolveIPNSViaGateway] HEAD ${url}`);
13
+ const response = await fetch(url, { method: "HEAD" });
14
+ if (!response.ok) {
15
+ DEBUG(`[resolveIPNSViaGateway] ${gateway} returned ${response.status}`);
16
+ continue;
17
+ }
18
+ const roots = response.headers.get("x-ipfs-roots") ?? response.headers.get("X-Ipfs-Roots");
19
+ if (roots) {
20
+ const first = roots.split(/[\s,]+/)[0]?.trim();
21
+ if (first) {
22
+ const cid = CID.parse(first);
23
+ DEBUG(`[resolveIPNSViaGateway] resolved via ${gateway} x-ipfs-roots:`, cid.toString());
24
+ return cid;
25
+ }
26
+ }
27
+ const etag = response.headers.get("etag");
28
+ if (etag) {
29
+ const m = etag.match(/^"?([a-z0-9]+)/i);
30
+ if (m) {
31
+ const cid = CID.parse(m[1]);
32
+ DEBUG(`[resolveIPNSViaGateway] resolved via ${gateway} etag:`, cid.toString());
33
+ return cid;
34
+ }
35
+ }
36
+ WARN(`[resolveIPNSViaGateway] ${gateway} returned 200 but no usable CID header`);
37
+ } catch (err) {
38
+ WARN(`[resolveIPNSViaGateway] ${gateway} failed:`, err);
39
+ }
40
+ }
41
+ return null;
42
+ }
43
+
1
44
  // src/ipns/ipns-record.ts
2
45
  import { createIPNSRecord, marshalIPNSRecord, unmarshalIPNSRecord } from "ipns";
3
46
  import { privateKeyFromRaw } from "@libp2p/crypto/keys";
@@ -36,29 +79,466 @@ async function resolveIPNSSequence(nameServiceUrl, ipnsName) {
36
79
  }
37
80
  return 0n;
38
81
  }
39
- async function publishIPNSRecord(privateKey, cid, targets, sequenceServiceUrl = "https://name.web3.storage") {
82
+ async function publishIPNSRecord(privateKey, cid, targets) {
40
83
  const ipnsName = ipnsNameFromPrivateKey(privateKey);
41
- const currentSeq = await resolveIPNSSequence(sequenceServiceUrl, ipnsName);
42
- const sequence = currentSeq != null ? currentSeq + 1n : 0n;
84
+ const sequence = await pickNextSequence(ipnsName, targets);
43
85
  const signed = await createSignedIPNSRecord(privateKey, cid, sequence);
86
+ const publishTargets = targets.filter((t) => typeof t.publish === "function");
87
+ if (publishTargets.length === 0) {
88
+ throw new Error("No publish-capable targets supplied to publishIPNSRecord");
89
+ }
44
90
  const results = await Promise.allSettled(
45
- targets.map((t) => t.publish(ipnsName, signed.recordBytes))
91
+ publishTargets.map((t) => t.publish(ipnsName, signed.recordBytes))
46
92
  );
47
- const failures = results.map((r, i) => ({ r, name: targets[i].name })).filter(({ r }) => r.status === "rejected");
48
- if (failures.length > 0 && failures.length < targets.length) {
93
+ const failures = results.map((r, i) => ({ r, name: publishTargets[i].name })).filter(({ r }) => r.status === "rejected");
94
+ if (failures.length > 0 && failures.length < publishTargets.length) {
49
95
  for (const { r, name } of failures) {
50
96
  console.warn(`[publishIPNSRecord] target '${name}' failed:`, r.reason);
51
97
  }
52
- } else if (failures.length === targets.length) {
98
+ } else if (failures.length === publishTargets.length) {
53
99
  throw new Error(`All IPNS publish targets failed: ${failures.map(({ r, name }) => `${name}: ${r.reason}`).join("; ")}`);
54
100
  }
55
101
  return signed;
56
102
  }
103
+ async function pickNextSequence(ipnsName, targets) {
104
+ const capable = targets.filter((t) => typeof t.resolveSequence === "function");
105
+ if (capable.length === 0) {
106
+ return 0n;
107
+ }
108
+ for (const target of capable) {
109
+ try {
110
+ const current = await target.resolveSequence(ipnsName);
111
+ return current == null ? 0n : current + 1n;
112
+ } catch (err) {
113
+ console.warn(`[publishIPNSRecord] target '${target.name}' sequence resolve failed:`, err);
114
+ }
115
+ }
116
+ console.warn(`[publishIPNSRecord] no target could resolve sequence for ${ipnsName} \u2014 starting at 0n`);
117
+ return 0n;
118
+ }
119
+
120
+ // src/ipns/ipns-w3name.ts
121
+ import { Logger as Logger2 } from "besonders-logger";
122
+ import { base64pad as base64pad2 } from "multiformats/bases/base64";
123
+ import * as W3Name from "w3name";
124
+ var { WARN: WARN2, LOG: LOG2, DEBUG: DEBUG2, ERROR } = Logger2.setup(Logger2.INFO);
125
+ async function tryResolveIPNS(ipns) {
126
+ const url = `https://name.web3.storage/name/${ipns.toString()}`;
127
+ let response;
128
+ try {
129
+ response = await fetch(url, { signal: AbortSignal.timeout(3e4) });
130
+ } catch (err) {
131
+ throw ERROR("[w3name] Network error resolving IPNS:", err);
132
+ }
133
+ if (response.status === 404) {
134
+ DEBUG2("[w3name] IPNS record not found (never published):", ipns.toString());
135
+ return null;
136
+ }
137
+ if (!response.ok) {
138
+ throw ERROR(`[w3name] HTTP ${response.status} resolving IPNS:`, response.statusText);
139
+ }
140
+ const existing = await W3Name.resolve(ipns);
141
+ return existing;
142
+ }
143
+ async function publishIPNS(ipnsPrivateKey, cid) {
144
+ const TIMEOUT_MS = 3e4;
145
+ const timeout = new Promise(
146
+ (_, reject) => setTimeout(() => reject(new Error(`publishIPNS timed out after ${TIMEOUT_MS}ms`)), TIMEOUT_MS)
147
+ );
148
+ return Promise.race([_publishIPNSImpl(ipnsPrivateKey, cid), timeout]);
149
+ }
150
+ async function _publishIPNSImpl(ipnsPrivateKey, cid) {
151
+ const value = `/ipfs/${cid}`;
152
+ const ipns = await W3Name.from(ipnsPrivateKey);
153
+ let revision;
154
+ const existing = await tryResolveIPNS(ipns);
155
+ if (existing) {
156
+ revision = await W3Name.increment(existing, value);
157
+ DEBUG2("[w3name] incrementing revision for", ipns.toString());
158
+ } else {
159
+ revision = await W3Name.v0(ipns, value);
160
+ DEBUG2("[w3name] creating initial revision for", ipns.toString());
161
+ }
162
+ await W3Name.publish(revision, ipns.key);
163
+ DEBUG2("[w3name] published", cid.toString(), "to", ipns.toString());
164
+ return revision;
165
+ }
166
+ function w3nameTarget(serviceUrl = "https://name.web3.storage") {
167
+ return {
168
+ name: "w3name",
169
+ async publish(ipnsName, recordBytes) {
170
+ const res = await fetch(`${serviceUrl}/name/${ipnsName}`, {
171
+ method: "POST",
172
+ body: base64pad2.baseEncode(recordBytes)
173
+ });
174
+ if (!res.ok) throw new Error(`W3Name HTTP ${res.status}`);
175
+ }
176
+ };
177
+ }
178
+ async function generateIpnsKey() {
179
+ return W3Name.create();
180
+ }
181
+ async function getW3NamePublic(pk) {
182
+ const ipns = await W3Name.from(pk);
183
+ return ipns.toString();
184
+ }
185
+
186
+ // src/ipns/ipns-watcher.ts
187
+ import { Logger as Logger3 } from "besonders-logger";
188
+ import { CID as CID2 } from "multiformats/cid";
189
+ import ReconnectingWebSocket from "partysocket/ws";
190
+ var { WARN: WARN3, LOG: LOG3, DEBUG: DEBUG3, ERROR: ERROR2 } = Logger3.setup(Logger3.INFO);
191
+ function buildWsUrl(nameBaseUrl, name) {
192
+ const base = nameBaseUrl.replace(/\/+$/, "").replace(/^http/, "ws");
193
+ return `${base}/${name}/watch`;
194
+ }
195
+ function buildHttpUrl(nameBaseUrl, name) {
196
+ const base = nameBaseUrl.replace(/\/+$/, "");
197
+ return `${base}/${name}`;
198
+ }
199
+ function requireNameBaseUrl(nameBaseUrl, fn) {
200
+ if (!nameBaseUrl) {
201
+ throw new Error(
202
+ `[${fn}] nameBaseUrl is required. The legacy default https://name.web3.storage is shut down. Pass the base URL of a naming service that supports WebSocket subscriptions to /name/<ipns>/watch and HTTP GET on /name/<ipns>.`
203
+ );
204
+ }
205
+ return nameBaseUrl;
206
+ }
207
+ function parseCidFromIpnsValue(value) {
208
+ try {
209
+ const cidStr = value.startsWith("/ipfs/") ? value.slice(6) : value;
210
+ return CID2.parse(cidStr);
211
+ } catch {
212
+ DEBUG3("[parseCidFromIpnsValue] failed to parse:", value);
213
+ return null;
214
+ }
215
+ }
216
+ function watchNameRaw(nameBaseUrl, name, options) {
217
+ const resolvedBase = requireNameBaseUrl(nameBaseUrl, "watchNameRaw");
218
+ const url = buildWsUrl(resolvedBase, name);
219
+ DEBUG3("[watchNameRaw] connecting to", url);
220
+ const ws = new WebSocket(url);
221
+ ws.onopen = () => {
222
+ LOG3("[watchNameRaw] connected to", name);
223
+ options.onOpen?.();
224
+ };
225
+ ws.onmessage = (event) => {
226
+ try {
227
+ const record = JSON.parse(event.data);
228
+ DEBUG3("[watchNameRaw] received update for", name, record);
229
+ options.onUpdate(record);
230
+ } catch (err) {
231
+ WARN3("[watchNameRaw] failed to parse message:", event.data, err);
232
+ options.onError?.(err instanceof Error ? err : new Error(String(err)));
233
+ }
234
+ };
235
+ ws.onerror = (event) => {
236
+ WARN3("[watchNameRaw] error for", name, event);
237
+ options.onError?.(event);
238
+ };
239
+ ws.onclose = (event) => {
240
+ DEBUG3("[watchNameRaw] closed for", name, "code:", event.code);
241
+ options.onClose?.(event);
242
+ };
243
+ return {
244
+ close: () => {
245
+ DEBUG3("[watchNameRaw] closing connection for", name);
246
+ ws.close();
247
+ },
248
+ ws
249
+ };
250
+ }
251
+ var DEFAULT_LIVENESS_INTERVAL = 36e5;
252
+ var IpnsWatcher = class {
253
+ name;
254
+ nameBaseUrl;
255
+ ws;
256
+ lastKnownValue = null;
257
+ options;
258
+ isFirstConnect = true;
259
+ livenessTimer = null;
260
+ connectedAt = null;
261
+ lastMessageAt = null;
262
+ constructor(nameBaseUrl, name, options) {
263
+ this.nameBaseUrl = requireNameBaseUrl(nameBaseUrl, "IpnsWatcher");
264
+ this.name = name;
265
+ this.options = options;
266
+ const url = buildWsUrl(this.nameBaseUrl, name);
267
+ DEBUG3("[IpnsWatcher] creating for", name);
268
+ this.ws = new ReconnectingWebSocket(url, [], {
269
+ maxReconnectionDelay: 9e5,
270
+ // 15min
271
+ minReconnectionDelay: 5e3,
272
+ reconnectionDelayGrowFactor: 2,
273
+ maxRetries: Infinity,
274
+ ...options.wsOptions
275
+ });
276
+ this.ws.onopen = () => {
277
+ LOG3("[IpnsWatcher] connected to", name);
278
+ this.connectedAt = /* @__PURE__ */ new Date();
279
+ options.onConnected?.();
280
+ if (this.isFirstConnect && (options.fetchInitialState ?? false)) {
281
+ this.checkForMissedUpdates();
282
+ } else if (!this.isFirstConnect && (options.catchUpOnReconnect ?? true)) {
283
+ this.checkForMissedUpdates();
284
+ }
285
+ this.isFirstConnect = false;
286
+ if (options.livenessCheck !== false) {
287
+ this.startLivenessCheck();
288
+ }
289
+ };
290
+ this.ws.onmessage = (event) => {
291
+ this.lastMessageAt = /* @__PURE__ */ new Date();
292
+ try {
293
+ const record = JSON.parse(event.data);
294
+ DEBUG3("[IpnsWatcher] received update for", name, record);
295
+ const lastValue = this.lastKnownValue;
296
+ const isNew = record.value !== lastValue;
297
+ const cid = parseCidFromIpnsValue(record.value);
298
+ if (!isNew && !options.includeUnchanged) {
299
+ DEBUG3("[IpnsWatcher] skipping unchanged value for", name);
300
+ return;
301
+ }
302
+ this.lastKnownValue = record.value;
303
+ const update = {
304
+ value: record.value,
305
+ cid,
306
+ lastValue,
307
+ isNew,
308
+ record
309
+ };
310
+ void options.onUpdate(update);
311
+ } catch (err) {
312
+ WARN3("[IpnsWatcher] failed to parse message:", event.data, err);
313
+ options.onError?.(err instanceof Error ? err : new Error(String(err)));
314
+ }
315
+ };
316
+ this.ws.onerror = (event) => {
317
+ const errorMsg = event instanceof ErrorEvent ? event.message : "WebSocket error";
318
+ if (errorMsg === "Unexpected EOF") {
319
+ LOG3("[IpnsWatcher] error for", name, ":", errorMsg, "(auto-reconnect enabled)");
320
+ } else {
321
+ WARN3("[IpnsWatcher] error for", name, ":", errorMsg);
322
+ }
323
+ if (errorMsg !== "Unexpected EOF") {
324
+ options.onError?.(event);
325
+ }
326
+ };
327
+ this.ws.onclose = () => {
328
+ DEBUG3("[IpnsWatcher] disconnected from", name);
329
+ this.stopLivenessCheck();
330
+ this.connectedAt = null;
331
+ this.lastMessageAt = null;
332
+ options.onDisconnected?.();
333
+ };
334
+ }
335
+ /**
336
+ * Resolve current IPNS value via HTTP API to catch missed updates
337
+ */
338
+ async checkForMissedUpdates() {
339
+ try {
340
+ DEBUG3("[IpnsWatcher] checking for missed updates for", this.name);
341
+ const response = await fetch(buildHttpUrl(this.nameBaseUrl, this.name));
342
+ if (!response.ok) {
343
+ if (response.status === 404) {
344
+ DEBUG3("[IpnsWatcher] IPNS not yet published:", this.name);
345
+ return;
346
+ }
347
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
348
+ }
349
+ const record = await response.json();
350
+ const lastValue = this.lastKnownValue;
351
+ const isNew = record.value !== lastValue;
352
+ const cid = parseCidFromIpnsValue(record.value);
353
+ if (!isNew && !this.options.includeUnchanged) {
354
+ DEBUG3("[IpnsWatcher] no new updates for", this.name);
355
+ return;
356
+ }
357
+ const logMsg = lastValue === null ? "[IpnsWatcher] fetched initial state for" : "[IpnsWatcher] caught missed update for";
358
+ LOG3(logMsg, this.name, {
359
+ previous: lastValue,
360
+ current: record.value
361
+ });
362
+ this.lastKnownValue = record.value;
363
+ const update = {
364
+ value: record.value,
365
+ cid,
366
+ lastValue,
367
+ isNew,
368
+ record
369
+ };
370
+ void this.options.onUpdate(update);
371
+ } catch (err) {
372
+ WARN3("[IpnsWatcher] failed to check for missed updates:", this.name, err);
373
+ this.options.onError?.(err instanceof Error ? err : new Error(String(err)));
374
+ }
375
+ }
376
+ /**
377
+ * Start periodic liveness checks to detect zombie connections.
378
+ */
379
+ startLivenessCheck() {
380
+ this.stopLivenessCheck();
381
+ const interval = this.options.livenessCheckInterval ?? DEFAULT_LIVENESS_INTERVAL;
382
+ DEBUG3("[IpnsWatcher] starting liveness check for", this.name, "interval:", interval);
383
+ this.livenessTimer = setInterval(() => {
384
+ void this.performLivenessCheck();
385
+ }, interval);
386
+ }
387
+ /**
388
+ * Stop periodic liveness checks.
389
+ */
390
+ stopLivenessCheck() {
391
+ if (this.livenessTimer !== null) {
392
+ DEBUG3("[IpnsWatcher] stopping liveness check for", this.name);
393
+ clearInterval(this.livenessTimer);
394
+ this.livenessTimer = null;
395
+ }
396
+ }
397
+ /**
398
+ * Perform a single liveness check via HTTP.
399
+ * If the HTTP value differs from lastKnownValue, the connection is stale.
400
+ */
401
+ async performLivenessCheck() {
402
+ try {
403
+ DEBUG3("[IpnsWatcher] performing liveness check for", this.name);
404
+ const response = await fetch(buildHttpUrl(this.nameBaseUrl, this.name));
405
+ if (!response.ok) {
406
+ if (response.status === 404) {
407
+ if (this.lastKnownValue === null) {
408
+ DEBUG3("[IpnsWatcher] liveness check OK (both null) for", this.name);
409
+ return;
410
+ }
411
+ WARN3("[IpnsWatcher] liveness check inconsistent (we have value, HTTP 404) for", this.name);
412
+ return;
413
+ }
414
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
415
+ }
416
+ const record = await response.json();
417
+ if (record.value === this.lastKnownValue) {
418
+ DEBUG3("[IpnsWatcher] liveness check OK for", this.name);
419
+ return;
420
+ }
421
+ const now = /* @__PURE__ */ new Date();
422
+ const silenceDuration = this.lastMessageAt ? now.getTime() - this.lastMessageAt.getTime() : this.connectedAt ? now.getTime() - this.connectedAt.getTime() : 0;
423
+ const staleInfo = {
424
+ connectedAt: this.connectedAt ?? now,
425
+ lastMessageAt: this.lastMessageAt,
426
+ silenceDuration,
427
+ staleValue: this.lastKnownValue,
428
+ currentValue: record.value
429
+ };
430
+ WARN3("[IpnsWatcher] stale connection detected for", this.name, {
431
+ connectedAt: staleInfo.connectedAt.toISOString(),
432
+ lastMessageAt: staleInfo.lastMessageAt?.toISOString() ?? "never",
433
+ silenceDuration: `${Math.round(silenceDuration / 1e3)}s`,
434
+ staleValue: staleInfo.staleValue,
435
+ currentValue: staleInfo.currentValue
436
+ });
437
+ this.options.onStaleConnection?.(staleInfo);
438
+ const lastValue = this.lastKnownValue;
439
+ const cid = parseCidFromIpnsValue(record.value);
440
+ this.lastKnownValue = record.value;
441
+ const update = {
442
+ value: record.value,
443
+ cid,
444
+ lastValue,
445
+ isNew: true,
446
+ record
447
+ };
448
+ void this.options.onUpdate(update);
449
+ LOG3("[IpnsWatcher] forcing reconnect due to stale connection for", this.name);
450
+ this.ws.reconnect();
451
+ } catch (err) {
452
+ WARN3("[IpnsWatcher] liveness check failed for", this.name, err);
453
+ }
454
+ }
455
+ /**
456
+ * Manually start/reconnect the WebSocket.
457
+ * Only needed if you used `wsOptions: { startClosed: true }`.
458
+ */
459
+ start() {
460
+ LOG3("[IpnsWatcher] starting watcher for", this.name);
461
+ this.ws.reconnect();
462
+ }
463
+ /**
464
+ * Alias for close() - for backward compatibility
465
+ */
466
+ stop() {
467
+ this.close();
468
+ }
469
+ /**
470
+ * Close the WebSocket connection and stop watching
471
+ */
472
+ close() {
473
+ LOG3("[IpnsWatcher] closing watcher for", this.name);
474
+ this.stopLivenessCheck();
475
+ this.ws.close();
476
+ }
477
+ /**
478
+ * Get the last known IPNS value
479
+ */
480
+ get lastValue() {
481
+ return this.lastKnownValue;
482
+ }
483
+ /**
484
+ * Get the WebSocket ready state
485
+ */
486
+ get readyState() {
487
+ return this.ws.readyState;
488
+ }
489
+ };
490
+ function watchName(nameBaseUrl, name, options) {
491
+ return new IpnsWatcher(nameBaseUrl, name, options);
492
+ }
493
+ async function* watchNameIterator(nameBaseUrl, name, signal) {
494
+ const queue = [];
495
+ let resolve2 = null;
496
+ let error = null;
497
+ const watcher = new IpnsWatcher(nameBaseUrl, name, {
498
+ onUpdate: (update) => {
499
+ queue.push(update);
500
+ resolve2?.();
501
+ },
502
+ onError: (err) => {
503
+ error = err instanceof Error ? err : new Error("WebSocket error");
504
+ resolve2?.();
505
+ }
506
+ });
507
+ signal?.addEventListener("abort", () => {
508
+ watcher.close();
509
+ });
510
+ try {
511
+ while (!signal?.aborted) {
512
+ if (queue.length > 0) {
513
+ yield queue.shift();
514
+ } else if (error) {
515
+ WARN3("[watchNameIterator] error occurred, continuing:", error);
516
+ error = null;
517
+ } else {
518
+ await new Promise((r) => {
519
+ resolve2 = r;
520
+ });
521
+ resolve2 = null;
522
+ }
523
+ }
524
+ } finally {
525
+ watcher.close();
526
+ }
527
+ }
57
528
  export {
529
+ IpnsWatcher,
58
530
  createSignedIPNSRecord,
531
+ generateIpnsKey,
532
+ getW3NamePublic,
59
533
  ipnsNameFromPrivateKey,
534
+ publishIPNS,
60
535
  publishIPNSRecord,
61
536
  resolveIPNSSequence,
62
- unmarshalIPNSRecord
537
+ resolveIPNSViaGateway,
538
+ unmarshalIPNSRecord,
539
+ w3nameTarget,
540
+ watchName,
541
+ watchNameIterator,
542
+ watchNameRaw
63
543
  };
64
544
  //# sourceMappingURL=ipns.js.map
package/dist/ipns.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ipns/ipns-record.ts"],"sourcesContent":["import { createIPNSRecord, marshalIPNSRecord, unmarshalIPNSRecord } from 'ipns'\nimport { privateKeyFromRaw } from '@libp2p/crypto/keys'\nimport { base36 } from 'multiformats/bases/base36'\nimport { base64pad } from 'multiformats/bases/base64'\nimport type { CID } from 'multiformats/cid'\n\nexport interface SignedIPNSRecord {\n\trecordBytes: Uint8Array // marshalled protobuf, signed — the wire format\n\tipnsName: string // k51... string\n\tvalue: string // /ipfs/<cid>\n\tsequence: bigint\n}\n\n/** Derive IPNS name string (k51...) from Ed25519 private key bytes */\nexport function ipnsNameFromPrivateKey(privKeyBytes: Uint8Array): string {\n\tconst privKey = privateKeyFromRaw(privKeyBytes)\n\treturn privKey.publicKey.toCID().toString(base36)\n}\n\n/**\n * Create a signed IPNS record (protobuf wire format).\n * Same bytes can be published to any naming service.\n */\nexport async function createSignedIPNSRecord(\n\tprivateKey: Uint8Array,\n\tcid: CID,\n\tsequence: bigint,\n\tlifetimeMs = 365 * 24 * 60 * 60 * 1000, // 1 year\n): Promise<SignedIPNSRecord> {\n\tconst privKey = privateKeyFromRaw(privateKey)\n\tconst value = `/ipfs/${cid.toV1()}`\n\tconst record = await createIPNSRecord(privKey, value, sequence, lifetimeMs)\n\tconst recordBytes = marshalIPNSRecord(record)\n\tconst ipnsName = privKey.publicKey.toCID().toString(base36)\n\treturn { recordBytes, ipnsName, value, sequence }\n}\n\nexport { unmarshalIPNSRecord }\n\n/** A target that can receive a signed IPNS record */\nexport interface IPNSPublishTarget {\n\tname: string\n\tpublish(ipnsName: string, recordBytes: Uint8Array): Promise<void>\n}\n\n/**\n * Resolve current IPNS sequence number from a naming service.\n * Returns null if the name was never published (404).\n * Throws on network/server errors.\n */\nexport async function resolveIPNSSequence(\n\tnameServiceUrl: string,\n\tipnsName: string,\n): Promise<bigint | null> {\n\tconst url = `${nameServiceUrl}/name/${ipnsName}`\n\tlet response: Response\n\ttry {\n\t\tresponse = await fetch(url)\n\t} catch (err) {\n\t\tthrow new Error(`Network error resolving IPNS ${ipnsName}: ${err}`)\n\t}\n\n\tif (response.status === 404) return null\n\tif (!response.ok) {\n\t\tthrow new Error(`HTTP ${response.status} resolving IPNS ${ipnsName}: ${response.statusText}`)\n\t}\n\n\tconst { record, value } = await response.json()\n\tif (!record && !value) return null // never published\n\n\t// If raw record is available, unmarshal to get sequence\n\tif (record) {\n\t\tconst bytes = base64pad.baseDecode(record)\n\t\tconst entry = unmarshalIPNSRecord(bytes)\n\t\treturn entry.sequence\n\t}\n\n\t// Some servers return value but not raw record — can't get sequence\n\treturn 0n\n}\n\n/**\n * Create a signed IPNS record and publish to all configured targets.\n * Resolves sequence from sequenceServiceUrl, creates record once, fans out.\n * Throws if ALL targets fail; warns on partial failure.\n */\nexport async function publishIPNSRecord(\n\tprivateKey: Uint8Array,\n\tcid: CID,\n\ttargets: IPNSPublishTarget[],\n\tsequenceServiceUrl = 'https://name.web3.storage',\n): Promise<SignedIPNSRecord> {\n\tconst ipnsName = ipnsNameFromPrivateKey(privateKey)\n\tconst currentSeq = await resolveIPNSSequence(sequenceServiceUrl, ipnsName)\n\tconst sequence = currentSeq != null ? currentSeq + 1n : 0n\n\tconst signed = await createSignedIPNSRecord(privateKey, cid, sequence)\n\n\tconst results = await Promise.allSettled(\n\t\ttargets.map(t => t.publish(ipnsName, signed.recordBytes))\n\t)\n\tconst failures = results\n\t\t.map((r, i) => ({ r, name: targets[i].name }))\n\t\t.filter(({ r }) => r.status === 'rejected') as { r: PromiseRejectedResult; name: string }[]\n\n\tif (failures.length > 0 && failures.length < targets.length) {\n\t\t// Partial failure — log but don't throw\n\t\tfor (const { r, name } of failures) {\n\t\t\tconsole.warn(`[publishIPNSRecord] target '${name}' failed:`, r.reason)\n\t\t}\n\t} else if (failures.length === targets.length) {\n\t\tthrow new Error(`All IPNS publish targets failed: ${failures.map(({ r, name }) => `${name}: ${r.reason}`).join('; ')}`)\n\t}\n\n\treturn signed\n}\n"],"mappings":";AAAA,SAAS,kBAAkB,mBAAmB,2BAA2B;AACzE,SAAS,yBAAyB;AAClC,SAAS,cAAc;AACvB,SAAS,iBAAiB;AAWnB,SAAS,uBAAuB,cAAkC;AACxE,QAAM,UAAU,kBAAkB,YAAY;AAC9C,SAAO,QAAQ,UAAU,MAAM,EAAE,SAAS,MAAM;AACjD;AAMA,eAAsB,uBACrB,YACA,KACA,UACA,aAAa,MAAM,KAAK,KAAK,KAAK,KACN;AAC5B,QAAM,UAAU,kBAAkB,UAAU;AAC5C,QAAM,QAAQ,SAAS,IAAI,KAAK,CAAC;AACjC,QAAM,SAAS,MAAM,iBAAiB,SAAS,OAAO,UAAU,UAAU;AAC1E,QAAM,cAAc,kBAAkB,MAAM;AAC5C,QAAM,WAAW,QAAQ,UAAU,MAAM,EAAE,SAAS,MAAM;AAC1D,SAAO,EAAE,aAAa,UAAU,OAAO,SAAS;AACjD;AAeA,eAAsB,oBACrB,gBACA,UACyB;AACzB,QAAM,MAAM,GAAG,cAAc,SAAS,QAAQ;AAC9C,MAAI;AACJ,MAAI;AACH,eAAW,MAAM,MAAM,GAAG;AAAA,EAC3B,SAAS,KAAK;AACb,UAAM,IAAI,MAAM,gCAAgC,QAAQ,KAAK,GAAG,EAAE;AAAA,EACnE;AAEA,MAAI,SAAS,WAAW,IAAK,QAAO;AACpC,MAAI,CAAC,SAAS,IAAI;AACjB,UAAM,IAAI,MAAM,QAAQ,SAAS,MAAM,mBAAmB,QAAQ,KAAK,SAAS,UAAU,EAAE;AAAA,EAC7F;AAEA,QAAM,EAAE,QAAQ,MAAM,IAAI,MAAM,SAAS,KAAK;AAC9C,MAAI,CAAC,UAAU,CAAC,MAAO,QAAO;AAG9B,MAAI,QAAQ;AACX,UAAM,QAAQ,UAAU,WAAW,MAAM;AACzC,UAAM,QAAQ,oBAAoB,KAAK;AACvC,WAAO,MAAM;AAAA,EACd;AAGA,SAAO;AACR;AAOA,eAAsB,kBACrB,YACA,KACA,SACA,qBAAqB,6BACO;AAC5B,QAAM,WAAW,uBAAuB,UAAU;AAClD,QAAM,aAAa,MAAM,oBAAoB,oBAAoB,QAAQ;AACzE,QAAM,WAAW,cAAc,OAAO,aAAa,KAAK;AACxD,QAAM,SAAS,MAAM,uBAAuB,YAAY,KAAK,QAAQ;AAErE,QAAM,UAAU,MAAM,QAAQ;AAAA,IAC7B,QAAQ,IAAI,OAAK,EAAE,QAAQ,UAAU,OAAO,WAAW,CAAC;AAAA,EACzD;AACA,QAAM,WAAW,QACf,IAAI,CAAC,GAAG,OAAO,EAAE,GAAG,MAAM,QAAQ,CAAC,EAAE,KAAK,EAAE,EAC5C,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,UAAU;AAE3C,MAAI,SAAS,SAAS,KAAK,SAAS,SAAS,QAAQ,QAAQ;AAE5D,eAAW,EAAE,GAAG,KAAK,KAAK,UAAU;AACnC,cAAQ,KAAK,+BAA+B,IAAI,aAAa,EAAE,MAAM;AAAA,IACtE;AAAA,EACD,WAAW,SAAS,WAAW,QAAQ,QAAQ;AAC9C,UAAM,IAAI,MAAM,oCAAoC,SAAS,IAAI,CAAC,EAAE,GAAG,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,IAAI,CAAC,EAAE;AAAA,EACvH;AAEA,SAAO;AACR;","names":[]}
1
+ {"version":3,"sources":["../src/ipns/gateway-resolver.ts","../src/ipns/ipns-record.ts","../src/ipns/ipns-w3name.ts","../src/ipns/ipns-watcher.ts"],"sourcesContent":["import { CID } from 'multiformats/cid'\nimport { Logger } from 'besonders-logger'\n\nconst { WARN, LOG, DEBUG } = Logger.setup(Logger.INFO) // eslint-disable-line unused-imports/no-unused-vars\n\n/**\n * Resolve an IPNS name to a CID using a public IPFS gateway's HTTP HEAD response.\n *\n * Mechanism: per the IPFS HTTP Gateway spec, `HEAD <gateway>/ipns/<name>/` returns the\n * IPNS-resolved root CID in the `X-Ipfs-Roots` response header (space-separated, ordered\n * from root to leaf). The first entry is the IPNS-resolved CID.\n *\n * This works against any gateway that follows the spec and exposes CORS for HEAD\n * (most public gateways do, e.g. ipfs.zt.ax, ipfs.io, dweb.link).\n *\n * The legacy w3name HTTP endpoint (`GET <base>/name/<ipns>` returning `{value: \"/ipfs/<cid>\"}`)\n * is also supported here for back-compat with self-hosted w3name-like services — but the\n * X-Ipfs-Roots path is preferred since it's the standardised gateway mechanism.\n *\n * @param ipns - The IPNS name (k51... string)\n * @param gateways - List of gateway base URLs (e.g. `[\"https://ipfs.zt.ax\"]`)\n * @returns The resolved CID, or null if no gateway could resolve the name\n */\nexport async function resolveIPNSViaGateway(ipns: string, gateways: string[]): Promise<CID | null> {\n\tif (!ipns.startsWith('k51')) return null // only IPNS libp2p-key names go through gateway\n\tif (!gateways?.length) return null\n\n\tfor (const rawGateway of gateways) {\n\t\tconst gateway = rawGateway.replace(/\\/+$/, '')\n\t\tconst url = `${gateway}/ipns/${ipns}/`\n\t\ttry {\n\t\t\tDEBUG(`[resolveIPNSViaGateway] HEAD ${url}`)\n\t\t\tconst response = await fetch(url, { method: 'HEAD' })\n\t\t\tif (!response.ok) {\n\t\t\t\tDEBUG(`[resolveIPNSViaGateway] ${gateway} returned ${response.status}`)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tconst roots = response.headers.get('x-ipfs-roots') ?? response.headers.get('X-Ipfs-Roots')\n\t\t\tif (roots) {\n\t\t\t\tconst first = roots.split(/[\\s,]+/)[0]?.trim()\n\t\t\t\tif (first) {\n\t\t\t\t\tconst cid = CID.parse(first)\n\t\t\t\t\tDEBUG(`[resolveIPNSViaGateway] resolved via ${gateway} x-ipfs-roots:`, cid.toString())\n\t\t\t\t\treturn cid\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Some gateways omit X-Ipfs-Roots but put the CID in etag (e.g. `<cid>.dag-json`)\n\t\t\tconst etag = response.headers.get('etag')\n\t\t\tif (etag) {\n\t\t\t\tconst m = etag.match(/^\"?([a-z0-9]+)/i)\n\t\t\t\tif (m) {\n\t\t\t\t\tconst cid = CID.parse(m[1])\n\t\t\t\t\tDEBUG(`[resolveIPNSViaGateway] resolved via ${gateway} etag:`, cid.toString())\n\t\t\t\t\treturn cid\n\t\t\t\t}\n\t\t\t}\n\t\t\tWARN(`[resolveIPNSViaGateway] ${gateway} returned 200 but no usable CID header`)\n\t\t} catch (err) {\n\t\t\tWARN(`[resolveIPNSViaGateway] ${gateway} failed:`, err)\n\t\t}\n\t}\n\treturn null\n}\n","import { createIPNSRecord, marshalIPNSRecord, unmarshalIPNSRecord } from 'ipns'\nimport { privateKeyFromRaw } from '@libp2p/crypto/keys'\nimport { base36 } from 'multiformats/bases/base36'\nimport { base64pad } from 'multiformats/bases/base64'\nimport type { CID } from 'multiformats/cid'\n\nexport interface SignedIPNSRecord {\n\trecordBytes: Uint8Array // marshalled protobuf, signed — the wire format\n\tipnsName: string // k51... string\n\tvalue: string // /ipfs/<cid>\n\tsequence: bigint\n}\n\n/** Derive IPNS name string (k51...) from Ed25519 private key bytes */\nexport function ipnsNameFromPrivateKey(privKeyBytes: Uint8Array): string {\n\tconst privKey = privateKeyFromRaw(privKeyBytes)\n\treturn privKey.publicKey.toCID().toString(base36)\n}\n\n/**\n * Create a signed IPNS record (protobuf wire format).\n * Same bytes can be published to any naming service.\n */\nexport async function createSignedIPNSRecord(\n\tprivateKey: Uint8Array,\n\tcid: CID,\n\tsequence: bigint,\n\tlifetimeMs = 365 * 24 * 60 * 60 * 1000, // 1 year\n): Promise<SignedIPNSRecord> {\n\tconst privKey = privateKeyFromRaw(privateKey)\n\tconst value = `/ipfs/${cid.toV1()}`\n\tconst record = await createIPNSRecord(privKey, value, sequence, lifetimeMs)\n\tconst recordBytes = marshalIPNSRecord(record)\n\tconst ipnsName = privKey.publicKey.toCID().toString(base36)\n\treturn { recordBytes, ipnsName, value, sequence }\n}\n\nexport { unmarshalIPNSRecord }\n\n/**\n * A target that can receive a signed IPNS record, advertise its current\n * sequence, or both.\n *\n * - `publish` is called by `publishIPNSRecord` to actually store the record\n * on the target's backing service. Targets without `publish` are skipped\n * during the fan-out (e.g. a sequence-only source).\n * - `resolveSequence` is consulted by `publishIPNSRecord` to compute the\n * next IPNS sequence. The first target to return a value (including\n * `null` for \"never published\") wins. Throw to indicate a transient\n * error — the caller will try the next target.\n *\n * Most real targets (e.g. a storage connector) provide both. A simple\n * \"track sequence in localStorage\" target only needs `resolveSequence`.\n */\nexport interface IPNSPublishTarget {\n\tname: string\n\tpublish?(ipnsName: string, recordBytes: Uint8Array): Promise<void>\n\tresolveSequence?(ipnsName: string): Promise<bigint | null>\n}\n\n/**\n * Resolve the current IPNS sequence from a generic naming service.\n * Returns null if the name was never published (404).\n * Throws on network/server errors so the caller can try a different target.\n */\nexport async function resolveIPNSSequence(\n\tnameServiceUrl: string,\n\tipnsName: string,\n): Promise<bigint | null> {\n\tconst url = `${nameServiceUrl}/name/${ipnsName}`\n\tlet response: Response\n\ttry {\n\t\tresponse = await fetch(url)\n\t} catch (err) {\n\t\tthrow new Error(`Network error resolving IPNS ${ipnsName}: ${err}`)\n\t}\n\n\tif (response.status === 404) return null\n\tif (!response.ok) {\n\t\tthrow new Error(`HTTP ${response.status} resolving IPNS ${ipnsName}: ${response.statusText}`)\n\t}\n\n\tconst { record, value } = await response.json()\n\tif (!record && !value) return null // never published\n\n\t// If raw record is available, unmarshal to get sequence\n\tif (record) {\n\t\tconst bytes = base64pad.baseDecode(record)\n\t\tconst entry = unmarshalIPNSRecord(bytes)\n\t\treturn entry.sequence\n\t}\n\n\t// Server only returned a value, no raw record — can't know the exact sequence.\n\t// Most services still accept this as \"previous published\"; treat as seq=0n.\n\treturn 0n\n}\n\n/**\n * Create a signed IPNS record and publish to all configured targets.\n *\n * Sequence resolution: walks `targets` in order asking each one with a\n * `resolveSequence` method. The first target to successfully return a value\n * (including `null` for \"never published\") determines the next sequence.\n * If every target throws or none supports `resolveSequence`, falls back to 0n.\n *\n * Publish fan-out: only targets with a `publish` method are called.\n * Throws if every publish-capable target fails; partial failures are logged.\n */\nexport async function publishIPNSRecord(\n\tprivateKey: Uint8Array,\n\tcid: CID,\n\ttargets: IPNSPublishTarget[],\n): Promise<SignedIPNSRecord> {\n\tconst ipnsName = ipnsNameFromPrivateKey(privateKey)\n\tconst sequence = await pickNextSequence(ipnsName, targets)\n\tconst signed = await createSignedIPNSRecord(privateKey, cid, sequence)\n\n\tconst publishTargets = targets.filter(t => typeof t.publish === 'function')\n\tif (publishTargets.length === 0) {\n\t\tthrow new Error('No publish-capable targets supplied to publishIPNSRecord')\n\t}\n\n\tconst results = await Promise.allSettled(\n\t\tpublishTargets.map(t => t.publish!(ipnsName, signed.recordBytes)),\n\t)\n\tconst failures = results\n\t\t.map((r, i) => ({ r, name: publishTargets[i].name }))\n\t\t.filter(({ r }) => r.status === 'rejected') as { r: PromiseRejectedResult; name: string }[]\n\n\tif (failures.length > 0 && failures.length < publishTargets.length) {\n\t\t// Partial failure — log but don't throw\n\t\tfor (const { r, name } of failures) {\n\t\t\tconsole.warn(`[publishIPNSRecord] target '${name}' failed:`, r.reason)\n\t\t}\n\t} else if (failures.length === publishTargets.length) {\n\t\tthrow new Error(`All IPNS publish targets failed: ${failures.map(({ r, name }) => `${name}: ${r.reason}`).join('; ')}`)\n\t}\n\n\treturn signed\n}\n\n/**\n * Walk targets, asking each to resolve the current IPNS sequence. First success wins.\n * Falls back to 0n if no target can answer.\n */\nasync function pickNextSequence(\n\tipnsName: string,\n\ttargets: IPNSPublishTarget[],\n): Promise<bigint> {\n\tconst capable = targets.filter(t => typeof t.resolveSequence === 'function')\n\tif (capable.length === 0) {\n\t\treturn 0n\n\t}\n\n\tfor (const target of capable) {\n\t\ttry {\n\t\t\tconst current = await target.resolveSequence!(ipnsName)\n\t\t\treturn current == null ? 0n : current + 1n\n\t\t} catch (err) {\n\t\t\tconsole.warn(`[publishIPNSRecord] target '${target.name}' sequence resolve failed:`, err)\n\t\t}\n\t}\n\n\tconsole.warn(`[publishIPNSRecord] no target could resolve sequence for ${ipnsName} — starting at 0n`)\n\treturn 0n\n}\n","import type { IPNSPublishTarget } from '@wovin/core/ipns'\nimport { Logger } from 'besonders-logger'\nimport { base64pad } from 'multiformats/bases/base64'\nimport { CID } from 'multiformats/cid'\nimport * as W3Name from 'w3name'\n\nconst { WARN, LOG, DEBUG, ERROR } = Logger.setup(Logger.INFO)\n\n/**\n * Try to resolve IPNS name to get existing revision.\n * Returns null if record doesn't exist (404).\n * Throws on network errors or server errors.\n *\n * This does a custom HTTP check first to distinguish 404 from network errors,\n * then delegates to W3Name.resolve() for validation if record exists.\n */\nasync function tryResolveIPNS(ipns: W3Name.WritableName): Promise<W3Name.Revision | null> {\n\tconst url = `https://name.web3.storage/name/${ipns.toString()}`\n\n\tlet response: Response\n\ttry {\n\t\tresponse = await fetch(url, { signal: AbortSignal.timeout(30_000) })\n\t} catch (err) {\n\t\t// Network error (no connection, DNS failure, etc.)\n\t\tthrow ERROR('[w3name] Network error resolving IPNS:', err)\n\t}\n\n\t// 404 = record never published\n\tif (response.status === 404) {\n\t\tDEBUG('[w3name] IPNS record not found (never published):', ipns.toString())\n\t\treturn null\n\t}\n\n\t// Other HTTP errors (5xx server error, etc.)\n\tif (!response.ok) {\n\t\tthrow ERROR(`[w3name] HTTP ${response.status} resolving IPNS:`, response.statusText)\n\t}\n\n\t// Success - use W3Name.resolve to get validated Revision\n\t// (We could parse the record ourselves, but W3Name does validation/signature checks)\n\tconst existing = await W3Name.resolve(ipns)\n\treturn existing\n}\n\n/**\n * Publish CID to IPNS, automatically handling increment vs v0.\n * Returns the revision for further processing (e.g., Kubo integration).\n */\nexport async function publishIPNS(ipnsPrivateKey: Uint8Array, cid: CID): Promise<W3Name.Revision> {\n\tconst TIMEOUT_MS = 30_000\n\tconst timeout = new Promise<never>((_, reject) =>\n\t\tsetTimeout(() => reject(new Error(`publishIPNS timed out after ${TIMEOUT_MS}ms`)), TIMEOUT_MS),\n\t)\n\treturn Promise.race([_publishIPNSImpl(ipnsPrivateKey, cid), timeout])\n}\n\nasync function _publishIPNSImpl(ipnsPrivateKey: Uint8Array, cid: CID): Promise<W3Name.Revision> {\n\tconst value = `/ipfs/${cid}`\n\tconst ipns = await W3Name.from(ipnsPrivateKey)\n\n\tlet revision: W3Name.Revision\n\tconst existing = await tryResolveIPNS(ipns)\n\n\tif (existing) {\n\t\t// Record exists - increment sequence number\n\t\trevision = await W3Name.increment(existing, value)\n\t\tDEBUG('[w3name] incrementing revision for', ipns.toString())\n\t} else {\n\t\t// First publish - use v0\n\t\trevision = await W3Name.v0(ipns, value)\n\t\tDEBUG('[w3name] creating initial revision for', ipns.toString())\n\t}\n\n\tawait W3Name.publish(revision, ipns.key)\n\tDEBUG('[w3name] published', cid.toString(), 'to', ipns.toString())\n\n\treturn revision // Return for Kubo integration or other uses\n}\n\n/**\n * Create an IPNSPublishTarget that publishes to W3Name service via HTTP POST.\n */\nexport function w3nameTarget(serviceUrl = 'https://name.web3.storage'): IPNSPublishTarget {\n\treturn {\n\t\tname: 'w3name',\n\t\tasync publish(ipnsName: string, recordBytes: Uint8Array) {\n\t\t\tconst res = await fetch(`${serviceUrl}/name/${ipnsName}`, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: base64pad.baseEncode(recordBytes),\n\t\t\t})\n\t\t\tif (!res.ok) throw new Error(`W3Name HTTP ${res.status}`)\n\t\t},\n\t}\n}\n\nexport async function generateIpnsKey() {\n\treturn W3Name.create() // Returns W3Name.WritableName type\n}\n\nexport async function getW3NamePublic(pk: Uint8Array) {\n\tconst ipns = await W3Name.from(pk)\n\treturn ipns.toString()\n}\n","import { Logger } from 'besonders-logger'\nimport { CID } from 'multiformats/cid'\nimport ReconnectingWebSocket, { type Options as PartysocketOptions } from 'partysocket/ws'\n\nexport type { PartysocketOptions }\n\nconst { WARN, LOG, DEBUG, ERROR } = Logger.setup(Logger.INFO) // eslint-disable-line unused-imports/no-unused-vars\n\n/**\n * `nameBaseUrl` is required for both `watchNameRaw` and `IpnsWatcher`. The\n * legacy `https://name.web3.storage` endpoint (used by the old hardcoded\n * `NAME_WS_URL`/`NAME_HTTP_URL` constants) is **shut down** — these classes\n * will not work without a real, configured naming service that supports\n * WebSocket subscriptions to `/name/<ipns>/watch` and HTTP GET on `/name/<ipns>`.\n *\n * As of writing no such public service exists, so most callers should expect\n * these to fail at runtime and handle the error in their `onError` handler.\n */\nfunction buildWsUrl(nameBaseUrl: string, name: string) {\n\tconst base = nameBaseUrl.replace(/\\/+$/, '').replace(/^http/, 'ws')\n\treturn `${base}/${name}/watch`\n}\nfunction buildHttpUrl(nameBaseUrl: string, name: string) {\n\tconst base = nameBaseUrl.replace(/\\/+$/, '')\n\treturn `${base}/${name}`\n}\n\nfunction requireNameBaseUrl(nameBaseUrl: string | undefined, fn: string): string {\n\tif (!nameBaseUrl) {\n\t\tthrow new Error(\n\t\t\t`[${fn}] nameBaseUrl is required. The legacy default `\n\t\t\t+ `https://name.web3.storage is shut down. Pass the base URL of a naming `\n\t\t\t+ `service that supports WebSocket subscriptions to /name/<ipns>/watch `\n\t\t\t+ `and HTTP GET on /name/<ipns>.`,\n\t\t)\n\t}\n\treturn nameBaseUrl\n}\n\nexport interface W3NameRecord {\n\tvalue: string // e.g. \"/ipfs/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi\"\n\tseq?: number\n\tvalidity?: string\n}\n\n/**\n * Debug info provided when a stale WebSocket connection is detected.\n */\nexport interface StaleConnectionInfo {\n\t/** When the WebSocket connection was established */\n\tconnectedAt: Date\n\t/** When we last received a WebSocket message */\n\tlastMessageAt: Date | null\n\t/** How long since last message (ms) */\n\tsilenceDuration: number\n\t/** The stale value from WebSocket */\n\tstaleValue: string | null\n\t/** The current value from HTTP */\n\tcurrentValue: string\n}\n\n/**\n * Enriched IPNS update with parsed CID and change detection.\n * Backwards compatible - `.value` still works as before.\n */\nexport interface IpnsUpdate {\n\t/** Raw IPNS value string (e.g. '/ipfs/bafy...') — same as W3NameRecord.value */\n\tvalue: string\n\t/** Parsed CID if value is valid IPFS path, null otherwise */\n\tcid: CID | null\n\t/** Previous value (null on first update) */\n\tlastValue: string | null\n\t/** Whether this is a change from lastValue */\n\tisNew: boolean\n\t/** Original W3NameRecord for access to seq/validity */\n\trecord: W3NameRecord\n}\n\n/**\n * Parse CID from IPNS value string (e.g. \"/ipfs/bafybeig...\")\n * @returns CID if valid, null otherwise\n */\nfunction parseCidFromIpnsValue(value: string): CID | null {\n\ttry {\n\t\t// Strip /ipfs/ prefix if present\n\t\tconst cidStr = value.startsWith('/ipfs/') ? value.slice(6) : value\n\t\treturn CID.parse(cidStr)\n\t} catch {\n\t\tDEBUG('[parseCidFromIpnsValue] failed to parse:', value)\n\t\treturn null\n\t}\n}\n\nexport interface WatchRawOptions {\n\t/** Called when the IPNS record is updated */\n\tonUpdate: (record: W3NameRecord) => void\n\t/** Called when an error occurs */\n\tonError?: (error: Event | Error) => void\n\t/** Called when the connection is opened */\n\tonOpen?: () => void\n\t/** Called when the connection is closed */\n\tonClose?: (event: CloseEvent) => void\n}\n\nexport interface WatchRawSubscription {\n\t/** Close the WebSocket connection */\n\tclose: () => void\n\t/** The underlying WebSocket instance */\n\tws: WebSocket\n}\n\n/**\n * Low-level WebSocket watcher for IPNS (no reconnect logic).\n * Use this when you want full control over connection lifecycle.\n * For most cases, prefer `watchName` or `IpnsWatcher` which handle reconnection.\n *\n * @param nameBaseUrl - Base URL of a naming service that supports `/name/<ipns>/watch` (WebSocket) and `/name/<ipns>` (HTTP)\n * @param name - The IPNS name/key to watch\n * @param options - Callback options\n * @returns Subscription with close() and ws\n *\n * @example\n * ```ts\n * const sub = watchNameRaw('https://name.example.com', 'k51qzi5u...', {\n * onUpdate: (record) => console.log('Update:', record.value),\n * onClose: () => console.log('Disconnected - handle reconnect yourself'),\n * })\n * ```\n */\nexport function watchNameRaw(nameBaseUrl: string, name: string, options: WatchRawOptions): WatchRawSubscription {\n\tconst resolvedBase = requireNameBaseUrl(nameBaseUrl, 'watchNameRaw')\n\tconst url = buildWsUrl(resolvedBase, name)\n\tDEBUG('[watchNameRaw] connecting to', url)\n\n\tconst ws = new WebSocket(url)\n\n\tws.onopen = () => {\n\t\tLOG('[watchNameRaw] connected to', name)\n\t\toptions.onOpen?.()\n\t}\n\n\tws.onmessage = (event) => {\n\t\ttry {\n\t\t\tconst record: W3NameRecord = JSON.parse(event.data)\n\t\t\tDEBUG('[watchNameRaw] received update for', name, record)\n\t\t\toptions.onUpdate(record)\n\t\t} catch (err) {\n\t\t\tWARN('[watchNameRaw] failed to parse message:', event.data, err)\n\t\t\toptions.onError?.(err instanceof Error ? err : new Error(String(err)))\n\t\t}\n\t}\n\n\tws.onerror = (event) => {\n\t\tWARN('[watchNameRaw] error for', name, event)\n\t\toptions.onError?.(event)\n\t}\n\n\tws.onclose = (event) => {\n\t\tDEBUG('[watchNameRaw] closed for', name, 'code:', event.code)\n\t\toptions.onClose?.(event)\n\t}\n\n\treturn {\n\t\tclose: () => {\n\t\t\tDEBUG('[watchNameRaw] closing connection for', name)\n\t\t\tws.close()\n\t\t},\n\t\tws,\n\t}\n}\n\nexport interface IpnsWatcherOptions {\n\t/** Called when the IPNS record is updated (enriched payload with CID and change detection) */\n\tonUpdate: (update: IpnsUpdate) => void | Promise<void>\n\t/** Called when an error occurs */\n\tonError?: (error: Error | Event) => void\n\t/** Called when the connection is opened/reconnected */\n\tonConnected?: () => void\n\t/** Called when the connection is closed */\n\tonDisconnected?: () => void\n\t/** Fetch current IPNS state on first connect (default: false) */\n\tfetchInitialState?: boolean\n\t/** Fetch current IPNS state on reconnect to catch missed updates (default: true) */\n\tcatchUpOnReconnect?: boolean\n\t/** If true, call onUpdate even when value hasn't changed (default: false) */\n\tincludeUnchanged?: boolean\n\t/**\n\t * Enable periodic liveness checks via HTTP to detect zombie connections (default: true).\n\t * When enabled, periodically fetches current IPNS value and forces reconnect if it\n\t * differs from the last WebSocket update.\n\t */\n\tlivenessCheck?: boolean\n\t/**\n\t * Liveness check interval in milliseconds (default: 3600000 = 1 hour).\n\t * Only used when livenessCheck is enabled.\n\t */\n\tlivenessCheckInterval?: number\n\t/**\n\t * Called when a stale connection is detected (WebSocket missed updates).\n\t * Provides debug info about the connection state.\n\t */\n\tonStaleConnection?: (info: StaleConnectionInfo) => void\n\t/**\n\t * Partysocket options (passed through to ReconnectingWebSocket).\n\t * Useful options: startClosed, maxReconnectionDelay, minReconnectionDelay, etc.\n\t * @see https://github.com/partykit/partykit/tree/main/packages/partysocket\n\t */\n\twsOptions?: PartysocketOptions\n}\n\n/**\n * Robust IPNS watcher with auto-reconnect and catch-up logic.\n * Uses partysocket for reliable WebSocket reconnection.\n *\n * @example\n * ```ts\n * const watcher = new IpnsWatcher('k51qzi5uqu...', {\n * onUpdate: (update) => console.log('New CID:', update.cid?.toString()),\n * onError: (err) => console.error('Error:', err),\n * })\n *\n * // Later, to stop watching:\n * watcher.close()\n * ```\n */\nconst DEFAULT_LIVENESS_INTERVAL = 3600000 // 1 hour\n\nexport class IpnsWatcher {\n\tprivate name: string\n\tprivate nameBaseUrl: string\n\tprivate ws: ReconnectingWebSocket\n\tprivate lastKnownValue: string | null = null\n\tprivate options: IpnsWatcherOptions\n\tprivate isFirstConnect = true\n\tprivate livenessTimer: ReturnType<typeof setInterval> | null = null\n\tprivate connectedAt: Date | null = null\n\tprivate lastMessageAt: Date | null = null\n\n\tconstructor(nameBaseUrl: string, name: string, options: IpnsWatcherOptions) {\n\t\tthis.nameBaseUrl = requireNameBaseUrl(nameBaseUrl, 'IpnsWatcher')\n\t\tthis.name = name\n\t\tthis.options = options\n\n\t\tconst url = buildWsUrl(this.nameBaseUrl, name)\n\t\tDEBUG('[IpnsWatcher] creating for', name)\n\n\t\tthis.ws = new ReconnectingWebSocket(url, [], {\n\t\t\tmaxReconnectionDelay: 900000, // 15min\n\t\t\tminReconnectionDelay: 5000,\n\t\t\treconnectionDelayGrowFactor: 2,\n\t\t\tmaxRetries: Infinity,\n\t\t\t...options.wsOptions,\n\t\t})\n\n\t\tthis.ws.onopen = () => {\n\t\t\tLOG('[IpnsWatcher] connected to', name)\n\t\t\tthis.connectedAt = new Date()\n\t\t\toptions.onConnected?.()\n\n\t\t\t// Check for current state on first connect if requested\n\t\t\tif (this.isFirstConnect && (options.fetchInitialState ?? false)) {\n\t\t\t\tthis.checkForMissedUpdates()\n\t\t\t}\n\t\t\t// Check for missed updates on reconnect\n\t\t\telse if (!this.isFirstConnect && (options.catchUpOnReconnect ?? true)) {\n\t\t\t\tthis.checkForMissedUpdates()\n\t\t\t}\n\n\t\t\tthis.isFirstConnect = false\n\n\t\t\t// Start liveness checking (default: enabled)\n\t\t\tif (options.livenessCheck !== false) {\n\t\t\t\tthis.startLivenessCheck()\n\t\t\t}\n\t\t}\n\n\t\tthis.ws.onmessage = (event) => {\n\t\t\tthis.lastMessageAt = new Date()\n\t\t\ttry {\n\t\t\t\tconst record: W3NameRecord = JSON.parse(event.data as string)\n\t\t\t\tDEBUG('[IpnsWatcher] received update for', name, record)\n\n\t\t\t\tconst lastValue = this.lastKnownValue\n\t\t\t\tconst isNew = record.value !== lastValue\n\t\t\t\tconst cid = parseCidFromIpnsValue(record.value)\n\n\t\t\t\t// Skip unchanged values unless includeUnchanged is set\n\t\t\t\tif (!isNew && !options.includeUnchanged) {\n\t\t\t\t\tDEBUG('[IpnsWatcher] skipping unchanged value for', name)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// Update lastKnownValue after the skip check\n\t\t\t\tthis.lastKnownValue = record.value\n\n\t\t\t\tconst update: IpnsUpdate = {\n\t\t\t\t\tvalue: record.value,\n\t\t\t\t\tcid,\n\t\t\t\t\tlastValue,\n\t\t\t\t\tisNew,\n\t\t\t\t\trecord,\n\t\t\t\t}\n\n\t\t\t\tvoid options.onUpdate(update)\n\t\t\t} catch (err) {\n\t\t\t\tWARN('[IpnsWatcher] failed to parse message:', event.data, err)\n\t\t\t\toptions.onError?.(err instanceof Error ? err : new Error(String(err)))\n\t\t\t}\n\t\t}\n\n\t\tthis.ws.onerror = (event) => {\n\t\t\t// Extract meaningful error info instead of logging entire ErrorEvent\n\t\t\tconst errorMsg = event instanceof ErrorEvent ? event.message : 'WebSocket error'\n\n\t\t\t// \"Unexpected EOF\" is a normal disconnection - partysocket will auto-reconnect\n\t\t\t// Log at INFO level as it's expected behavior, not an error\n\t\t\tif (errorMsg === 'Unexpected EOF') {\n\t\t\t\tLOG('[IpnsWatcher] error for', name, ':', errorMsg, '(auto-reconnect enabled)')\n\t\t\t} else {\n\t\t\t\tWARN('[IpnsWatcher] error for', name, ':', errorMsg)\n\t\t\t}\n\n\t\t\t// Still call the error handler for unexpected errors\n\t\t\tif (errorMsg !== 'Unexpected EOF') {\n\t\t\t\toptions.onError?.(event)\n\t\t\t}\n\t\t}\n\n\t\tthis.ws.onclose = () => {\n\t\t\tDEBUG('[IpnsWatcher] disconnected from', name)\n\t\t\tthis.stopLivenessCheck()\n\t\t\tthis.connectedAt = null\n\t\t\tthis.lastMessageAt = null\n\t\t\toptions.onDisconnected?.()\n\t\t}\n\t}\n\n\t/**\n\t * Resolve current IPNS value via HTTP API to catch missed updates\n\t */\n\tprivate async checkForMissedUpdates(): Promise<void> {\n\t\ttry {\n\t\t\tDEBUG('[IpnsWatcher] checking for missed updates for', this.name)\n\t\t\tconst response = await fetch(buildHttpUrl(this.nameBaseUrl, this.name))\n\n\t\t\tif (!response.ok) {\n\t\t\t\tif (response.status === 404) {\n\t\t\t\t\tDEBUG('[IpnsWatcher] IPNS not yet published:', this.name)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthrow new Error(`HTTP ${response.status}: ${response.statusText}`)\n\t\t\t}\n\n\t\t\tconst record: W3NameRecord = await response.json()\n\t\t\tconst lastValue = this.lastKnownValue\n\t\t\tconst isNew = record.value !== lastValue\n\t\t\tconst cid = parseCidFromIpnsValue(record.value)\n\n\t\t\t// Skip unchanged values unless includeUnchanged is set\n\t\t\tif (!isNew && !this.options.includeUnchanged) {\n\t\t\t\tDEBUG('[IpnsWatcher] no new updates for', this.name)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst logMsg = lastValue === null\n\t\t\t\t? '[IpnsWatcher] fetched initial state for'\n\t\t\t\t: '[IpnsWatcher] caught missed update for'\n\t\t\tLOG(logMsg, this.name, {\n\t\t\t\tprevious: lastValue,\n\t\t\t\tcurrent: record.value,\n\t\t\t})\n\n\t\t\t// Update lastKnownValue after the skip check\n\t\t\tthis.lastKnownValue = record.value\n\n\t\t\tconst update: IpnsUpdate = {\n\t\t\t\tvalue: record.value,\n\t\t\t\tcid,\n\t\t\t\tlastValue,\n\t\t\t\tisNew,\n\t\t\t\trecord,\n\t\t\t}\n\n\t\t\tvoid this.options.onUpdate(update)\n\t\t} catch (err) {\n\t\t\tWARN('[IpnsWatcher] failed to check for missed updates:', this.name, err)\n\t\t\tthis.options.onError?.(err instanceof Error ? err : new Error(String(err)))\n\t\t}\n\t}\n\n\t/**\n\t * Start periodic liveness checks to detect zombie connections.\n\t */\n\tprivate startLivenessCheck(): void {\n\t\tthis.stopLivenessCheck() // Clear any existing timer\n\t\tconst interval = this.options.livenessCheckInterval ?? DEFAULT_LIVENESS_INTERVAL\n\t\tDEBUG('[IpnsWatcher] starting liveness check for', this.name, 'interval:', interval)\n\n\t\tthis.livenessTimer = setInterval(() => {\n\t\t\tvoid this.performLivenessCheck()\n\t\t}, interval)\n\t}\n\n\t/**\n\t * Stop periodic liveness checks.\n\t */\n\tprivate stopLivenessCheck(): void {\n\t\tif (this.livenessTimer !== null) {\n\t\t\tDEBUG('[IpnsWatcher] stopping liveness check for', this.name)\n\t\t\tclearInterval(this.livenessTimer)\n\t\t\tthis.livenessTimer = null\n\t\t}\n\t}\n\n\t/**\n\t * Perform a single liveness check via HTTP.\n\t * If the HTTP value differs from lastKnownValue, the connection is stale.\n\t */\n\tprivate async performLivenessCheck(): Promise<void> {\n\t\ttry {\n\t\t\tDEBUG('[IpnsWatcher] performing liveness check for', this.name)\n\t\t\tconst response = await fetch(buildHttpUrl(this.nameBaseUrl, this.name))\n\n\t\t\tif (!response.ok) {\n\t\t\t\tif (response.status === 404) {\n\t\t\t\t\t// IPNS not published - if we also have null, that's consistent\n\t\t\t\t\tif (this.lastKnownValue === null) {\n\t\t\t\t\t\tDEBUG('[IpnsWatcher] liveness check OK (both null) for', this.name)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t// We have a value but HTTP says 404 - this shouldn't happen normally\n\t\t\t\t\tWARN('[IpnsWatcher] liveness check inconsistent (we have value, HTTP 404) for', this.name)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthrow new Error(`HTTP ${response.status}: ${response.statusText}`)\n\t\t\t}\n\n\t\t\tconst record: W3NameRecord = await response.json()\n\n\t\t\t// Check if values match\n\t\t\tif (record.value === this.lastKnownValue) {\n\t\t\t\tDEBUG('[IpnsWatcher] liveness check OK for', this.name)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Stale connection detected!\n\t\t\tconst now = new Date()\n\t\t\tconst silenceDuration = this.lastMessageAt\n\t\t\t\t? now.getTime() - this.lastMessageAt.getTime()\n\t\t\t\t: this.connectedAt\n\t\t\t\t\t? now.getTime() - this.connectedAt.getTime()\n\t\t\t\t\t: 0\n\n\t\t\tconst staleInfo: StaleConnectionInfo = {\n\t\t\t\tconnectedAt: this.connectedAt ?? now,\n\t\t\t\tlastMessageAt: this.lastMessageAt,\n\t\t\t\tsilenceDuration,\n\t\t\t\tstaleValue: this.lastKnownValue,\n\t\t\t\tcurrentValue: record.value,\n\t\t\t}\n\n\t\t\tWARN('[IpnsWatcher] stale connection detected for', this.name, {\n\t\t\t\tconnectedAt: staleInfo.connectedAt.toISOString(),\n\t\t\t\tlastMessageAt: staleInfo.lastMessageAt?.toISOString() ?? 'never',\n\t\t\t\tsilenceDuration: `${Math.round(silenceDuration / 1000)}s`,\n\t\t\t\tstaleValue: staleInfo.staleValue,\n\t\t\t\tcurrentValue: staleInfo.currentValue,\n\t\t\t})\n\n\t\t\t// Notify via callback\n\t\t\tthis.options.onStaleConnection?.(staleInfo)\n\n\t\t\t// Fire immediate update with the current value\n\t\t\tconst lastValue = this.lastKnownValue\n\t\t\tconst cid = parseCidFromIpnsValue(record.value)\n\t\t\tthis.lastKnownValue = record.value\n\n\t\t\tconst update: IpnsUpdate = {\n\t\t\t\tvalue: record.value,\n\t\t\t\tcid,\n\t\t\t\tlastValue,\n\t\t\t\tisNew: true,\n\t\t\t\trecord,\n\t\t\t}\n\t\t\tvoid this.options.onUpdate(update)\n\n\t\t\t// Force reconnect to get a fresh connection\n\t\t\tLOG('[IpnsWatcher] forcing reconnect due to stale connection for', this.name)\n\t\t\tthis.ws.reconnect()\n\t\t} catch (err) {\n\t\t\t// Don't treat HTTP errors as stale - could be network issue\n\t\t\tWARN('[IpnsWatcher] liveness check failed for', this.name, err)\n\t\t}\n\t}\n\n\t/**\n\t * Manually start/reconnect the WebSocket.\n\t * Only needed if you used `wsOptions: { startClosed: true }`.\n\t */\n\tstart(): void {\n\t\tLOG('[IpnsWatcher] starting watcher for', this.name)\n\t\tthis.ws.reconnect()\n\t}\n\n\t/**\n\t * Alias for close() - for backward compatibility\n\t */\n\tstop(): void {\n\t\tthis.close()\n\t}\n\n\t/**\n\t * Close the WebSocket connection and stop watching\n\t */\n\tclose(): void {\n\t\tLOG('[IpnsWatcher] closing watcher for', this.name)\n\t\tthis.stopLivenessCheck()\n\t\tthis.ws.close()\n\t}\n\n\t/**\n\t * Get the last known IPNS value\n\t */\n\tget lastValue(): string | null {\n\t\treturn this.lastKnownValue\n\t}\n\n\t/**\n\t * Get the WebSocket ready state\n\t */\n\tget readyState(): number {\n\t\treturn this.ws.readyState\n\t}\n}\n\n/**\n * Create an IPNS watcher with auto-reconnect and catch-up logic.\n * Convenience function that creates and returns an IpnsWatcher instance.\n *\n * @param nameBaseUrl - Base URL of a naming service that supports `/name/<ipns>/watch` (WebSocket) and `/name/<ipns>` (HTTP)\n * @param name - The IPNS name/key to watch (e.g. \"k51qzi5u...\")\n * @param options - Callback options for handling events\n * @returns An IpnsWatcher instance with close() method\n */\nexport function watchName(nameBaseUrl: string, name: string, options: IpnsWatcherOptions): IpnsWatcher {\n\treturn new IpnsWatcher(nameBaseUrl, name, options)\n}\n\n/**\n * Watch an IPNS name and return updates as an async iterator.\n * Includes auto-reconnect - iterator continues through disconnections.\n *\n * @param nameBaseUrl - Base URL of a naming service that supports `/name/<ipns>/watch` (WebSocket) and `/name/<ipns>` (HTTP)\n * @param name - The IPNS name/key to watch\n * @param signal - Optional AbortSignal to stop the watch\n *\n * @example\n * ```ts\n * const controller = new AbortController()\n * for await (const update of watchNameIterator('https://name.example.com', 'k51qzi5u...', controller.signal)) {\n * console.log('Update:', update.cid?.toString())\n * }\n * ```\n */\nexport async function* watchNameIterator(\n\tnameBaseUrl: string,\n\tname: string,\n\tsignal?: AbortSignal,\n): AsyncGenerator<IpnsUpdate, void, unknown> {\n\tconst queue: IpnsUpdate[] = []\n\tlet resolve: (() => void) | null = null\n\tlet error: Error | null = null\n\n\tconst watcher = new IpnsWatcher(nameBaseUrl, name, {\n\t\tonUpdate: (update) => {\n\t\t\tqueue.push(update)\n\t\t\tresolve?.()\n\t\t},\n\t\tonError: (err) => {\n\t\t\terror = err instanceof Error ? err : new Error('WebSocket error')\n\t\t\tresolve?.()\n\t\t},\n\t})\n\n\tsignal?.addEventListener('abort', () => {\n\t\twatcher.close()\n\t})\n\n\ttry {\n\t\twhile (!signal?.aborted) {\n\t\t\tif (queue.length > 0) {\n\t\t\t\tyield queue.shift()!\n\t\t\t} else if (error) {\n\t\t\t\t// Log error but continue - partysocket will reconnect\n\t\t\t\tWARN('[watchNameIterator] error occurred, continuing:', error)\n\t\t\t\terror = null\n\t\t\t} else {\n\t\t\t\tawait new Promise<void>((r) => {\n\t\t\t\t\tresolve = r\n\t\t\t\t})\n\t\t\t\tresolve = null\n\t\t\t}\n\t\t}\n\t} finally {\n\t\twatcher.close()\n\t}\n}\n"],"mappings":"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","names":["Logger","base64pad","WARN","LOG","DEBUG","Logger","CID","WARN","LOG","DEBUG","ERROR","resolve"]}
@@ -31,8 +31,8 @@ export declare function chunkApplogs(applogCids: CID<unknown, 297, 18, 1>[], siz
31
31
  blocks: any[];
32
32
  chunks: any[];
33
33
  }>;
34
- export declare function unchunkApplogsBlock(block: SnapBlockLogsOrChunks, blockStore: BlockStoreish): Promise<CID[]>;
35
- export declare function isSnapBlockChunks(block: SnapBlockLogsOrChunks): block is SnapBlockChunks;
34
+ export declare function unchunkApplogsBlock(block: SnapBlockLogsOrChunks | null | undefined, blockStore: BlockStoreish): Promise<CID[]>;
35
+ export declare function isSnapBlockChunks(block: SnapBlockLogsOrChunks | null | undefined): block is SnapBlockChunks;
36
36
  /**
37
37
  * @param applogs Encrypted or plain applogs
38
38
  * @returns Car file
@@ -1 +1 @@
1
- {"version":3,"file":"snap-push.d.ts","sourceRoot":"","sources":["../../src/pubsub/snap-push.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AAGtC,OAAO,KAAK,EACX,MAAM,EAEN,2BAA2B,EAC3B,gCAAgC,EAIhC,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAE,aAAa,EAA4C,MAAM,gBAAgB,CAAA;AAIxF,OAAO,EAAE,eAAe,EAAqB,MAAM,EAAE,MAAM,cAAc,CAAA;AAGzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,eAAe,EAAiB,qBAAqB,EAAE,MAAM,mBAAmB,CAAA;AAehH,wBAAsB,sBAAsB,CAC3C,KAAK,EAAE,QAAQ,EACf,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,eAAe,EAChC,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,GAAG,GAAG,IAAI,EACvB,WAAW,EAAE,MAAM,GAAG,IAAI;;;;;;GA+H1B;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACxC,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,gCAAgC,EACzC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,WAAW,EAAE,GAAG,GAAG,IAAI,EACvB,WAAW,EAAE,MAAM,GAAG,IAAI;;;;;;GAiC1B;AAED,uEAAuE;AACvE,wBAAsB,YAAY,CAAC,UAAU,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,SAAQ;;;;;;;;GAatF;AACD,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,qBAAqB,EAAE,UAAU,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAYjH;AACD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,qBAAqB,GAAG,KAAK,IAAI,eAAe,CAExF;AACD;;;GAGG;AACH,wBAAsB,0BAA0B,CAC/C,OAAO,EAAE,2BAA2B,iBAUpC"}
1
+ {"version":3,"file":"snap-push.d.ts","sourceRoot":"","sources":["../../src/pubsub/snap-push.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AAGtC,OAAO,KAAK,EACX,MAAM,EAEN,2BAA2B,EAC3B,gCAAgC,EAIhC,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAE,aAAa,EAA4C,MAAM,gBAAgB,CAAA;AAIxF,OAAO,EAAE,eAAe,EAAqB,MAAM,EAAE,MAAM,cAAc,CAAA;AAGzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,eAAe,EAAiB,qBAAqB,EAAE,MAAM,mBAAmB,CAAA;AAehH,wBAAsB,sBAAsB,CAC3C,KAAK,EAAE,QAAQ,EACf,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,eAAe,EAChC,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,GAAG,GAAG,IAAI,EACvB,WAAW,EAAE,MAAM,GAAG,IAAI;;;;;;GA+H1B;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACxC,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,gCAAgC,EACzC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,WAAW,EAAE,GAAG,GAAG,IAAI,EACvB,WAAW,EAAE,MAAM,GAAG,IAAI;;;;;;GAiC1B;AAED,uEAAuE;AACvE,wBAAsB,YAAY,CAAC,UAAU,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,SAAQ;;;;;;;;GAatF;AACD,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,qBAAqB,GAAG,IAAI,GAAG,SAAS,EAAE,UAAU,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAapI;AACD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,qBAAqB,GAAG,IAAI,GAAG,SAAS,GAAG,KAAK,IAAI,eAAe,CAE3G;AACD;;;GAGG;AACH,wBAAsB,0BAA0B,CAC/C,OAAO,EAAE,2BAA2B,iBAUpC"}