nostr-wot-sdk 0.5.1 → 0.5.3

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/CHANGELOG.md CHANGED
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.5.2] - 2025-02-05
9
+
10
+ ### Added
11
+
12
+ - **`getExtensionStatus()` method** - Detailed extension connection status
13
+ - `'connected'` - Extension enabled and working on this domain
14
+ - `'not-enabled'` - Extension installed but not enabled for this domain
15
+ - `'unavailable'` - Not installed (or local dev build)
16
+ - `'not-browser'` - SSR/Node.js environment
17
+
18
+ - **`extensionId` option** - Chrome Web Store extension ID for install detection
19
+ - Enables detecting "installed but not enabled" state via `chrome-extension://` URL check
20
+ - Pass your extension's ID: `new WoT({ extensionId: 'abcdef...' })`
21
+
22
+ - New `ExtensionConnectionStatus` type export
23
+
8
24
  ## [0.5.1] - 2025-02-05
9
25
 
10
26
  ### Removed
@@ -210,6 +226,7 @@ function Profile({ pubkey }) {
210
226
  - TypeScript support with full type definitions
211
227
  - Error classes: `WoTError`, `NetworkError`, `NotFoundError`, `TimeoutError`, `ValidationError`
212
228
 
229
+ [0.5.2]: https://github.com/nostr-wot/nostr-wot-sdk/compare/v0.5.1...v0.5.2
213
230
  [0.5.1]: https://github.com/nostr-wot/nostr-wot-sdk/compare/v0.5.0...v0.5.1
214
231
  [0.5.0]: https://github.com/nostr-wot/nostr-wot-sdk/compare/v0.4.2...v0.5.0
215
232
  [0.4.2]: https://github.com/nostr-wot/nostr-wot-sdk/compare/v0.4.1...v0.4.2
package/README.md CHANGED
@@ -75,6 +75,7 @@ const wot = new WoT(options);
75
75
  | `maxHops` | number | `3` | Default max search depth |
76
76
  | `timeout` | number | `5000` | Request timeout (ms) |
77
77
  | `fallback` | object | — | Fallback config when extension unavailable |
78
+ | `extensionId` | string | — | Chrome Web Store extension ID (for detecting "installed but not enabled" state) |
78
79
 
79
80
  Trust scores are calculated by the extension and not configurable via the SDK.
80
81
 
@@ -147,6 +148,23 @@ const usingExt = await wot.isUsingExtension();
147
148
  // Returns: boolean
148
149
  ```
149
150
 
151
+ #### `getExtensionStatus()`
152
+
153
+ Get detailed extension connection status. Useful for showing appropriate UI based on why the extension isn't working.
154
+ ```javascript
155
+ const status = await wot.getExtensionStatus();
156
+ // Returns: 'connected' | 'not-enabled' | 'unavailable' | 'not-browser'
157
+ ```
158
+
159
+ | Status | Description |
160
+ |--------|-------------|
161
+ | `'connected'` | Extension is enabled and working on this domain |
162
+ | `'not-enabled'` | Extension is installed but not enabled for this domain |
163
+ | `'unavailable'` | Extension is not installed (or using local dev build) |
164
+ | `'not-browser'` | Running in SSR/Node.js environment |
165
+
166
+ **Note:** Detecting `'not-enabled'` requires providing the `extensionId` option.
167
+
150
168
  #### `getExtensionConfig()`
151
169
 
152
170
  Get extension's configuration.
package/dist/index.cjs CHANGED
@@ -86,26 +86,28 @@ function chunk(array, size) {
86
86
  }
87
87
 
88
88
  // src/wot.ts
89
+ var DEFAULT_EXTENSION_ID = "gfmefgdkmjpjinecjchlangpamhclhdo";
89
90
  var WoT = class {
90
91
  constructor(options = {}) {
91
92
  this.extension = null;
92
93
  this.extensionPubkey = null;
93
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
94
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
94
95
  this.fallbackOptions = (_a = options.fallback) != null ? _a : null;
96
+ this.extensionId = (_b = options.extensionId) != null ? _b : DEFAULT_EXTENSION_ID;
95
97
  if (options.myPubkey && isValidPubkey(options.myPubkey)) {
96
98
  this.fallbackPubkey = normalizePubkey(options.myPubkey);
97
- } else if ((_b = this.fallbackOptions) == null ? void 0 : _b.myPubkey) {
99
+ } else if ((_c = this.fallbackOptions) == null ? void 0 : _c.myPubkey) {
98
100
  this.fallbackPubkey = normalizePubkey(this.fallbackOptions.myPubkey);
99
101
  } else {
100
102
  this.fallbackPubkey = null;
101
103
  }
102
- const oracleUrl = (_e = (_d = options.oracle) != null ? _d : (_c = this.fallbackOptions) == null ? void 0 : _c.oracle) != null ? _e : DEFAULT_ORACLE;
104
+ const oracleUrl = (_f = (_e = options.oracle) != null ? _e : (_d = this.fallbackOptions) == null ? void 0 : _d.oracle) != null ? _f : DEFAULT_ORACLE;
103
105
  if (!isValidOracleUrl(oracleUrl)) {
104
106
  throw new ValidationError("oracle must be a valid HTTPS URL", "oracle");
105
107
  }
106
108
  this.oracle = oracleUrl;
107
- this.maxHops = (_h = (_g = options.maxHops) != null ? _g : (_f = this.fallbackOptions) == null ? void 0 : _f.maxHops) != null ? _h : DEFAULT_MAX_HOPS;
108
- this.timeout = (_k = (_j = options.timeout) != null ? _j : (_i = this.fallbackOptions) == null ? void 0 : _i.timeout) != null ? _k : DEFAULT_TIMEOUT;
109
+ this.maxHops = (_i = (_h = options.maxHops) != null ? _h : (_g = this.fallbackOptions) == null ? void 0 : _g.maxHops) != null ? _i : DEFAULT_MAX_HOPS;
110
+ this.timeout = (_l = (_k = options.timeout) != null ? _k : (_j = this.fallbackOptions) == null ? void 0 : _j.timeout) != null ? _l : DEFAULT_TIMEOUT;
109
111
  }
110
112
  /**
111
113
  * Checks if browser extension is available and returns it
@@ -419,6 +421,33 @@ var WoT = class {
419
421
  const ext = await this.getExtension();
420
422
  return ext !== null;
421
423
  }
424
+ /**
425
+ * Get detailed extension connection status
426
+ * Can detect if extension is installed but not enabled for this domain
427
+ * @returns Connection status: 'connected' | 'not-enabled' | 'unavailable' | 'not-browser'
428
+ */
429
+ async getExtensionStatus() {
430
+ var _a;
431
+ if (typeof window === "undefined") {
432
+ return "not-browser";
433
+ }
434
+ if ((_a = window.nostr) == null ? void 0 : _a.wot) {
435
+ return "connected";
436
+ }
437
+ if (this.extensionId) {
438
+ try {
439
+ const response = await fetch(
440
+ `chrome-extension://${this.extensionId}/detect.json`,
441
+ { method: "HEAD" }
442
+ );
443
+ if (response.ok) {
444
+ return "not-enabled";
445
+ }
446
+ } catch (e) {
447
+ }
448
+ }
449
+ return "unavailable";
450
+ }
422
451
  /**
423
452
  * Get extension configuration (only available when using extension)
424
453
  * @returns Extension config or null if not using extension
package/dist/index.d.cts CHANGED
@@ -1,3 +1,7 @@
1
+ /**
2
+ * Extension connection status
3
+ */
4
+ type ExtensionConnectionStatus = 'connected' | 'not-enabled' | 'unavailable' | 'not-browser';
1
5
  /**
2
6
  * Configuration for trust score calculation (used by extension)
3
7
  */
