@wovin/daemon-utils 0.0.18 → 0.0.19

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.
@@ -0,0 +1,174 @@
1
+ import {
2
+ g
3
+ } from "./chunk-MZUPFLFH.min.js";
4
+
5
+ // src/watcher.ts
6
+ var { WARN, LOG, DEBUG, ERROR } = g.setup(g.INFO);
7
+ var NAME_WS_URL = "wss://name.web3.storage/name";
8
+ var NAME_HTTP_URL = "https://name.web3.storage/name";
9
+ function watchNameRaw(name, options) {
10
+ const url = `${NAME_WS_URL}/${name}/watch`;
11
+ DEBUG("[w3name-watch] connecting to", url);
12
+ const ws = new WebSocket(url);
13
+ ws.onopen = () => {
14
+ LOG("[w3name-watch] connected to", name);
15
+ options.onOpen?.();
16
+ };
17
+ ws.onmessage = (event) => {
18
+ try {
19
+ const record = JSON.parse(event.data);
20
+ DEBUG("[w3name-watch] received update for", name, record);
21
+ options.onUpdate(record);
22
+ } catch (err) {
23
+ WARN("[w3name-watch] failed to parse message:", event.data, err);
24
+ options.onError?.(err instanceof Error ? err : new Error(String(err)));
25
+ }
26
+ };
27
+ ws.onerror = (event) => {
28
+ WARN("[w3name-watch] error for", name, event);
29
+ options.onError?.(event);
30
+ };
31
+ ws.onclose = (event) => {
32
+ DEBUG("[w3name-watch] closed for", name, "code:", event.code, "reason:", event.reason);
33
+ options.onClose?.(event);
34
+ };
35
+ return {
36
+ close: () => {
37
+ DEBUG("[w3name-watch] closing connection for", name);
38
+ ws.close();
39
+ },
40
+ ws
41
+ };
42
+ }
43
+ var IpnsWatcher = class {
44
+ name;
45
+ options;
46
+ subscription = null;
47
+ backoffDelay;
48
+ lastKnownValue = null;
49
+ stopped = false;
50
+ reconnectTimeout = null;
51
+ constructor(name, options) {
52
+ this.name = name;
53
+ this.options = {
54
+ onUpdate: options.onUpdate,
55
+ onError: options.onError ?? (() => {
56
+ }),
57
+ onConnected: options.onConnected ?? (() => {
58
+ }),
59
+ onDisconnected: options.onDisconnected ?? (() => {
60
+ }),
61
+ initialBackoff: options.initialBackoff ?? 5e3,
62
+ maxBackoff: options.maxBackoff ?? 9e5
63
+ };
64
+ this.backoffDelay = this.options.initialBackoff;
65
+ }
66
+ start() {
67
+ if (this.stopped) {
68
+ WARN(`[IpnsWatcher] Cannot restart stopped watcher for ${this.name}`);
69
+ return;
70
+ }
71
+ this.connect();
72
+ }
73
+ stop() {
74
+ this.stopped = true;
75
+ if (this.reconnectTimeout) {
76
+ clearTimeout(this.reconnectTimeout);
77
+ this.reconnectTimeout = null;
78
+ }
79
+ if (this.subscription) {
80
+ this.subscription.close();
81
+ this.subscription = null;
82
+ }
83
+ LOG(`[IpnsWatcher] Stopped watching ${this.name}`);
84
+ }
85
+ connect() {
86
+ if (this.stopped) return;
87
+ this.subscription = watchNameRaw(this.name, {
88
+ onUpdate: async (record) => {
89
+ this.lastKnownValue = record.value;
90
+ this.backoffDelay = this.options.initialBackoff;
91
+ await this.options.onUpdate(record);
92
+ },
93
+ onError: (error) => {
94
+ this.options.onError(error);
95
+ },
96
+ onOpen: () => {
97
+ this.options.onConnected();
98
+ },
99
+ onClose: () => {
100
+ this.options.onDisconnected();
101
+ this.scheduleReconnect();
102
+ }
103
+ });
104
+ }
105
+ scheduleReconnect() {
106
+ if (this.stopped) return;
107
+ WARN(
108
+ `[IpnsWatcher] Scheduling reconnect for ${this.name} in ${this.backoffDelay}ms`
109
+ );
110
+ this.reconnectTimeout = setTimeout(() => {
111
+ if (this.stopped) return;
112
+ this.checkForMissedUpdates().then(() => {
113
+ this.connect();
114
+ this.backoffDelay = Math.min(this.backoffDelay * 2, this.options.maxBackoff);
115
+ }).catch((err) => {
116
+ WARN(`[IpnsWatcher] Error during catch-up for ${this.name}:`, err);
117
+ this.options.onError(err instanceof Error ? err : new Error(String(err)));
118
+ this.connect();
119
+ this.backoffDelay = Math.min(this.backoffDelay * 2, this.options.maxBackoff);
120
+ });
121
+ }, this.backoffDelay);
122
+ }
123
+ /**
124
+ * Resolve current IPNS value via HTTP API
125
+ */
126
+ async resolveCurrentValue() {
127
+ try {
128
+ const response = await fetch(`${NAME_HTTP_URL}/${this.name}`);
129
+ if (!response.ok) {
130
+ if (response.status === 404) {
131
+ DEBUG(`[IpnsWatcher] IPNS ${this.name} not yet published`);
132
+ return null;
133
+ }
134
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
135
+ }
136
+ const record = await response.json();
137
+ return record;
138
+ } catch (err) {
139
+ WARN(`[IpnsWatcher] Failed to resolve IPNS ${this.name}:`, err);
140
+ return null;
141
+ }
142
+ }
143
+ async checkForMissedUpdates() {
144
+ try {
145
+ const currentRecord = await this.resolveCurrentValue();
146
+ if (currentRecord && currentRecord.value !== this.lastKnownValue) {
147
+ DEBUG(
148
+ `[IpnsWatcher] Detected missed update for ${this.name}`,
149
+ "previous:",
150
+ this.lastKnownValue,
151
+ "current:",
152
+ currentRecord.value
153
+ );
154
+ this.lastKnownValue = currentRecord.value;
155
+ await this.options.onUpdate(currentRecord);
156
+ }
157
+ } catch (err) {
158
+ WARN(`[IpnsWatcher] Failed to check for missed updates for ${this.name}:`, err);
159
+ throw err;
160
+ }
161
+ }
162
+ };
163
+ function watchName(name, options) {
164
+ const watcher = new IpnsWatcher(name, options);
165
+ watcher.start();
166
+ return watcher;
167
+ }
168
+
169
+ export {
170
+ watchNameRaw,
171
+ IpnsWatcher,
172
+ watchName
173
+ };
174
+ //# sourceMappingURL=chunk-GB53WLYA.min.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/watcher.ts"],"sourcesContent":["/**\n * IPNS Thread Watcher\n * Watches an IPNS thread for new audio files via WebSocket\n */\n\nimport { Logger } from 'besonders-logger'\n\nconst { WARN, LOG, DEBUG, ERROR } = Logger.setup(Logger.INFO)\n\nconst NAME_WS_URL = 'wss://name.web3.storage/name'\nconst NAME_HTTP_URL = 'https://name.web3.storage/name'\n\nexport interface W3NameRecord {\n\tvalue: string // e.g. \"/ipfs/bafybeig...\"\n\tseq?: number\n\tvalidity?: string\n}\n\nexport interface WatchSubscription {\n\tclose: () => void\n\tws: WebSocket\n}\n\nexport interface WatchOptions {\n\tonUpdate: (record: W3NameRecord) => void\n\tonError?: (error: Event | Error) => void\n\tonOpen?: () => void\n\tonClose?: (event: CloseEvent) => void\n}\n\nexport interface IpnsWatcherOptions {\n\tonUpdate: (record: W3NameRecord) => void | Promise<void>\n\tonError?: (error: Error | Event) => void\n\tonConnected?: () => void\n\tonDisconnected?: () => void\n\t/** Initial backoff delay in ms (default: 5000) */\n\tinitialBackoff?: number\n\t/** Max backoff delay in ms (default: 900000 = 15min) */\n\tmaxBackoff?: number\n}\n\n/**\n * Low-level WebSocket watcher for IPNS (no reconnect logic)\n * @internal Use IpnsWatcher for robust watching with auto-reconnect\n */\nexport function watchNameRaw(name: string, options: WatchOptions): WatchSubscription {\n\tconst url = `${NAME_WS_URL}/${name}/watch`\n\tDEBUG('[w3name-watch] connecting to', url)\n\n\tconst ws = new WebSocket(url)\n\n\tws.onopen = () => {\n\t\tLOG('[w3name-watch] 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('[w3name-watch] received update for', name, record)\n\t\t\toptions.onUpdate(record)\n\t\t} catch (err) {\n\t\t\tWARN('[w3name-watch] 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('[w3name-watch] error for', name, event)\n\t\toptions.onError?.(event)\n\t}\n\n\tws.onclose = (event) => {\n\t\tDEBUG('[w3name-watch] closed for', name, 'code:', event.code, 'reason:', event.reason)\n\t\toptions.onClose?.(event)\n\t}\n\n\treturn {\n\t\tclose: () => {\n\t\t\tDEBUG('[w3name-watch] closing connection for', name)\n\t\t\tws.close()\n\t\t},\n\t\tws,\n\t}\n}\n\n/**\n * Robust IPNS watcher with auto-reconnect and catch-up logic\n */\nexport class IpnsWatcher {\n\tprivate name: string\n\tprivate options: Required<Omit<IpnsWatcherOptions, 'onError' | 'onConnected' | 'onDisconnected'>> & {\n\t\tonError: (error: Error | Event) => void\n\t\tonConnected: () => void\n\t\tonDisconnected: () => void\n\t}\n\tprivate subscription: WatchSubscription | null = null\n\tprivate backoffDelay: number\n\tprivate lastKnownValue: string | null = null\n\tprivate stopped: boolean = false\n\tprivate reconnectTimeout: NodeJS.Timeout | null = null\n\n\tconstructor(name: string, options: IpnsWatcherOptions) {\n\t\tthis.name = name\n\t\tthis.options = {\n\t\t\tonUpdate: options.onUpdate,\n\t\t\tonError: options.onError ?? (() => {}),\n\t\t\tonConnected: options.onConnected ?? (() => {}),\n\t\t\tonDisconnected: options.onDisconnected ?? (() => {}),\n\t\t\tinitialBackoff: options.initialBackoff ?? 5000,\n\t\t\tmaxBackoff: options.maxBackoff ?? 900000,\n\t\t}\n\t\tthis.backoffDelay = this.options.initialBackoff\n\t}\n\n\tstart(): void {\n\t\tif (this.stopped) {\n\t\t\tWARN(`[IpnsWatcher] Cannot restart stopped watcher for ${this.name}`)\n\t\t\treturn\n\t\t}\n\t\tthis.connect()\n\t}\n\n\tstop(): void {\n\t\tthis.stopped = true\n\t\tif (this.reconnectTimeout) {\n\t\t\tclearTimeout(this.reconnectTimeout)\n\t\t\tthis.reconnectTimeout = null\n\t\t}\n\t\tif (this.subscription) {\n\t\t\tthis.subscription.close()\n\t\t\tthis.subscription = null\n\t\t}\n\t\tLOG(`[IpnsWatcher] Stopped watching ${this.name}`)\n\t}\n\n\tprivate connect(): void {\n\t\tif (this.stopped) return\n\n\t\tthis.subscription = watchNameRaw(this.name, {\n\t\t\tonUpdate: async (record) => {\n\t\t\t\tthis.lastKnownValue = record.value\n\t\t\t\tthis.backoffDelay = this.options.initialBackoff\n\t\t\t\tawait this.options.onUpdate(record)\n\t\t\t},\n\t\t\tonError: (error) => {\n\t\t\t\tthis.options.onError(error)\n\t\t\t},\n\t\t\tonOpen: () => {\n\t\t\t\tthis.options.onConnected()\n\t\t\t},\n\t\t\tonClose: () => {\n\t\t\t\tthis.options.onDisconnected()\n\t\t\t\tthis.scheduleReconnect()\n\t\t\t},\n\t\t})\n\t}\n\n\tprivate scheduleReconnect(): void {\n\t\tif (this.stopped) return\n\n\t\tWARN(\n\t\t\t`[IpnsWatcher] Scheduling reconnect for ${this.name} in ${this.backoffDelay}ms`,\n\t\t)\n\n\t\tthis.reconnectTimeout = setTimeout(() => {\n\t\t\tif (this.stopped) return\n\n\t\t\tthis.checkForMissedUpdates()\n\t\t\t\t.then(() => {\n\t\t\t\t\tthis.connect()\n\t\t\t\t\t// Exponential backoff: double delay up to max\n\t\t\t\t\tthis.backoffDelay = Math.min(this.backoffDelay * 2, this.options.maxBackoff)\n\t\t\t\t})\n\t\t\t\t.catch((err) => {\n\t\t\t\t\tWARN(`[IpnsWatcher] Error during catch-up for ${this.name}:`, err)\n\t\t\t\t\tthis.options.onError(err instanceof Error ? err : new Error(String(err)))\n\t\t\t\t\t// Still reconnect, but don't update lastKnownValue\n\t\t\t\t\tthis.connect()\n\t\t\t\t\tthis.backoffDelay = Math.min(this.backoffDelay * 2, this.options.maxBackoff)\n\t\t\t\t})\n\t\t}, this.backoffDelay)\n\t}\n\n\t/**\n\t * Resolve current IPNS value via HTTP API\n\t */\n\tprivate async resolveCurrentValue(): Promise<W3NameRecord | null> {\n\t\ttry {\n\t\t\tconst response = await fetch(`${NAME_HTTP_URL}/${this.name}`)\n\t\t\tif (!response.ok) {\n\t\t\t\tif (response.status === 404) {\n\t\t\t\t\tDEBUG(`[IpnsWatcher] IPNS ${this.name} not yet published`)\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t\tthrow new Error(`HTTP ${response.status}: ${response.statusText}`)\n\t\t\t}\n\t\t\tconst record: W3NameRecord = await response.json()\n\t\t\treturn record\n\t\t} catch (err) {\n\t\t\tWARN(`[IpnsWatcher] Failed to resolve IPNS ${this.name}:`, err)\n\t\t\treturn null\n\t\t}\n\t}\n\n\tprivate async checkForMissedUpdates(): Promise<void> {\n\t\ttry {\n\t\t\tconst currentRecord = await this.resolveCurrentValue()\n\t\t\tif (currentRecord && currentRecord.value !== this.lastKnownValue) {\n\t\t\t\tDEBUG(\n\t\t\t\t\t`[IpnsWatcher] Detected missed update for ${this.name}`,\n\t\t\t\t\t'previous:',\n\t\t\t\t\tthis.lastKnownValue,\n\t\t\t\t\t'current:',\n\t\t\t\t\tcurrentRecord.value,\n\t\t\t\t)\n\t\t\t\tthis.lastKnownValue = currentRecord.value\n\t\t\t\tawait this.options.onUpdate(currentRecord)\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tWARN(`[IpnsWatcher] Failed to check for missed updates for ${this.name}:`, err)\n\t\t\tthrow err\n\t\t}\n\t}\n}\n\n/**\n * Subscribe to IPNS name updates with auto-reconnect and catch-up logic\n */\nexport function watchName(name: string, options: IpnsWatcherOptions): IpnsWatcher {\n\tconst watcher = new IpnsWatcher(name, options)\n\twatcher.start()\n\treturn watcher\n}\n"],"mappings":";;;;;AAOA,IAAM,EAAE,MAAM,KAAK,OAAO,MAAM,IAAI,EAAO,MAAM,EAAO,IAAI;AAE5D,IAAM,cAAc;AACpB,IAAM,gBAAgB;AAmCf,SAAS,aAAa,MAAc,SAA0C;AACpF,QAAM,MAAM,GAAG,WAAW,IAAI,IAAI;AAClC,QAAM,gCAAgC,GAAG;AAEzC,QAAM,KAAK,IAAI,UAAU,GAAG;AAE5B,KAAG,SAAS,MAAM;AACjB,QAAI,+BAA+B,IAAI;AACvC,YAAQ,SAAS;AAAA,EAClB;AAEA,KAAG,YAAY,CAAC,UAAU;AACzB,QAAI;AACH,YAAM,SAAuB,KAAK,MAAM,MAAM,IAAI;AAClD,YAAM,sCAAsC,MAAM,MAAM;AACxD,cAAQ,SAAS,MAAM;AAAA,IACxB,SAAS,KAAK;AACb,WAAK,2CAA2C,MAAM,MAAM,GAAG;AAC/D,cAAQ,UAAU,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AAAA,IACtE;AAAA,EACD;AAEA,KAAG,UAAU,CAAC,UAAU;AACvB,SAAK,4BAA4B,MAAM,KAAK;AAC5C,YAAQ,UAAU,KAAK;AAAA,EACxB;AAEA,KAAG,UAAU,CAAC,UAAU;AACvB,UAAM,6BAA6B,MAAM,SAAS,MAAM,MAAM,WAAW,MAAM,MAAM;AACrF,YAAQ,UAAU,KAAK;AAAA,EACxB;AAEA,SAAO;AAAA,IACN,OAAO,MAAM;AACZ,YAAM,yCAAyC,IAAI;AACnD,SAAG,MAAM;AAAA,IACV;AAAA,IACA;AAAA,EACD;AACD;AAKO,IAAM,cAAN,MAAkB;AAAA,EAChB;AAAA,EACA;AAAA,EAKA,eAAyC;AAAA,EACzC;AAAA,EACA,iBAAgC;AAAA,EAChC,UAAmB;AAAA,EACnB,mBAA0C;AAAA,EAElD,YAAY,MAAc,SAA6B;AACtD,SAAK,OAAO;AACZ,SAAK,UAAU;AAAA,MACd,UAAU,QAAQ;AAAA,MAClB,SAAS,QAAQ,YAAY,MAAM;AAAA,MAAC;AAAA,MACpC,aAAa,QAAQ,gBAAgB,MAAM;AAAA,MAAC;AAAA,MAC5C,gBAAgB,QAAQ,mBAAmB,MAAM;AAAA,MAAC;AAAA,MAClD,gBAAgB,QAAQ,kBAAkB;AAAA,MAC1C,YAAY,QAAQ,cAAc;AAAA,IACnC;AACA,SAAK,eAAe,KAAK,QAAQ;AAAA,EAClC;AAAA,EAEA,QAAc;AACb,QAAI,KAAK,SAAS;AACjB,WAAK,oDAAoD,KAAK,IAAI,EAAE;AACpE;AAAA,IACD;AACA,SAAK,QAAQ;AAAA,EACd;AAAA,EAEA,OAAa;AACZ,SAAK,UAAU;AACf,QAAI,KAAK,kBAAkB;AAC1B,mBAAa,KAAK,gBAAgB;AAClC,WAAK,mBAAmB;AAAA,IACzB;AACA,QAAI,KAAK,cAAc;AACtB,WAAK,aAAa,MAAM;AACxB,WAAK,eAAe;AAAA,IACrB;AACA,QAAI,kCAAkC,KAAK,IAAI,EAAE;AAAA,EAClD;AAAA,EAEQ,UAAgB;AACvB,QAAI,KAAK,QAAS;AAElB,SAAK,eAAe,aAAa,KAAK,MAAM;AAAA,MAC3C,UAAU,OAAO,WAAW;AAC3B,aAAK,iBAAiB,OAAO;AAC7B,aAAK,eAAe,KAAK,QAAQ;AACjC,cAAM,KAAK,QAAQ,SAAS,MAAM;AAAA,MACnC;AAAA,MACA,SAAS,CAAC,UAAU;AACnB,aAAK,QAAQ,QAAQ,KAAK;AAAA,MAC3B;AAAA,MACA,QAAQ,MAAM;AACb,aAAK,QAAQ,YAAY;AAAA,MAC1B;AAAA,MACA,SAAS,MAAM;AACd,aAAK,QAAQ,eAAe;AAC5B,aAAK,kBAAkB;AAAA,MACxB;AAAA,IACD,CAAC;AAAA,EACF;AAAA,EAEQ,oBAA0B;AACjC,QAAI,KAAK,QAAS;AAElB;AAAA,MACC,0CAA0C,KAAK,IAAI,OAAO,KAAK,YAAY;AAAA,IAC5E;AAEA,SAAK,mBAAmB,WAAW,MAAM;AACxC,UAAI,KAAK,QAAS;AAElB,WAAK,sBAAsB,EACzB,KAAK,MAAM;AACX,aAAK,QAAQ;AAEb,aAAK,eAAe,KAAK,IAAI,KAAK,eAAe,GAAG,KAAK,QAAQ,UAAU;AAAA,MAC5E,CAAC,EACA,MAAM,CAAC,QAAQ;AACf,aAAK,2CAA2C,KAAK,IAAI,KAAK,GAAG;AACjE,aAAK,QAAQ,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AAExE,aAAK,QAAQ;AACb,aAAK,eAAe,KAAK,IAAI,KAAK,eAAe,GAAG,KAAK,QAAQ,UAAU;AAAA,MAC5E,CAAC;AAAA,IACH,GAAG,KAAK,YAAY;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,sBAAoD;AACjE,QAAI;AACH,YAAM,WAAW,MAAM,MAAM,GAAG,aAAa,IAAI,KAAK,IAAI,EAAE;AAC5D,UAAI,CAAC,SAAS,IAAI;AACjB,YAAI,SAAS,WAAW,KAAK;AAC5B,gBAAM,sBAAsB,KAAK,IAAI,oBAAoB;AACzD,iBAAO;AAAA,QACR;AACA,cAAM,IAAI,MAAM,QAAQ,SAAS,MAAM,KAAK,SAAS,UAAU,EAAE;AAAA,MAClE;AACA,YAAM,SAAuB,MAAM,SAAS,KAAK;AACjD,aAAO;AAAA,IACR,SAAS,KAAK;AACb,WAAK,wCAAwC,KAAK,IAAI,KAAK,GAAG;AAC9D,aAAO;AAAA,IACR;AAAA,EACD;AAAA,EAEA,MAAc,wBAAuC;AACpD,QAAI;AACH,YAAM,gBAAgB,MAAM,KAAK,oBAAoB;AACrD,UAAI,iBAAiB,cAAc,UAAU,KAAK,gBAAgB;AACjE;AAAA,UACC,4CAA4C,KAAK,IAAI;AAAA,UACrD;AAAA,UACA,KAAK;AAAA,UACL;AAAA,UACA,cAAc;AAAA,QACf;AACA,aAAK,iBAAiB,cAAc;AACpC,cAAM,KAAK,QAAQ,SAAS,aAAa;AAAA,MAC1C;AAAA,IACD,SAAS,KAAK;AACb,WAAK,wDAAwD,KAAK,IAAI,KAAK,GAAG;AAC9E,YAAM;AAAA,IACP;AAAA,EACD;AACD;AAKO,SAAS,UAAU,MAAc,SAA0C;AACjF,QAAM,UAAU,IAAI,YAAY,MAAM,OAAO;AAC7C,UAAQ,MAAM;AACd,SAAO;AACR;","names":[]}
package/dist/index.min.js CHANGED
@@ -15,11 +15,14 @@ import {
15
15
  loadSecretOrThrow
16
16
  } from "./chunk-XAFSYOU2.min.js";
