nostr-wot-sdk 0.2.0 → 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.
- package/LICENSE +1 -1
- package/README.md +121 -12
- package/dist/index.cjs +290 -22
- package/dist/index.d.cts +121 -2
- package/dist/index.d.ts +121 -2
- package/dist/index.js +282 -23
- package/dist/local/index.cjs +23 -0
- package/dist/local/index.cjs.map +1 -1
- package/dist/local/index.js +23 -0
- package/dist/local/index.js.map +1 -1
- package/dist/react/index.cjs +346 -40
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +121 -5
- package/dist/react/index.d.ts +121 -5
- package/dist/react/index.js +347 -42
- package/dist/react/index.js.map +1 -1
- package/package.json +3 -3
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ npm install nostr-wot-sdk
|
|
|
11
11
|
|
|
12
12
|
### With Browser Extension (Recommended)
|
|
13
13
|
|
|
14
|
-
Install the [Nostr WoT Extension](https://github.com/
|
|
14
|
+
Install the [Nostr WoT Extension](https://github.com/nostr-wot/nostr-wot-extension) for the best experience. The extension downloads your follow graph locally and works across all websites.
|
|
15
15
|
|
|
16
16
|
```javascript
|
|
17
17
|
import { WoT } from 'nostr-wot-sdk';
|
|
@@ -72,7 +72,7 @@ const wot = new WoT(options);
|
|
|
72
72
|
|
|
73
73
|
| Option | Type | Default | Description |
|
|
74
74
|
|--------|------|---------|-------------|
|
|
75
|
-
| `useExtension` | boolean | `false
|
|
75
|
+
| `useExtension` | boolean | `false`* | Use browser extension if available (recommended) |
|
|
76
76
|
| `oracle` | string | `'https://nostr-wot.com'` | Oracle API URL (fallback) |
|
|
77
77
|
| `myPubkey` | string | — | Your pubkey (optional with extension, required otherwise) |
|
|
78
78
|
| `maxHops` | number | `3` | Default max search depth |
|
|
@@ -80,6 +80,8 @@ const wot = new WoT(options);
|
|
|
80
80
|
| `scoring` | object | See below | Trust score weights |
|
|
81
81
|
| `fallback` | object | — | Fallback config when extension unavailable |
|
|
82
82
|
|
|
83
|
+
*Note: When using the React `WoTProvider`, `useExtension` defaults to `true`.
|
|
84
|
+
|
|
83
85
|
**Note:** When `useExtension: true` and the extension is installed, the extension's pubkey and data are always used, regardless of `myPubkey` or `oracle` settings.
|
|
84
86
|
|
|
85
87
|
### Methods
|
|
@@ -229,7 +231,7 @@ const status = await wot.isConfigured();
|
|
|
229
231
|
|
|
230
232
|
## Browser Extension
|
|
231
233
|
|
|
232
|
-
Install the [Nostr WoT Extension](https://github.com/
|
|
234
|
+
Install the [Nostr WoT Extension](https://github.com/nostr-wot/nostr-wot-extension) for:
|
|
233
235
|
|
|
234
236
|
- **Local Data** — Downloads and caches your follow graph locally
|
|
235
237
|
- **Fast Queries** — No network requests needed after sync
|
|
@@ -237,7 +239,7 @@ Install the [Nostr WoT Extension](https://github.com/mappingbitcoin/nostr-wot-ex
|
|
|
237
239
|
- **Privacy** — Queries never leave your browser
|
|
238
240
|
- **Offline** — Works without internet once synced
|
|
239
241
|
|
|
240
|
-
The SDK automatically detects
|
|
242
|
+
The SDK automatically detects and connects to the extension using an event-based handshake. When the extension is present, it **always takes priority** over oracle settings.
|
|
241
243
|
|
|
242
244
|
```javascript
|
|
243
245
|
const wot = new WoT({
|
|
@@ -256,6 +258,50 @@ if (await wot.isUsingExtension()) {
|
|
|
256
258
|
}
|
|
257
259
|
```
|
|
258
260
|
|
|
261
|
+
### Extension Connection Utilities
|
|
262
|
+
|
|
263
|
+
For advanced use cases, the SDK exports low-level extension connection functions:
|
|
264
|
+
|
|
265
|
+
```javascript
|
|
266
|
+
import {
|
|
267
|
+
checkExtension, // Check if extension is installed
|
|
268
|
+
connectExtension, // Connect to the extension
|
|
269
|
+
checkAndConnect, // Check and connect in one call
|
|
270
|
+
ExtensionConnector // Stateful connector class
|
|
271
|
+
} from 'nostr-wot-sdk';
|
|
272
|
+
|
|
273
|
+
// Check if extension is installed (100ms timeout)
|
|
274
|
+
const isInstalled = await checkExtension();
|
|
275
|
+
|
|
276
|
+
// Connect to extension (5s timeout)
|
|
277
|
+
const extension = await connectExtension();
|
|
278
|
+
|
|
279
|
+
// Or do both in one call
|
|
280
|
+
const result = await checkAndConnect();
|
|
281
|
+
if (result.state === 'connected') {
|
|
282
|
+
const distance = await result.extension.getDistance('target...');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// For stateful connection management
|
|
286
|
+
const connector = new ExtensionConnector();
|
|
287
|
+
connector.subscribe((result) => {
|
|
288
|
+
console.log('State changed:', result.state);
|
|
289
|
+
});
|
|
290
|
+
await connector.connect();
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
#### Extension Events
|
|
294
|
+
|
|
295
|
+
The SDK uses a standard event-based protocol to communicate with the extension:
|
|
296
|
+
|
|
297
|
+
| Event | Direction | Purpose |
|
|
298
|
+
|-------|-----------|---------|
|
|
299
|
+
| `nostr-wot-check` | Page → Extension | Check if extension installed |
|
|
300
|
+
| `nostr-wot-present` | Extension → Page | Response confirming presence |
|
|
301
|
+
| `nostr-wot-connect` | Page → Extension | Request API injection |
|
|
302
|
+
| `nostr-wot-ready` | Extension → Page | API is ready at `window.nostr.wot` |
|
|
303
|
+
| `nostr-wot-error` | Extension → Page | Injection failed with error |
|
|
304
|
+
|
|
259
305
|
## Custom Scoring
|
|
260
306
|
|
|
261
307
|
Define how trust scores are calculated:
|
|
@@ -311,19 +357,33 @@ Storage options: `'memory'` (default), `'indexeddb'` (browser), or custom adapte
|
|
|
311
357
|
## Framework Integration
|
|
312
358
|
|
|
313
359
|
### React
|
|
360
|
+
|
|
361
|
+
The SDK provides first-class React support with automatic extension detection and connection. Just wrap your app with `WoTProvider` and you're ready to go — no additional configuration needed.
|
|
362
|
+
|
|
314
363
|
```javascript
|
|
315
|
-
import { useWoT,
|
|
364
|
+
import { WoTProvider, useWoT, useExtension } from 'nostr-wot-sdk/react';
|
|
316
365
|
|
|
317
|
-
// Wrap your app
|
|
366
|
+
// Wrap your app - automatically connects to extension
|
|
318
367
|
function App() {
|
|
319
368
|
return (
|
|
320
|
-
<WoTProvider
|
|
369
|
+
<WoTProvider>
|
|
321
370
|
<YourApp />
|
|
322
371
|
</WoTProvider>
|
|
323
372
|
);
|
|
324
373
|
}
|
|
325
374
|
|
|
326
|
-
//
|
|
375
|
+
// Check extension status anywhere
|
|
376
|
+
function ExtensionStatus() {
|
|
377
|
+
const { isConnected, isConnecting, isInstalled, error } = useExtension();
|
|
378
|
+
|
|
379
|
+
if (isConnecting) return <span>Connecting to extension...</span>;
|
|
380
|
+
if (!isInstalled) return <span>Install the WoT extension for best experience</span>;
|
|
381
|
+
if (error) return <span>Error: {error}</span>;
|
|
382
|
+
if (isConnected) return <span>Connected to extension!</span>;
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Use WoT data in components
|
|
327
387
|
function Profile({ pubkey }) {
|
|
328
388
|
const { distance, score, loading } = useWoT(pubkey);
|
|
329
389
|
|
|
@@ -341,14 +401,63 @@ function Profile({ pubkey }) {
|
|
|
341
401
|
}
|
|
342
402
|
```
|
|
343
403
|
|
|
404
|
+
#### Provider Options
|
|
405
|
+
|
|
406
|
+
```javascript
|
|
407
|
+
// With fallback for when extension is not installed
|
|
408
|
+
<WoTProvider options={{
|
|
409
|
+
fallback: { myPubkey: 'abc123...' }
|
|
410
|
+
}}>
|
|
411
|
+
|
|
412
|
+
// Oracle-only mode (no extension)
|
|
413
|
+
<WoTProvider options={{
|
|
414
|
+
useExtension: false,
|
|
415
|
+
myPubkey: 'abc123...'
|
|
416
|
+
}}>
|
|
417
|
+
|
|
418
|
+
// Custom extension connection timeouts
|
|
419
|
+
<WoTProvider extensionOptions={{
|
|
420
|
+
checkTimeout: 100, // Extension detection timeout (ms)
|
|
421
|
+
connectTimeout: 5000 // Connection timeout (ms)
|
|
422
|
+
}}>
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
#### Available Hooks
|
|
426
|
+
|
|
427
|
+
| Hook | Description |
|
|
428
|
+
|------|-------------|
|
|
429
|
+
| `useWoT(pubkey)` | Get distance, score, and details for a pubkey |
|
|
430
|
+
| `useIsInWoT(pubkey)` | Check if pubkey is in your WoT (boolean) |
|
|
431
|
+
| `useTrustScore(pubkey)` | Get trust score only |
|
|
432
|
+
| `useBatchWoT(pubkeys[])` | Check multiple pubkeys efficiently |
|
|
433
|
+
| `useExtension()` | Get extension connection state |
|
|
434
|
+
| `useWoTInstance()` | Get raw WoT instance for advanced usage |
|
|
435
|
+
|
|
436
|
+
#### Extension State
|
|
437
|
+
|
|
438
|
+
The `useExtension()` hook provides detailed extension status:
|
|
439
|
+
|
|
440
|
+
```javascript
|
|
441
|
+
const {
|
|
442
|
+
state, // 'idle' | 'checking' | 'connecting' | 'connected' | 'not-installed' | 'error'
|
|
443
|
+
isConnected, // Extension is connected and ready
|
|
444
|
+
isConnecting, // Currently checking/connecting
|
|
445
|
+
isInstalled, // Extension is installed (may still be connecting)
|
|
446
|
+
isChecked, // Initial check complete
|
|
447
|
+
error, // Error message if connection failed
|
|
448
|
+
connect, // Function to manually retry connection
|
|
449
|
+
} = useExtension();
|
|
450
|
+
```
|
|
451
|
+
|
|
344
452
|
## TypeScript
|
|
345
453
|
|
|
346
454
|
Full type definitions included:
|
|
347
455
|
```typescript
|
|
348
456
|
import { WoT, DistanceResult, WoTOptions } from 'nostr-wot-sdk';
|
|
349
457
|
|
|
350
|
-
const
|
|
351
|
-
const
|
|
458
|
+
const options: WoTOptions = { useExtension: true };
|
|
459
|
+
const wot = new WoT(options);
|
|
460
|
+
const result: DistanceResult | null = await wot.getDetails(pubkey);
|
|
352
461
|
const score: number = await wot.getTrustScore(pubkey);
|
|
353
462
|
```
|
|
354
463
|
|
|
@@ -369,8 +478,8 @@ try {
|
|
|
369
478
|
|
|
370
479
|
## Related
|
|
371
480
|
|
|
372
|
-
- [Nostr WoT Extension](https://github.com/
|
|
373
|
-
- [WoT Oracle](https://github.com/
|
|
481
|
+
- [Nostr WoT Extension](https://github.com/nostr-wot/nostr-wot-extension) — Browser extension (recommended)
|
|
482
|
+
- [WoT Oracle](https://github.com/nostr-wot/nostr-wot-oracle) — Backend service
|
|
374
483
|
- [nostr-wot.com](https://nostr-wot.com) — Public oracle & docs
|
|
375
484
|
|
|
376
485
|
## License
|
package/dist/index.cjs
CHANGED
|
@@ -79,6 +79,23 @@ var DEFAULT_TIMEOUT = 5e3;
|
|
|
79
79
|
function isValidPubkey(pubkey) {
|
|
80
80
|
return /^[0-9a-f]{64}$/i.test(pubkey);
|
|
81
81
|
}
|
|
82
|
+
function isValidOracleUrl(url) {
|
|
83
|
+
try {
|
|
84
|
+
const parsed = new URL(url);
|
|
85
|
+
return parsed.protocol === "https:";
|
|
86
|
+
} catch (e) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function isValidRelayUrl(url) {
|
|
91
|
+
try {
|
|
92
|
+
const parsed = new URL(url);
|
|
93
|
+
return parsed.protocol === "wss:" || parsed.protocol === "ws:";
|
|
94
|
+
} catch (e) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
var MAX_BATCH_SIZE = 1e4;
|
|
82
99
|
function normalizePubkey(pubkey) {
|
|
83
100
|
return pubkey.toLowerCase();
|
|
84
101
|
}
|
|
@@ -118,11 +135,10 @@ async function fetchWithTimeout(url, options = {}) {
|
|
|
118
135
|
const controller = new AbortController();
|
|
119
136
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
120
137
|
try {
|
|
121
|
-
|
|
138
|
+
return await fetch(url, {
|
|
122
139
|
...fetchOptions,
|
|
123
140
|
signal: controller.signal
|
|
124
141
|
});
|
|
125
|
-
return response;
|
|
126
142
|
} finally {
|
|
127
143
|
clearTimeout(timeoutId);
|
|
128
144
|
}
|
|
@@ -135,6 +151,231 @@ function chunk(array, size) {
|
|
|
135
151
|
return chunks;
|
|
136
152
|
}
|
|
137
153
|
|
|
154
|
+
// src/extension.ts
|
|
155
|
+
function checkExtension(timeout = 100) {
|
|
156
|
+
return new Promise((resolve) => {
|
|
157
|
+
var _a;
|
|
158
|
+
if (typeof window === "undefined") {
|
|
159
|
+
resolve(false);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const win = window;
|
|
163
|
+
if ((_a = win.nostr) == null ? void 0 : _a.wot) {
|
|
164
|
+
resolve(true);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const handler = () => {
|
|
168
|
+
window.removeEventListener("nostr-wot-present", handler);
|
|
169
|
+
clearTimeout(timer);
|
|
170
|
+
resolve(true);
|
|
171
|
+
};
|
|
172
|
+
window.addEventListener("nostr-wot-present", handler);
|
|
173
|
+
const timer = setTimeout(() => {
|
|
174
|
+
window.removeEventListener("nostr-wot-present", handler);
|
|
175
|
+
resolve(false);
|
|
176
|
+
}, timeout);
|
|
177
|
+
window.dispatchEvent(new CustomEvent("nostr-wot-check"));
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
function connectExtension(timeout = 5e3) {
|
|
181
|
+
return new Promise((resolve, reject) => {
|
|
182
|
+
var _a;
|
|
183
|
+
if (typeof window === "undefined") {
|
|
184
|
+
reject(new Error("Not in browser environment"));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const win = window;
|
|
188
|
+
if ((_a = win.nostr) == null ? void 0 : _a.wot) {
|
|
189
|
+
resolve(win.nostr.wot);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
let timer;
|
|
193
|
+
const onReady = () => {
|
|
194
|
+
var _a2;
|
|
195
|
+
cleanup();
|
|
196
|
+
const ext = (_a2 = window.nostr) == null ? void 0 : _a2.wot;
|
|
197
|
+
if (ext) {
|
|
198
|
+
resolve(ext);
|
|
199
|
+
} else {
|
|
200
|
+
reject(new Error("Extension ready event fired but API not found"));
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
const onError = (e) => {
|
|
204
|
+
cleanup();
|
|
205
|
+
const detail = e.detail;
|
|
206
|
+
reject(new Error((detail == null ? void 0 : detail.error) || "Connection failed"));
|
|
207
|
+
};
|
|
208
|
+
const cleanup = () => {
|
|
209
|
+
clearTimeout(timer);
|
|
210
|
+
window.removeEventListener("nostr-wot-ready", onReady);
|
|
211
|
+
window.removeEventListener("nostr-wot-error", onError);
|
|
212
|
+
};
|
|
213
|
+
window.addEventListener("nostr-wot-ready", onReady);
|
|
214
|
+
window.addEventListener("nostr-wot-error", onError);
|
|
215
|
+
window.dispatchEvent(new CustomEvent("nostr-wot-connect"));
|
|
216
|
+
timer = setTimeout(() => {
|
|
217
|
+
cleanup();
|
|
218
|
+
reject(new Error("Connection timeout"));
|
|
219
|
+
}, timeout);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
async function checkAndConnect(options = {}) {
|
|
223
|
+
var _a;
|
|
224
|
+
const { checkTimeout = 100, connectTimeout = 5e3, autoConnect = true } = options;
|
|
225
|
+
if (typeof window === "undefined") {
|
|
226
|
+
return { state: "not-installed", extension: null };
|
|
227
|
+
}
|
|
228
|
+
const win = window;
|
|
229
|
+
if ((_a = win.nostr) == null ? void 0 : _a.wot) {
|
|
230
|
+
return { state: "connected", extension: win.nostr.wot };
|
|
231
|
+
}
|
|
232
|
+
const isInstalled = await checkExtension(checkTimeout);
|
|
233
|
+
if (!isInstalled) {
|
|
234
|
+
return { state: "not-installed", extension: null };
|
|
235
|
+
}
|
|
236
|
+
if (!autoConnect) {
|
|
237
|
+
return { state: "idle", extension: null };
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
const extension = await connectExtension(connectTimeout);
|
|
241
|
+
return { state: "connected", extension };
|
|
242
|
+
} catch (error) {
|
|
243
|
+
return {
|
|
244
|
+
state: "error",
|
|
245
|
+
extension: null,
|
|
246
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
var ExtensionConnector = class {
|
|
251
|
+
constructor(options = {}) {
|
|
252
|
+
this.options = options;
|
|
253
|
+
this.state = "idle";
|
|
254
|
+
this.extension = null;
|
|
255
|
+
this.connectionPromise = null;
|
|
256
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Get current state
|
|
260
|
+
*/
|
|
261
|
+
getState() {
|
|
262
|
+
return this.state;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Get current extension instance
|
|
266
|
+
*/
|
|
267
|
+
getExtension() {
|
|
268
|
+
return this.extension;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Get current error
|
|
272
|
+
*/
|
|
273
|
+
getError() {
|
|
274
|
+
return this.error;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Get current result
|
|
278
|
+
*/
|
|
279
|
+
getResult() {
|
|
280
|
+
return {
|
|
281
|
+
state: this.state,
|
|
282
|
+
extension: this.extension,
|
|
283
|
+
error: this.error
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Subscribe to state changes
|
|
288
|
+
*/
|
|
289
|
+
subscribe(listener) {
|
|
290
|
+
this.listeners.add(listener);
|
|
291
|
+
return () => {
|
|
292
|
+
this.listeners.delete(listener);
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
notify() {
|
|
296
|
+
const result = this.getResult();
|
|
297
|
+
for (const listener of this.listeners) {
|
|
298
|
+
listener(result);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
setState(state, extension = null, error) {
|
|
302
|
+
this.state = state;
|
|
303
|
+
this.extension = extension;
|
|
304
|
+
this.error = error;
|
|
305
|
+
this.notify();
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Connect to the extension
|
|
309
|
+
* Returns existing promise if already connecting
|
|
310
|
+
*/
|
|
311
|
+
async connect() {
|
|
312
|
+
if (this.state === "connected" && this.extension) {
|
|
313
|
+
return this.getResult();
|
|
314
|
+
}
|
|
315
|
+
if (this.connectionPromise) {
|
|
316
|
+
return this.connectionPromise;
|
|
317
|
+
}
|
|
318
|
+
this.connectionPromise = this.doConnect();
|
|
319
|
+
const result = await this.connectionPromise;
|
|
320
|
+
this.connectionPromise = null;
|
|
321
|
+
return result;
|
|
322
|
+
}
|
|
323
|
+
async doConnect() {
|
|
324
|
+
var _a;
|
|
325
|
+
const { checkTimeout = 100, connectTimeout = 5e3 } = this.options;
|
|
326
|
+
if (typeof window === "undefined") {
|
|
327
|
+
this.setState("not-installed");
|
|
328
|
+
return this.getResult();
|
|
329
|
+
}
|
|
330
|
+
const win = window;
|
|
331
|
+
if ((_a = win.nostr) == null ? void 0 : _a.wot) {
|
|
332
|
+
this.setState("connected", win.nostr.wot);
|
|
333
|
+
return this.getResult();
|
|
334
|
+
}
|
|
335
|
+
this.setState("checking");
|
|
336
|
+
const isInstalled = await checkExtension(checkTimeout);
|
|
337
|
+
if (!isInstalled) {
|
|
338
|
+
this.setState("not-installed");
|
|
339
|
+
return this.getResult();
|
|
340
|
+
}
|
|
341
|
+
this.setState("connecting");
|
|
342
|
+
try {
|
|
343
|
+
const extension = await connectExtension(connectTimeout);
|
|
344
|
+
this.setState("connected", extension);
|
|
345
|
+
} catch (error) {
|
|
346
|
+
this.setState(
|
|
347
|
+
"error",
|
|
348
|
+
null,
|
|
349
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
return this.getResult();
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Reset state and disconnect
|
|
356
|
+
*/
|
|
357
|
+
reset() {
|
|
358
|
+
this.state = "idle";
|
|
359
|
+
this.extension = null;
|
|
360
|
+
this.error = void 0;
|
|
361
|
+
this.connectionPromise = null;
|
|
362
|
+
this.notify();
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
var defaultConnector = null;
|
|
366
|
+
function getDefaultConnector(options) {
|
|
367
|
+
if (!defaultConnector) {
|
|
368
|
+
defaultConnector = new ExtensionConnector(options);
|
|
369
|
+
}
|
|
370
|
+
return defaultConnector;
|
|
371
|
+
}
|
|
372
|
+
function resetDefaultConnector() {
|
|
373
|
+
if (defaultConnector) {
|
|
374
|
+
defaultConnector.reset();
|
|
375
|
+
defaultConnector = null;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
138
379
|
// src/wot.ts
|
|
139
380
|
var WoT = class {
|
|
140
381
|
constructor(options) {
|
|
@@ -164,13 +405,18 @@ var WoT = class {
|
|
|
164
405
|
this.fallbackPubkey = null;
|
|
165
406
|
}
|
|
166
407
|
}
|
|
167
|
-
|
|
408
|
+
const oracleUrl = (_f = (_e = options.oracle) != null ? _e : (_d = this.fallbackOptions) == null ? void 0 : _d.oracle) != null ? _f : DEFAULT_ORACLE;
|
|
409
|
+
if (!isValidOracleUrl(oracleUrl)) {
|
|
410
|
+
throw new ValidationError("oracle must be a valid HTTPS URL", "oracle");
|
|
411
|
+
}
|
|
412
|
+
this.oracle = oracleUrl;
|
|
168
413
|
this.maxHops = (_i = (_h = options.maxHops) != null ? _h : (_g = this.fallbackOptions) == null ? void 0 : _g.maxHops) != null ? _i : DEFAULT_MAX_HOPS;
|
|
169
414
|
this.timeout = (_l = (_k = options.timeout) != null ? _k : (_j = this.fallbackOptions) == null ? void 0 : _j.timeout) != null ? _l : DEFAULT_TIMEOUT;
|
|
170
415
|
this.scoring = mergeScoringConfig((_n = options.scoring) != null ? _n : (_m = this.fallbackOptions) == null ? void 0 : _m.scoring);
|
|
171
416
|
}
|
|
172
417
|
/**
|
|
173
418
|
* Checks if browser extension is available and returns it
|
|
419
|
+
* Uses event-based connection flow for reliable detection
|
|
174
420
|
*/
|
|
175
421
|
async getExtension() {
|
|
176
422
|
var _a, _b;
|
|
@@ -181,6 +427,17 @@ var WoT = class {
|
|
|
181
427
|
const win = window;
|
|
182
428
|
if ((_a = win.nostr) == null ? void 0 : _a.wot) {
|
|
183
429
|
this.extension = win.nostr.wot;
|
|
430
|
+
} else {
|
|
431
|
+
const result = await checkAndConnect({
|
|
432
|
+
checkTimeout: 100,
|
|
433
|
+
connectTimeout: 5e3,
|
|
434
|
+
autoConnect: true
|
|
435
|
+
});
|
|
436
|
+
if (result.state === "connected" && result.extension) {
|
|
437
|
+
this.extension = result.extension;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (this.extension) {
|
|
184
441
|
try {
|
|
185
442
|
this.extensionPubkey = await this.extension.getMyPubkey();
|
|
186
443
|
} catch (e) {
|
|
@@ -217,28 +474,15 @@ var WoT = class {
|
|
|
217
474
|
var _a;
|
|
218
475
|
const timeout = (_a = options.timeout) != null ? _a : this.timeout;
|
|
219
476
|
const url = `${this.oracle}/api${endpoint}`;
|
|
477
|
+
let response;
|
|
220
478
|
try {
|
|
221
|
-
|
|
479
|
+
response = await fetchWithTimeout(url, {
|
|
222
480
|
timeout,
|
|
223
481
|
headers: {
|
|
224
482
|
"Content-Type": "application/json"
|
|
225
483
|
}
|
|
226
484
|
});
|
|
227
|
-
if (!response.ok) {
|
|
228
|
-
if (response.status === 404) {
|
|
229
|
-
throw new NotFoundError("", `Resource not found: ${endpoint}`);
|
|
230
|
-
}
|
|
231
|
-
throw new NetworkError(
|
|
232
|
-
`HTTP ${response.status}: ${response.statusText}`,
|
|
233
|
-
response.status,
|
|
234
|
-
url
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
return await response.json();
|
|
238
485
|
} catch (error) {
|
|
239
|
-
if (error instanceof NotFoundError || error instanceof NetworkError) {
|
|
240
|
-
throw error;
|
|
241
|
-
}
|
|
242
486
|
if (error instanceof Error) {
|
|
243
487
|
if (error.name === "AbortError") {
|
|
244
488
|
throw new TimeoutError(timeout);
|
|
@@ -247,6 +491,17 @@ var WoT = class {
|
|
|
247
491
|
}
|
|
248
492
|
throw new NetworkError("Unknown network error", void 0, url);
|
|
249
493
|
}
|
|
494
|
+
if (!response.ok) {
|
|
495
|
+
if (response.status === 404) {
|
|
496
|
+
throw new NotFoundError("", `Resource not found: ${endpoint}`);
|
|
497
|
+
}
|
|
498
|
+
throw new NetworkError(
|
|
499
|
+
`HTTP ${response.status}: ${response.statusText}`,
|
|
500
|
+
response.status,
|
|
501
|
+
url
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
return await response.json();
|
|
250
505
|
}
|
|
251
506
|
/**
|
|
252
507
|
* Validates a pubkey parameter
|
|
@@ -368,6 +623,12 @@ var WoT = class {
|
|
|
368
623
|
if (!Array.isArray(targets) || targets.length === 0) {
|
|
369
624
|
throw new ValidationError("targets must be a non-empty array", "targets");
|
|
370
625
|
}
|
|
626
|
+
if (targets.length > MAX_BATCH_SIZE) {
|
|
627
|
+
throw new ValidationError(
|
|
628
|
+
`targets array exceeds maximum size of ${MAX_BATCH_SIZE}`,
|
|
629
|
+
"targets"
|
|
630
|
+
);
|
|
631
|
+
}
|
|
371
632
|
const normalizedTargets = targets.map(
|
|
372
633
|
(t, i) => this.validatePubkey(t, `targets[${i}]`)
|
|
373
634
|
);
|
|
@@ -448,17 +709,15 @@ var WoT = class {
|
|
|
448
709
|
const normalizedTarget = this.validatePubkey(target, "target");
|
|
449
710
|
const ext = await this.getExtension();
|
|
450
711
|
if (ext) {
|
|
451
|
-
|
|
452
|
-
return result;
|
|
712
|
+
return ext.getDetails(normalizedTarget);
|
|
453
713
|
}
|
|
454
714
|
const myPubkey = await this.getEffectivePubkey();
|
|
455
715
|
const maxHops = (_a = options == null ? void 0 : options.maxHops) != null ? _a : this.maxHops;
|
|
456
716
|
try {
|
|
457
|
-
|
|
717
|
+
return await this.apiRequest(
|
|
458
718
|
`/details/${myPubkey}/${normalizedTarget}?maxHops=${maxHops}`,
|
|
459
719
|
options
|
|
460
720
|
);
|
|
461
|
-
return result;
|
|
462
721
|
} catch (error) {
|
|
463
722
|
if (error instanceof NotFoundError) {
|
|
464
723
|
return null;
|
|
@@ -640,6 +899,8 @@ exports.DEFAULT_MAX_HOPS = DEFAULT_MAX_HOPS;
|
|
|
640
899
|
exports.DEFAULT_ORACLE = DEFAULT_ORACLE;
|
|
641
900
|
exports.DEFAULT_SCORING = DEFAULT_SCORING;
|
|
642
901
|
exports.DEFAULT_TIMEOUT = DEFAULT_TIMEOUT;
|
|
902
|
+
exports.ExtensionConnector = ExtensionConnector;
|
|
903
|
+
exports.MAX_BATCH_SIZE = MAX_BATCH_SIZE;
|
|
643
904
|
exports.NetworkError = NetworkError;
|
|
644
905
|
exports.NotFoundError = NotFoundError;
|
|
645
906
|
exports.RelayError = RelayError;
|
|
@@ -649,5 +910,12 @@ exports.ValidationError = ValidationError;
|
|
|
649
910
|
exports.WoT = WoT;
|
|
650
911
|
exports.WoTError = WoTError;
|
|
651
912
|
exports.calculateTrustScore = calculateTrustScore;
|
|
913
|
+
exports.checkAndConnect = checkAndConnect;
|
|
914
|
+
exports.checkExtension = checkExtension;
|
|
915
|
+
exports.connectExtension = connectExtension;
|
|
916
|
+
exports.getDefaultConnector = getDefaultConnector;
|
|
917
|
+
exports.isValidOracleUrl = isValidOracleUrl;
|
|
652
918
|
exports.isValidPubkey = isValidPubkey;
|
|
919
|
+
exports.isValidRelayUrl = isValidRelayUrl;
|
|
653
920
|
exports.normalizePubkey = normalizePubkey;
|
|
921
|
+
exports.resetDefaultConnector = resetDefaultConnector;
|