@@ -73,6 +77,11 @@ interface WoTOptions {
73
77
  * Recommended to provide myPubkey here for oracle fallback.
74
78
  */
75
79
  fallback?: WoTFallbackOptions;
80
+ /**
81
+ * Chrome Web Store extension ID (for install detection)
82
+ * Only needed if you want to detect "installed but not enabled" state
83
+ */
84
+ extensionId?: string;
76
85
  }
77
86
  /**
78
87
  * Options for query methods
@@ -364,6 +373,7 @@ declare class WoT {
364
373
  private readonly maxHops;
365
374
  private readonly timeout;
366
375
  private readonly fallbackOptions;
376
+ private readonly extensionId;
367
377
  private extension;
368
378
  private extensionPubkey;
369
379
  constructor(options?: WoTOptions);
@@ -443,6 +453,12 @@ declare class WoT {
443
453
  * Check if extension is available and being used
444
454
  */
445
455
  isUsingExtension(): Promise<boolean>;
456
+ /**
457
+ * Get detailed extension connection status
458
+ * Can detect if extension is installed but not enabled for this domain
459
+ * @returns Connection status: 'connected' | 'not-enabled' | 'unavailable' | 'not-browser'
460
+ */
461
+ getExtensionStatus(): Promise<ExtensionConnectionStatus>;
446
462
  /**
447
463
  * Get extension configuration (only available when using extension)
448
464
  * @returns Extension config or null if not using extension
@@ -604,4 +620,4 @@ declare const MAX_BATCH_SIZE = 10000;
604
620
  */