17
17
  import {
18
- watchName
19
- } from "./chunk-IAQU2NGT.min.js";
18
+ IpnsWatcher,
19
+ watchName,
20
+ watchNameRaw
21
+ } from "./chunk-GB53WLYA.min.js";
20
22
  import "./chunk-MZUPFLFH.min.js";
21
23
  import "./chunk-PHITDXZT.min.js";
22
24
  export {
25
+ IpnsWatcher,
23
26
  ManagedThread,
24
27
  generateIpnsKey,
25
28
  getW3NamePublic,
@@ -28,6 +31,7 @@ export {
28
31
  loadSecretOrThrow,
29
32
  publishIPNS,
30
33
  publishIpnsThread,
31
- watchName
34
+ watchName,
35
+ watchNameRaw
32
36
  };
33
37
  //# sourceMappingURL=index.min.js.map
package/dist/watcher.d.ts CHANGED
@@ -17,8 +17,45 @@ export interface WatchOptions {
17
17
  onOpen?: () => void;
18
18
  onClose?: (event: CloseEvent) => void;
19
19
  }
20
+ export interface IpnsWatcherOptions {
21
+ onUpdate: (record: W3NameRecord) => void | Promise<void>;
22
+ onError?: (error: Error | Event) => void;
23
+ onConnected?: () => void;
24
+ onDisconnected?: () => void;
25
+ /** Initial backoff delay in ms (default: 5000) */
26
+ initialBackoff?: number;
27
+ /** Max backoff delay in ms (default: 900000 = 15min) */
28
+ maxBackoff?: number;
29
+ }
30
+ /**
31
+ * Low-level WebSocket watcher for IPNS (no reconnect logic)
32
+ * @internal Use IpnsWatcher for robust watching with auto-reconnect
33
+ */
34
+ export declare function watchNameRaw(name: string, options: WatchOptions): WatchSubscription;
35
+ /**
36
+ * Robust IPNS watcher with auto-reconnect and catch-up logic
37
+ */
38
+ export declare class IpnsWatcher {
39
+ private name;
40
+ private options;
41
+ private subscription;
42
+ private backoffDelay;
43
+ private lastKnownValue;
44
+ private stopped;
45
+ private reconnectTimeout;
46
+ constructor(name: string, options: IpnsWatcherOptions);
47
+ start(): void;
48
+ stop(): void;
49
+ private connect;
50
+ private scheduleReconnect;
51
+ /**
52
+ * Resolve current IPNS value via HTTP API
53
+ */
54
+ private resolveCurrentValue;
55
+ private checkForMissedUpdates;
56
+ }
20
57
  /**
21
- * Subscribe to IPNS name updates via WebSocket
58
+ * Subscribe to IPNS name updates with auto-reconnect and catch-up logic
22
59
  */
23
- export declare function watchName(name: string, options: WatchOptions): WatchSubscription;
60
+ export declare function watchName(name: string, options: IpnsWatcherOptions): IpnsWatcher;
24
61
  //# sourceMappingURL=watcher.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,iBAAiB;IACjC,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,EAAE,EAAE,SAAS,CAAA;CACb;AAED,MAAM,WAAW,YAAY;IAC5B,QAAQ,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAA;IACxC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,KAAK,IAAI,CAAA;IACxC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAA;CACrC;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,iBAAiB,CAuChF"}
1
+ {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAAA;;;GAGG;AASH,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,iBAAiB;IACjC,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,EAAE,EAAE,SAAS,CAAA;CACb;AAED,MAAM,WAAW,YAAY;IAC5B,QAAQ,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAA;IACxC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,KAAK,IAAI,CAAA;IACxC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAA;CACrC;AAED,MAAM,WAAW,kBAAkB;IAClC,QAAQ,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,KAAK,IAAI,CAAA;IACxC,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;IACxB,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAC3B,kDAAkD;IAClD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,iBAAiB,CAuCnF;AAED;;GAEG;AACH,qBAAa,WAAW;IACvB,OAAO,CAAC,IAAI,CAAQ;IACpB,OAAO,CAAC,OAAO,CAId;IACD,OAAO,CAAC,YAAY,CAAiC;IACrD,OAAO,CAAC,YAAY,CAAQ;IAC5B,OAAO,CAAC,cAAc,CAAsB;IAC5C,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,gBAAgB,CAA8B;gBAE1C,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB;IAarD,KAAK,IAAI,IAAI;IAQb,IAAI,IAAI,IAAI;IAaZ,OAAO,CAAC,OAAO;IAsBf,OAAO,CAAC,iBAAiB;IA0BzB;;OAEG;YACW,mBAAmB;YAkBnB,qBAAqB;CAmBnC;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,GAAG,WAAW,CAIhF"}
@@ -1,9 +1,13 @@
1
1
  import {
2
- watchName
3
- } from "./chunk-IAQU2NGT.min.js";
2
+ IpnsWatcher,
3
+ watchName,
4
+ watchNameRaw
5
+ } from "./chunk-GB53WLYA.min.js";
4
6
  import "./chunk-MZUPFLFH.min.js";
5
7
  import "./chunk-PHITDXZT.min.js";
6
8
  export {
7
- watchName
9
+ IpnsWatcher,
10
+ watchName,
11
+ watchNameRaw
8
12
  };
9
13
  //# sourceMappingURL=watcher.min.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wovin/daemon-utils",
3
- "version": "0.0.18",
3
+ "version": "0.0.19",
4
4
  "type": "module",
5
5
  "main": "./dist/index.min.js",
6
6
  "module": "./dist/index.min.js",
@@ -18,8 +18,8 @@
18
18
  "access": "public"
19
19
  },
