@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.
- package/dist/chunk-GB53WLYA.min.js +174 -0
- package/dist/chunk-GB53WLYA.min.js.map +1 -0
- package/dist/index.min.js +7 -3
- package/dist/watcher.d.ts +39 -2
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.min.js +7 -3
- package/package.json +7 -3
- package/dist/chunk-IAQU2NGT.min.js +0 -46
- package/dist/chunk-IAQU2NGT.min.js.map +0 -1
|
@@ -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
|
-
|
|
19
|
-
|
|
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
|
|
58
|
+
* Subscribe to IPNS name updates with auto-reconnect and catch-up logic
|
|
22
59
|
*/
|
|
23
|
-
export declare function watchName(name: string, options:
|
|
60
|
+
export declare function watchName(name: string, options: IpnsWatcherOptions): IpnsWatcher;
|
|
24
61
|
//# sourceMappingURL=watcher.d.ts.map
|
package/dist/watcher.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
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"}
|
package/dist/watcher.min.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
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.
|
|
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": "
|
|
22
|
-
"@wovin/connect-ucan-store-proxy": "
|
|
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":[]}
|