605
621
  declare function normalizePubkey(pubkey: string): string;
606
622
 
607
- export { type BatchResult, DEFAULT_MAX_HOPS, DEFAULT_ORACLE, DEFAULT_TIMEOUT, type DistanceBatchOptions, type DistanceResult, type ExtensionConfig, type ExtensionDistanceResult, type ExtensionStatus, type GraphStats, MAX_BATCH_SIZE, NetworkError, type NostrContactEvent, type NostrWindow, type NostrWoTExtension, NotFoundError, type QueryOptions, type ScoringConfig, TimeoutError, ValidationError, WoT, WoTError, type WoTFallbackOptions, type WoTOptions, isValidOracleUrl, isValidPubkey, normalizePubkey };
623
+ export { type BatchResult, DEFAULT_MAX_HOPS, DEFAULT_ORACLE, DEFAULT_TIMEOUT, type DistanceBatchOptions, type DistanceResult, type ExtensionConfig, type ExtensionConnectionStatus, type ExtensionDistanceResult, type ExtensionStatus, type GraphStats, MAX_BATCH_SIZE, NetworkError, type NostrContactEvent, type NostrWindow, type NostrWoTExtension, NotFoundError, type QueryOptions, type ScoringConfig, TimeoutError, ValidationError, WoT, WoTError, type WoTFallbackOptions, type WoTOptions, isValidOracleUrl, isValidPubkey, normalizePubkey };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,7 @@
1
+ /**
2
+ * Extension connection status
3
+ */
4
+ type ExtensionConnectionStatus = 'connected' | 'not-enabled' | 'unavailable' | 'not-browser';
1
5
  /**
2
6
  * Configuration for trust score calculation (used by extension)
3
7
  */
@@ -73,6 +77,11 @@ interface WoTOptions {
73
77
  * Recommended to provide myPubkey here for oracle fallback.
74
78
  */
75
79
  fallback?: WoTFallbackOptions;
80
+ /**
81
+ * Chrome Web Store extension ID (for install detection)
82
+ * Only needed if you want to detect "installed but not enabled" state
83
+ */
84
+ extensionId?: string;
76
85
  }