20
20
  "peerDependencies": {
21
- "@wovin/core": "^0.0.18",
22
- "@wovin/connect-ucan-store-proxy": "^0.0.18"
21
+ "@wovin/core": "0.0.19",
22
+ "@wovin/connect-ucan-store-proxy": "0.0.19"
23
23
  },
24
24
  "dependencies": {
25
25
  "besonders-logger": "1.0.2",
@@ -29,8 +29,11 @@
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^22.15.21",
32
+ "@wovin/connect-ucan-store-proxy": "0.0.19",
33
+ "@wovin/core": "0.0.19",
32
34
  "concurrently": "^8.2.2",
33
35
  "tsup": "^8.5.0",
36
+ "tsx": "^4.19.2",
34
37
  "typescript": "^5.9.2",
35
38
  "tsupconfig": "^0.0.0"
36
39
  },
@@ -41,6 +44,7 @@
41
44
  "dev": "concurrently \"pnpm dev:code\" \"pnpm dev:types\"",
42
45
  "dev:code": "tsup --watch",
43
46
  "dev:types": "tsc --emitDeclarationOnly --declaration --watch",
47
+ "test": "tsx test.ts",
44
48
  "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
45
49
  "pub": "npm publish --tag latest --access=public"
46
50
  }
@@ -1,46 +0,0 @@
1
- import {
2
- g
3
- } from "./chunk-MZUPFLFH.min.js";
4
-
5
- // src/watcher.ts
6
- var { WARN, LOG, DEBUG, ERROR } = g.setup(g.INFO);
7
- var NAME_WS_URL = "wss://name.web3.storage/name";
8
- function watchName(name, options) {
9
- const url = `${NAME_WS_URL}/${name}/watch`;
10
- DEBUG("[w3name-watch] connecting to", url);
11
- const ws = new WebSocket(url);
12
- ws.onopen = () => {
13
- LOG("[w3name-watch] connected to", name);
14
- options.onOpen?.();
15
- };
16
- ws.onmessage = (event) => {
17
- try {
18
- const record = JSON.parse(event.data);
19
- DEBUG("[w3name-watch] received update for", name, record);
20
- options.onUpdate(record);
21
- } catch (err) {
22
- WARN("[w3name-watch] failed to parse message:", event.data, err);
23
- options.onError?.(err instanceof Error ? err : new Error(String(err)));
24
- }
25
- };
26
- ws.onerror = (event) => {
27
- ERROR("[w3name-watch] error for", name, event);
28
- options.onError?.(event);
29
- };
30
- ws.onclose = (event) => {
31
- DEBUG("[w3name-watch] closed for", name, "code:", event.code, "reason:", event.reason);
32
- options.onClose?.(event);
33
- };
34
- return {
35
- close: () => {
36
- DEBUG("[w3name-watch] closing connection for", name);
37
- ws.close();
38
- },
39
- ws
40
- };
41
- }
42
-
43
- export {
44
- watchName
45
- };
46
- //# sourceMappingURL=chunk-IAQU2NGT.min.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/watcher.ts"],"sourcesContent":["/**\n * IPNS Thread Watcher\n * Watches an IPNS thread for new audio files via WebSocket\n */\n\nimport { Logger } from 'besonders-logger'\n\nconst { WARN, LOG, DEBUG, ERROR } = Logger.setup(Logger.INFO)\n\nconst NAME_WS_URL = 'wss://name.web3.storage/name'\n\nexport interface W3NameRecord {\n\tvalue: string // e.g. \"/ipfs/bafybeig...\"\n\tseq?: number\n\tvalidity?: string\n}\n\nexport interface WatchSubscription {\n\tclose: () => void\n\tws: WebSocket\n}\n\nexport interface WatchOptions {\n\tonUpdate: (record: W3NameRecord) => void\n\tonError?: (error: Event | Error) => void\n\tonOpen?: () => void\n\tonClose?: (event: CloseEvent) => void\n}\n\n/**\n * Subscribe to IPNS name updates via WebSocket\n */\nexport function watchName(name: string, options: WatchOptions): WatchSubscription {\n\tconst url = `${NAME_WS_URL}/${name}/watch`\n\tDEBUG('[w3name-watch] connecting to', url)\n\n\tconst ws = new WebSocket(url)\n\n\tws.onopen = () => {\n\t\tLOG('[w3name-watch] 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('[w3name-watch] received update for', name, record)\n\t\t\toptions.onUpdate(record)\n\t\t} catch (err) {\n\t\t\tWARN('[w3name-watch] 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\tERROR('[w3name-watch] error for', name, event)\n\t\toptions.onError?.(event)\n\t}\n\n\tws.onclose = (event) => {\n\t\tDEBUG('[w3name-watch] closed for', name, 'code:', event.code, 'reason:', event.reason)\n\t\toptions.onClose?.(event)\n\t}\n\n\treturn {\n\t\tclose: () => {\n\t\t\tDEBUG('[w3name-watch] closing connection for', name)\n\t\t\tws.close()\n\t\t},\n\t\tws,\n\t}\n}\n"],"mappings":";;;;;AAOA,IAAM,EAAE,MAAM,KAAK,OAAO,MAAM,IAAI,EAAO,MAAM,EAAO,IAAI;AAE5D,IAAM,cAAc;AAuBb,SAAS,UAAU,MAAc,SAA0C;AACjF,QAAM,MAAM,GAAG,WAAW,IAAI,IAAI;AAClC,QAAM,gCAAgC,GAAG;AAEzC,QAAM,KAAK,IAAI,UAAU,GAAG;AAE5B,KAAG,SAAS,MAAM;AACjB,QAAI,+BAA+B,IAAI;AACvC,YAAQ,SAAS;AAAA,EAClB;AAEA,KAAG,YAAY,CAAC,UAAU;AACzB,QAAI;AACH,YAAM,SAAuB,KAAK,MAAM,MAAM,IAAI;AAClD,YAAM,sCAAsC,MAAM,MAAM;AACxD,cAAQ,SAAS,MAAM;AAAA,IACxB,SAAS,KAAK;AACb,WAAK,2CAA2C,MAAM,MAAM,GAAG;AAC/D,cAAQ,UAAU,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AAAA,IACtE;AAAA,EACD;AAEA,KAAG,UAAU,CAAC,UAAU;AACvB,UAAM,4BAA4B,MAAM,KAAK;AAC7C,YAAQ,UAAU,KAAK;AAAA,EACxB;AAEA,KAAG,UAAU,CAAC,UAAU;AACvB,UAAM,6BAA6B,MAAM,SAAS,MAAM,MAAM,WAAW,MAAM,MAAM;AACrF,YAAQ,UAAU,KAAK;AAAA,EACxB;AAEA,SAAO;AAAA,IACN,OAAO,MAAM;AACZ,YAAM,yCAAyC,IAAI;AACnD,SAAG,MAAM;AAAA,IACV;AAAA,IACA;AAAA,EACD;AACD;","names":[]}