nostr-wot-sdk 0.2.0 → 0.3.1
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 +128 -14
- package/dist/index.cjs +305 -29
- package/dist/index.d.cts +146 -8
- package/dist/index.d.ts +146 -8
- package/dist/index.js +297 -30
- 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 +361 -47
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +133 -8
- package/dist/react/index.d.ts +133 -8
- package/dist/react/index.js +362 -49
- 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
|
|
@@ -159,17 +161,22 @@ const config = await wot.getExtensionConfig();
|
|
|
159
161
|
|
|
160
162
|
### Batch Operations
|
|
161
163
|
|
|
162
|
-
#### `getDistanceBatch(targets)`
|
|
164
|
+
#### `getDistanceBatch(targets, includePaths?)`
|
|
163
165
|
|
|
164
166
|
Get distances for multiple pubkeys in a single call.
|
|
165
167
|
```javascript
|
|
168
|
+
// Without paths (faster, default)
|
|
166
169
|
const distances = await wot.getDistanceBatch(['pk1...', 'pk2...']);
|
|
167
170
|
// Returns: { 'pk1...': 2, 'pk2...': null }
|
|
171
|
+
|
|
172
|
+
// With paths (includes path count for scoring)
|
|
173
|
+
const details = await wot.getDistanceBatch(['pk1...', 'pk2...'], true);
|
|
174
|
+
// Returns: { 'pk1...': { hops: 2, paths: 5 }, 'pk2...': null }
|
|
168
175
|
```
|
|
169
176
|
|
|
170
177
|
#### `getTrustScoreBatch(targets)`
|
|
171
178
|
|
|
172
|
-
Get trust scores for multiple pubkeys in a single call.
|
|
179
|
+
Get trust scores for multiple pubkeys in a single call. Uses path counts internally for accurate scoring.
|
|
173
180
|
```javascript
|
|
174
181
|
const scores = await wot.getTrustScoreBatch(['pk1...', 'pk2...']);
|
|
175
182
|
// Returns: { 'pk1...': 0.72, 'pk2...': null }
|
|
@@ -229,7 +236,7 @@ const status = await wot.isConfigured();
|
|
|
229
236
|
|
|
230
237
|
## Browser Extension
|
|
231
238
|
|
|
232
|
-
Install the [Nostr WoT Extension](https://github.com/
|
|
239
|
+
Install the [Nostr WoT Extension](https://github.com/nostr-wot/nostr-wot-extension) for:
|
|
233
240
|
|
|
234
241
|
- **Local Data** — Downloads and caches your follow graph locally
|
|
235
242
|
- **Fast Queries** — No network requests needed after sync
|
|
@@ -237,7 +244,7 @@ Install the [Nostr WoT Extension](https://github.com/mappingbitcoin/nostr-wot-ex
|
|
|
237
244
|
- **Privacy** — Queries never leave your browser
|
|
238
245
|
- **Offline** — Works without internet once synced
|
|
239
246
|
|
|
240
|
-
The SDK automatically detects
|
|
247
|
+
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
248
|
|
|
242
249
|
```javascript
|
|
243
250
|
const wot = new WoT({
|
|
@@ -256,6 +263,50 @@ if (await wot.isUsingExtension()) {
|
|
|
256
263
|
}
|
|
257
264
|
```
|
|
258
265
|
|
|
266
|
+
### Extension Connection Utilities
|
|
267
|
+
|
|
268
|
+
For advanced use cases, the SDK exports low-level extension connection functions:
|
|
269
|
+
|
|
270
|
+
```javascript
|
|
271
|
+
import {
|
|
272
|
+
checkExtension, // Check if extension is installed
|
|
273
|
+
connectExtension, // Connect to the extension
|
|
274
|
+
checkAndConnect, // Check and connect in one call
|
|
275
|
+
ExtensionConnector // Stateful connector class
|
|
276
|
+
} from 'nostr-wot-sdk';
|
|
277
|
+
|
|
278
|
+
// Check if extension is installed (100ms timeout)
|
|
279
|
+
const isInstalled = await checkExtension();
|
|
280
|
+
|
|
281
|
+
// Connect to extension (5s timeout)
|
|
282
|
+
const extension = await connectExtension();
|
|
283
|
+
|
|
284
|
+
// Or do both in one call
|
|
285
|
+
const result = await checkAndConnect();
|
|
286
|
+
if (result.state === 'connected') {
|
|
287
|
+
const distance = await result.extension.getDistance('target...');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// For stateful connection management
|
|
291
|
+
const connector = new ExtensionConnector();
|
|
292
|
+
connector.subscribe((result) => {
|
|
293
|
+
console.log('State changed:', result.state);
|
|
294
|
+
});
|
|
295
|
+
await connector.connect();
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
#### Extension Events
|
|
299
|
+
|
|
300
|
+
The SDK uses a standard event-based protocol to communicate with the extension:
|
|
301
|
+
|
|
302
|
+
| Event | Direction | Purpose |
|
|
303
|
+
|-------|-----------|---------|
|
|
304
|
+
| `nostr-wot-check` | Page → Extension | Check if extension installed |
|
|
305
|
+
| `nostr-wot-present` | Extension → Page | Response confirming presence |
|
|
306
|
+
| `nostr-wot-connect` | Page → Extension | Request API injection |
|
|
307
|
+
| `nostr-wot-ready` | Extension → Page | API is ready at `window.nostr.wot` |
|
|
308
|
+
| `nostr-wot-error` | Extension → Page | Injection failed with error |
|
|
309
|
+
|
|
259
310
|
## Custom Scoring
|
|
260
311
|
|
|
261
312
|
Define how trust scores are calculated:
|
|
@@ -311,19 +362,33 @@ Storage options: `'memory'` (default), `'indexeddb'` (browser), or custom adapte
|
|
|
311
362
|
## Framework Integration
|
|
312
363
|
|
|
313
364
|
### React
|
|
365
|
+
|
|
366
|
+
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.
|
|
367
|
+
|
|
314
368
|
```javascript
|
|
315
|
-
import { useWoT,
|
|
369
|
+
import { WoTProvider, useWoT, useExtension } from 'nostr-wot-sdk/react';
|
|
316
370
|
|
|
317
|
-
// Wrap your app
|
|
371
|
+
// Wrap your app - automatically connects to extension
|
|
318
372
|
function App() {
|
|
319
373
|
return (
|
|
320
|
-
<WoTProvider
|
|
374
|
+
<WoTProvider>
|
|
321
375
|
<YourApp />
|
|
322
376
|
</WoTProvider>
|
|
323
377
|
);
|
|
324
378
|
}
|
|
325
379
|
|
|
326
|
-
//
|
|
380
|
+
// Check extension status anywhere
|
|
381
|
+
function ExtensionStatus() {
|
|
382
|
+
const { isConnected, isConnecting, isInstalled, error } = useExtension();
|
|
383
|
+
|
|
384
|
+
if (isConnecting) return <span>Connecting to extension...</span>;
|
|
385
|
+
if (!isInstalled) return <span>Install the WoT extension for best experience</span>;
|
|
386
|
+
if (error) return <span>Error: {error}</span>;
|
|
387
|
+
if (isConnected) return <span>Connected to extension!</span>;
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Use WoT data in components
|
|
327
392
|
function Profile({ pubkey }) {
|
|
328
393
|
const { distance, score, loading } = useWoT(pubkey);
|
|
329
394
|
|
|
@@ -341,14 +406,63 @@ function Profile({ pubkey }) {
|
|
|
341
406
|
}
|
|
342
407
|
```
|
|
343
408
|
|
|
409
|
+
#### Provider Options
|
|
410
|
+
|
|
411
|
+
```javascript
|
|
412
|
+
// With fallback for when extension is not installed
|
|
413
|
+
<WoTProvider options={{
|
|
414
|
+
fallback: { myPubkey: 'abc123...' }
|
|
415
|
+
}}>
|
|
416
|
+
|
|
417
|
+
// Oracle-only mode (no extension)
|
|
418
|
+
<WoTProvider options={{
|
|
419
|
+
useExtension: false,
|
|
420
|
+
myPubkey: 'abc123...'
|
|
421
|
+
}}>
|
|
422
|
+
|
|
423
|
+
// Custom extension connection timeouts
|
|
424
|
+
<WoTProvider extensionOptions={{
|
|
425
|
+
checkTimeout: 100, // Extension detection timeout (ms)
|
|
426
|
+
connectTimeout: 5000 // Connection timeout (ms)
|
|
427
|
+
}}>
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
#### Available Hooks
|
|
431
|
+
|
|
432
|
+
| Hook | Description |
|
|
433
|
+
|------|-------------|
|
|
434
|
+
| `useWoT(pubkey)` | Get distance, score, and details for a pubkey |
|
|
435
|
+
| `useIsInWoT(pubkey)` | Check if pubkey is in your WoT (boolean) |
|
|
436
|
+
| `useTrustScore(pubkey)` | Get trust score only |
|
|
437
|
+
| `useBatchWoT(pubkeys[])` | Check multiple pubkeys efficiently |
|
|
438
|
+
| `useExtension()` | Get extension connection state |
|
|
439
|
+
| `useWoTInstance()` | Get raw WoT instance for advanced usage |
|
|
440
|
+
|
|
441
|
+
#### Extension State
|
|
442
|
+
|
|
443
|
+
The `useExtension()` hook provides detailed extension status:
|
|
444
|
+
|
|
445
|
+
```javascript
|
|
446
|
+
const {
|
|
447
|
+
state, // 'idle' | 'checking' | 'connecting' | 'connected' | 'not-installed' | 'error'
|
|
448
|
+
isConnected, // Extension is connected and ready
|
|
449
|
+
isConnecting, // Currently checking/connecting
|
|
450
|
+
isInstalled, // Extension is installed (may still be connecting)
|
|
451
|
+
isChecked, // Initial check complete
|
|
452
|
+
error, // Error message if connection failed
|
|
453
|
+
connect, // Function to manually retry connection
|
|
454
|
+
} = useExtension();
|
|
455
|
+
```
|
|
456
|
+
|
|
344
457
|
## TypeScript
|
|
345
458
|
|
|
346
459
|
Full type definitions included:
|
|
347
460
|
```typescript
|
|
348
461
|
import { WoT, DistanceResult, WoTOptions } from 'nostr-wot-sdk';
|
|
349
462
|
|
|
350
|
-
const
|
|
351
|
-
const
|
|
463
|
+
const options: WoTOptions = { useExtension: true };
|
|
464
|
+
const wot = new WoT(options);
|
|
465
|
+
const result: DistanceResult | null = await wot.getDetails(pubkey);
|
|
352
466
|
const score: number = await wot.getTrustScore(pubkey);
|
|
353
467
|
```
|
|
354
468
|
|
|
@@ -369,8 +483,8 @@ try {
|
|
|
369
483
|
|
|
370
484
|
## Related
|
|
371
485
|
|
|
372
|
-
- [Nostr WoT Extension](https://github.com/
|
|
373
|
-
- [WoT Oracle](https://github.com/
|
|
486
|
+
- [Nostr WoT Extension](https://github.com/nostr-wot/nostr-wot-extension) — Browser extension (recommended)
|
|
487
|
+
- [WoT Oracle](https://github.com/nostr-wot/nostr-wot-oracle) — Backend service
|
|
374
488
|
- [nostr-wot.com](https://nostr-wot.com) — Public oracle & docs
|
|
375
489
|
|
|
376
490
|
## 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;
|
|
@@ -585,12 +844,7 @@ var WoT = class {
|
|
|
585
844
|
const normalizedTarget = this.validatePubkey(target, "target");
|
|
586
845
|
return ext.getPath(normalizedTarget);
|
|
587
846
|
}
|
|
588
|
-
|
|
589
|
-
* Get distances for multiple pubkeys in a single call
|
|
590
|
-
* @param targets - Array of target pubkeys
|
|
591
|
-
* @returns Record of pubkey to hop count (null if not connected)
|
|
592
|
-
*/
|
|
593
|
-
async getDistanceBatch(targets) {
|
|
847
|
+
async getDistanceBatch(targets, includePaths = false) {
|
|
594
848
|
if (!Array.isArray(targets) || targets.length === 0) {
|
|
595
849
|
return {};
|
|
596
850
|
}
|
|
@@ -599,7 +853,20 @@ var WoT = class {
|
|
|
599
853
|
);
|
|
600
854
|
const ext = await this.getExtension();
|
|
601
855
|
if (ext) {
|
|
602
|
-
|
|
856
|
+
if (includePaths) {
|
|
857
|
+
return ext.getDistanceBatch(normalizedTargets, true);
|
|
858
|
+
}
|
|
859
|
+
return ext.getDistanceBatch(normalizedTargets, false);
|
|
860
|
+
}
|
|
861
|
+
if (includePaths) {
|
|
862
|
+
const results2 = {};
|
|
863
|
+
await Promise.all(
|
|
864
|
+
normalizedTargets.map(async (pubkey) => {
|
|
865
|
+
const details = await this.getDetails(pubkey);
|
|
866
|
+
results2[pubkey] = details ? { hops: details.hops, paths: details.paths } : null;
|
|
867
|
+
})
|
|
868
|
+
);
|
|
869
|
+
return results2;
|
|
603
870
|
}
|
|
604
871
|
const results = {};
|
|
605
872
|
await Promise.all(
|
|
@@ -640,6 +907,8 @@ exports.DEFAULT_MAX_HOPS = DEFAULT_MAX_HOPS;
|
|
|
640
907
|
exports.DEFAULT_ORACLE = DEFAULT_ORACLE;
|
|
641
908
|
exports.DEFAULT_SCORING = DEFAULT_SCORING;
|
|
642
909
|
exports.DEFAULT_TIMEOUT = DEFAULT_TIMEOUT;
|
|
910
|
+
exports.ExtensionConnector = ExtensionConnector;
|
|
911
|
+
exports.MAX_BATCH_SIZE = MAX_BATCH_SIZE;
|
|
643
912
|
exports.NetworkError = NetworkError;
|
|
644
913
|
exports.NotFoundError = NotFoundError;
|
|
645
914
|
exports.RelayError = RelayError;
|
|
@@ -649,5 +918,12 @@ exports.ValidationError = ValidationError;
|
|
|
649
918
|
exports.WoT = WoT;
|
|
650
919
|
exports.WoTError = WoTError;
|
|
651
920
|
exports.calculateTrustScore = calculateTrustScore;
|
|
921
|
+
exports.checkAndConnect = checkAndConnect;
|
|
922
|
+
exports.checkExtension = checkExtension;
|
|
923
|
+
exports.connectExtension = connectExtension;
|
|
924
|
+
exports.getDefaultConnector = getDefaultConnector;
|
|
925
|
+
exports.isValidOracleUrl = isValidOracleUrl;
|
|
652
926
|
exports.isValidPubkey = isValidPubkey;
|
|
927
|
+
exports.isValidRelayUrl = isValidRelayUrl;
|
|
653
928
|
exports.normalizePubkey = normalizePubkey;
|
|
929
|
+
exports.resetDefaultConnector = resetDefaultConnector;
|