77
86
  /**
78
87
  * Options for query methods
@@ -364,6 +373,7 @@ declare class WoT {
364
373
  private readonly maxHops;
365
374
  private readonly timeout;
366
375
  private readonly fallbackOptions;
376
+ private readonly extensionId;
367
377
  private extension;
368
378
  private extensionPubkey;
369
379
  constructor(options?: WoTOptions);
@@ -443,6 +453,12 @@ declare class WoT {
443
453
  * Check if extension is available and being used
444
454
  */
445
455
  isUsingExtension(): Promise<boolean>;
456
+ /**
457
+ * Get detailed extension connection status
458
+ * Can detect if extension is installed but not enabled for this domain
459
+ * @returns Connection status: 'connected' | 'not-enabled' | 'unavailable' | 'not-browser'
460
+ */
461
+ getExtensionStatus(): Promise<ExtensionConnectionStatus>;
446
462
  /**
447
463
  * Get extension configuration (only available when using extension)
448
464
  * @returns Extension config or null if not using extension
@@ -604,4 +620,4 @@ declare const MAX_BATCH_SIZE = 10000;
604
620
  */
605
621
  declare function normalizePubkey(pubkey: string): string;
606
622
 
607
- export { type BatchResult, DEFAULT_MAX_HOPS, DEFAULT_ORACLE, DEFAULT_TIMEOUT, type DistanceBatchOptions, type DistanceResult, type ExtensionConfig, type ExtensionDistanceResult, type ExtensionStatus, type GraphStats, MAX_BATCH_SIZE, NetworkError, type NostrContactEvent, type NostrWindow, type NostrWoTExtension, NotFoundError, type QueryOptions, type ScoringConfig, TimeoutError, ValidationError, WoT, WoTError, type WoTFallbackOptions, type WoTOptions, isValidOracleUrl, isValidPubkey, normalizePubkey };
623
+ export { type BatchResult, DEFAULT_MAX_HOPS, DEFAULT_ORACLE, DEFAULT_TIMEOUT, type DistanceBatchOptions, type DistanceResult, type ExtensionConfig, type ExtensionConnectionStatus, type ExtensionDistanceResult, type ExtensionStatus, type GraphStats, MAX_BATCH_SIZE, NetworkError, type NostrContactEvent, type NostrWindow, type NostrWoTExtension, NotFoundError, type QueryOptions, type ScoringConfig, TimeoutError, ValidationError, WoT, WoTError, type WoTFallbackOptions, type WoTOptions, isValidOracleUrl, isValidPubkey, normalizePubkey };
package/dist/index.js CHANGED
@@ -84,26 +84,28 @@ function chunk(array, size) {
84
84
  }
85
85
 
86
86
  // src/wot.ts
87
+ var DEFAULT_EXTENSION_ID = "gfmefgdkmjpjinecjchlangpamhclhdo";
87
88
  var WoT = class {
88
89
  constructor(options = {}) {
89
90
  this.extension = null;
90
91
  this.extensionPubkey = null;
91
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
92
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
92
93
  this.fallbackOptions = (_a = options.fallback) != null ? _a : null;
94
+ this.extensionId = (_b = options.extensionId) != null ? _b : DEFAULT_EXTENSION_ID;
93
95
  if (options.myPubkey && isValidPubkey(options.myPubkey)) {
94
96
  this.fallbackPubkey = normalizePubkey(options.myPubkey);
95
- } else if ((_b = this.fallbackOptions) == null ? void 0 : _b.myPubkey) {
97
+ } else if ((_c = this.fallbackOptions) == null ? void 0 : _c.myPubkey) {
96
98
  this.fallbackPubkey = normalizePubkey(this.fallbackOptions.myPubkey);
97
99
  } else {
98
100
  this.fallbackPubkey = null;
99
101
  }
100
- const oracleUrl = (_e = (_d = options.oracle) != null ? _d : (_c = this.fallbackOptions) == null ? void 0 : _c.oracle) != null ? _e : DEFAULT_ORACLE;
102
+ const oracleUrl = (_f = (_e = options.oracle) != null ? _e : (_d = this.fallbackOptions) == null ? void 0 : _d.oracle) != null ? _f : DEFAULT_ORACLE;
101
103
  if (!isValidOracleUrl(oracleUrl)) {
102
104
  throw new ValidationError("oracle must be a valid HTTPS URL", "oracle");
103
105
  }
104
106
  this.oracle = oracleUrl;
105
- this.maxHops = (_h = (_g = options.maxHops) != null ? _g : (_f = this.fallbackOptions) == null ? void 0 : _f.maxHops) != null ? _h : DEFAULT_MAX_HOPS;
106
- this.timeout = (_k = (_j = options.timeout) != null ? _j : (_i = this.fallbackOptions) == null ? void 0 : _i.timeout) != null ? _k : DEFAULT_TIMEOUT;
107
+ this.maxHops = (_i = (_h = options.maxHops) != null ? _h : (_g = this.fallbackOptions) == null ? void 0 : _g.maxHops) != null ? _i : DEFAULT_MAX_HOPS;
108
+ this.timeout = (_l = (_k = options.timeout) != null ? _k : (_j = this.fallbackOptions) == null ? void 0 : _j.timeout) != null ? _l : DEFAULT_TIMEOUT;
107
109
  }
108
110
  /**
109
111
  * Checks if browser extension is available and returns it
@@ -417,6 +419,33 @@ var WoT = class {
417
419
  const ext = await this.getExtension();
418
420
  return ext !== null;
419
421
  }
422
+ /**
423
+ * Get detailed extension connection status
424
+ * Can detect if extension is installed but not enabled for this domain
425
+ * @returns Connection status: 'connected' | 'not-enabled' | 'unavailable' | 'not-browser'
426
+ */
427
+ async getExtensionStatus() {
428
+ var _a;
429
+ if (typeof window === "undefined") {
430
+ return "not-browser";
431
+ }
432
+ if ((_a = window.nostr) == null ? void 0 : _a.wot) {
433
+ return "connected";
434
+ }
435
+ if (this.extensionId) {
436
+ try {
437
+ const response = await fetch(
438
+ `chrome-extension://${this.extensionId}/detect.json`,
439
+ { method: "HEAD" }
440
+ );
441
+ if (response.ok) {
442
+ return "not-enabled";
443
+ }
444
+ } catch (e) {
445
+ }
446
+ }
447
+ return "unavailable";
448
+ }
420
449
  /**
421
450
  * Get extension configuration (only available when using extension)
422
451
  * @returns Extension config or null if not using extension
@@ -91,26 +91,28 @@ function chunk(array, size) {
91
91
  }
92
92
 
93
93
  // src/wot.ts
94
+ var DEFAULT_EXTENSION_ID = "gfmefgdkmjpjinecjchlangpamhclhdo";
94
95
  var WoT = class {
95
96
  constructor(options = {}) {
96
97
  this.extension = null;
97
98
  this.extensionPubkey = null;
98
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
99
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
99
100
  this.fallbackOptions = (_a = options.fallback) != null ? _a : null;
101
+ this.extensionId = (_b = options.extensionId) != null ? _b : DEFAULT_EXTENSION_ID;
100
102
  if (options.myPubkey && isValidPubkey(options.myPubkey)) {
101
103
  this.fallbackPubkey = normalizePubkey(options.myPubkey);
102
- } else if ((_b = this.fallbackOptions) == null ? void 0 : _b.myPubkey) {
104
+ } else if ((_c = this.fallbackOptions) == null ? void 0 : _c.myPubkey) {
103
105
  this.fallbackPubkey = normalizePubkey(this.fallbackOptions.myPubkey);
104
106
  } else {
105
107
  this.fallbackPubkey = null;
106
108
  }
107
- const oracleUrl = (_e = (_d = options.oracle) != null ? _d : (_c = this.fallbackOptions) == null ? void 0 : _c.oracle) != null ? _e : DEFAULT_ORACLE;
109
+ const oracleUrl = (_f = (_e = options.oracle) != null ? _e : (_d = this.fallbackOptions) == null ? void 0 : _d.oracle) != null ? _f : DEFAULT_ORACLE;
108
110
  if (!isValidOracleUrl(oracleUrl)) {
109
111
  throw new ValidationError("oracle must be a valid HTTPS URL", "oracle");
110
112
  }
111
113
  this.oracle = oracleUrl;
112
- this.maxHops = (_h = (_g = options.maxHops) != null ? _g : (_f = this.fallbackOptions) == null ? void 0 : _f.maxHops) != null ? _h : DEFAULT_MAX_HOPS;
113
- this.timeout = (_k = (_j = options.timeout) != null ? _j : (_i = this.fallbackOptions) == null ? void 0 : _i.timeout) != null ? _k : DEFAULT_TIMEOUT;
114
+ this.maxHops = (_i = (_h = options.maxHops) != null ? _h : (_g = this.fallbackOptions) == null ? void 0 : _g.maxHops) != null ? _i : DEFAULT_MAX_HOPS;
115
+ this.timeout = (_l = (_k = options.timeout) != null ? _k : (_j = this.fallbackOptions) == null ? void 0 : _j.timeout) != null ? _l : DEFAULT_TIMEOUT;
114
116
  }
115
117
  /**
116
118
  * Checks if browser extension is available and returns it
@@ -424,6 +426,33 @@ var WoT = class {
424
426
  const ext = await this.getExtension();
425
427
  return ext !== null;
426
428
  }
429
+ /**
430
+ * Get detailed extension connection status
431
+ * Can detect if extension is installed but not enabled for this domain
432
+ * @returns Connection status: 'connected' | 'not-enabled' | 'unavailable' | 'not-browser'
433
+ */
434
+ async getExtensionStatus() {
435
+ var _a;
436
+ if (typeof window === "undefined") {
437
+ return "not-browser";
438
+ }
439
+ if ((_a = window.nostr) == null ? void 0 : _a.wot) {
440
+ return "connected";
441
+ }
442
+ if (this.extensionId) {
443
+ try {
444
+ const response = await fetch(
445
+ `chrome-extension://${this.extensionId}/detect.json`,
446
+ { method: "HEAD" }
447
+ );
448
+ if (response.ok) {
449
+ return "not-enabled";
450
+ }
451
+ } catch (e) {
452
+ }
453
+ }
454
+ return "unavailable";
455
+ }
427
456
  /**
428
457
  * Get extension configuration (only available when using extension)
429
458
  * @returns Extension config or null if not using extension
@@ -600,8 +629,30 @@ function WoTProvider({
600
629
  setIsReady(true);
601
630
  }, []);
602
631
  react.useEffect(() => {
603
- checkExtension();
604
- }, [checkExtension]);
632
+ if (checkExtensionAvailable()) {
633
+ setExtensionState("connected");
634
+ setIsReady(true);
635
+ return;
636
+ }
637
+ let attempts = 0;
638
+ const maxAttempts = 15;
639
+ const intervalMs = 100;
640
+ const intervalId = setInterval(() => {
641
+ attempts++;
642
+ if (checkExtensionAvailable()) {
643
+ setExtensionState("connected");
644
+ setIsReady(true);
645
+ clearInterval(intervalId);
646
+ return;
647
+ }
648
+ if (attempts >= maxAttempts) {
649
+ setExtensionState("not-available");
650
+ setIsReady(true);
651
+ clearInterval(intervalId);
652
+ }
653
+ }, intervalMs);
654
+ return () => clearInterval(intervalId);
655
+ }, []);
605
656
  const wot = react.useMemo(() => {
606
657
  if (!isReady) {
607
658
  return null;