@unicitylabs/sphere-sdk 0.5.3 → 0.5.5

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.
Files changed (50) hide show
  1. package/README.md +2 -0
  2. package/dist/connect/index.cjs +145 -23
  3. package/dist/connect/index.cjs.map +1 -1
  4. package/dist/connect/index.d.cts +15 -2
  5. package/dist/connect/index.d.ts +15 -2
  6. package/dist/connect/index.js +145 -23
  7. package/dist/connect/index.js.map +1 -1
  8. package/dist/core/index.cjs +670 -473
  9. package/dist/core/index.cjs.map +1 -1
  10. package/dist/core/index.d.cts +123 -2
  11. package/dist/core/index.d.ts +123 -2
  12. package/dist/core/index.js +667 -473
  13. package/dist/core/index.js.map +1 -1
  14. package/dist/impl/browser/connect/index.cjs +119 -1
  15. package/dist/impl/browser/connect/index.cjs.map +1 -1
  16. package/dist/impl/browser/connect/index.d.cts +53 -1
  17. package/dist/impl/browser/connect/index.d.ts +53 -1
  18. package/dist/impl/browser/connect/index.js +119 -1
  19. package/dist/impl/browser/connect/index.js.map +1 -1
  20. package/dist/impl/browser/index.cjs +306 -193
  21. package/dist/impl/browser/index.cjs.map +1 -1
  22. package/dist/impl/browser/index.js +306 -193
  23. package/dist/impl/browser/index.js.map +1 -1
  24. package/dist/impl/browser/ipfs.cjs +134 -19
  25. package/dist/impl/browser/ipfs.cjs.map +1 -1
  26. package/dist/impl/browser/ipfs.js +134 -19
  27. package/dist/impl/browser/ipfs.js.map +1 -1
  28. package/dist/impl/nodejs/connect/index.cjs +101 -6
  29. package/dist/impl/nodejs/connect/index.cjs.map +1 -1
  30. package/dist/impl/nodejs/connect/index.d.cts +2 -0
  31. package/dist/impl/nodejs/connect/index.d.ts +2 -0
  32. package/dist/impl/nodejs/connect/index.js +101 -6
  33. package/dist/impl/nodejs/connect/index.js.map +1 -1
  34. package/dist/impl/nodejs/index.cjs +267 -152
  35. package/dist/impl/nodejs/index.cjs.map +1 -1
  36. package/dist/impl/nodejs/index.d.cts +2 -1
  37. package/dist/impl/nodejs/index.d.ts +2 -1
  38. package/dist/impl/nodejs/index.js +267 -152
  39. package/dist/impl/nodejs/index.js.map +1 -1
  40. package/dist/index.cjs +682 -493
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.cts +124 -8
  43. package/dist/index.d.ts +124 -8
  44. package/dist/index.js +680 -493
  45. package/dist/index.js.map +1 -1
  46. package/dist/l1/index.cjs +139 -32
  47. package/dist/l1/index.cjs.map +1 -1
  48. package/dist/l1/index.js +139 -32
  49. package/dist/l1/index.js.map +1 -1
  50. package/package.json +1 -16
package/README.md CHANGED
@@ -16,6 +16,7 @@ A modular TypeScript SDK for Unicity wallet operations supporting both Layer 1 (
16
16
  - **TXF Serialization** - Token eXchange Format for storage and transfer
17
17
  - **Token Validation** - Aggregator-based token verification
18
18
  - **Core Utilities** - Crypto, currency, bech32, base58 functions
19
+ - **Connect Protocol** - dApp ↔ wallet communication via `ConnectClient` / `ConnectHost` (browser extension + popup)
19
20
 
20
21
  ## Installation
21
22
 
@@ -32,6 +33,7 @@ Choose your platform:
32
33
  | **Browser** | [QUICKSTART-BROWSER.md](docs/QUICKSTART-BROWSER.md) | SDK only | IPFS sync (built-in) |
33
34
  | **Node.js** | [QUICKSTART-NODEJS.md](docs/QUICKSTART-NODEJS.md) | SDK + `ws` | IPFS sync (built-in) |
34
35
  | **CLI** | See below | SDK + `tsx` | - |
36
+ | **dApp integration** | [CONNECT.md](docs/CONNECT.md) | SDK only | Sphere extension |
35
37
 
36
38
  ## CLI (Command Line Interface)
37
39
 
@@ -42,6 +42,110 @@ __export(connect_exports, {
42
42
  });
43
43
  module.exports = __toCommonJS(connect_exports);
44
44
 
45
+ // core/logger.ts
46
+ var LOGGER_KEY = "__sphere_sdk_logger__";
47
+ function getState() {
48
+ const g = globalThis;
49
+ if (!g[LOGGER_KEY]) {
50
+ g[LOGGER_KEY] = { debug: false, tags: {}, handler: null };
51
+ }
52
+ return g[LOGGER_KEY];
53
+ }
54
+ function isEnabled(tag) {
55
+ const state = getState();
56
+ if (tag in state.tags) return state.tags[tag];
57
+ return state.debug;
58
+ }
59
+ var logger = {
60
+ /**
61
+ * Configure the logger. Can be called multiple times (last write wins).
62
+ * Typically called by createBrowserProviders(), createNodeProviders(), or Sphere.init().
63
+ */
64
+ configure(config) {
65
+ const state = getState();
66
+ if (config.debug !== void 0) state.debug = config.debug;
67
+ if (config.handler !== void 0) state.handler = config.handler;
68
+ },
69
+ /**
70
+ * Enable/disable debug logging for a specific tag.
71
+ * Per-tag setting overrides the global debug flag.
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * logger.setTagDebug('Nostr', true); // enable only Nostr logs
76
+ * logger.setTagDebug('Nostr', false); // disable Nostr logs even if global debug=true
77
+ * ```
78
+ */
79
+ setTagDebug(tag, enabled) {
80
+ getState().tags[tag] = enabled;
81
+ },
82
+ /**
83
+ * Clear per-tag override, falling back to global debug flag.
84
+ */
85
+ clearTagDebug(tag) {
86
+ delete getState().tags[tag];
87
+ },
88
+ /** Returns true if debug mode is enabled for the given tag (or globally). */
89
+ isDebugEnabled(tag) {
90
+ if (tag) return isEnabled(tag);
91
+ return getState().debug;
92
+ },
93
+ /**
94
+ * Debug-level log. Only shown when debug is enabled (globally or for this tag).
95
+ * Use for detailed operational information.
96
+ */
97
+ debug(tag, message, ...args) {
98
+ if (!isEnabled(tag)) return;
99
+ const state = getState();
100
+ if (state.handler) {
101
+ state.handler("debug", tag, message, ...args);
102
+ } else {
103
+ console.log(`[${tag}]`, message, ...args);
104
+ }
105
+ },
106
+ /**
107
+ * Warning-level log. ALWAYS shown regardless of debug flag.
108
+ * Use for important but non-critical issues (timeouts, retries, degraded state).
109
+ */
110
+ warn(tag, message, ...args) {
111
+ const state = getState();
112
+ if (state.handler) {
113
+ state.handler("warn", tag, message, ...args);
114
+ } else {
115
+ console.warn(`[${tag}]`, message, ...args);
116
+ }
117
+ },
118
+ /**
119
+ * Error-level log. ALWAYS shown regardless of debug flag.
120
+ * Use for critical failures that should never be silenced.
121
+ */
122
+ error(tag, message, ...args) {
123
+ const state = getState();
124
+ if (state.handler) {
125
+ state.handler("error", tag, message, ...args);
126
+ } else {
127
+ console.error(`[${tag}]`, message, ...args);
128
+ }
129
+ },
130
+ /** Reset all logger state (debug flag, tags, handler). Primarily for tests. */
131
+ reset() {
132
+ const g = globalThis;
133
+ delete g[LOGGER_KEY];
134
+ }
135
+ };
136
+
137
+ // core/errors.ts
138
+ var SphereError = class extends Error {
139
+ code;
140
+ cause;
141
+ constructor(message, code, cause) {
142
+ super(message);
143
+ this.name = "SphereError";
144
+ this.code = code;
145
+ this.cause = cause;
146
+ }
147
+ };
148
+
45
149
  // constants.ts
46
150
  var STORAGE_KEYS_GLOBAL = {
47
151
  /** Encrypted BIP39 mnemonic */
@@ -306,7 +410,7 @@ var ConnectHost = class {
306
410
  return;
307
411
  }
308
412
  } catch (error) {
309
- console.warn("[ConnectHost] Error handling message:", error);
413
+ logger.warn("ConnectHost", "Error handling message:", error);
310
414
  }
311
415
  }
312
416
  // ===========================================================================
@@ -318,10 +422,16 @@ var ConnectHost = class {
318
422
  this.sendHandshakeResponse([], void 0, void 0);
319
423
  return;
320
424
  }
425
+ if (msg.sessionId && this.session?.active && this.session.id === msg.sessionId) {
426
+ const identity2 = this.getPublicIdentity();
427
+ this.sendHandshakeResponse([...this.grantedPermissions], this.session.id, identity2);
428
+ return;
429
+ }
321
430
  const requestedPermissions = msg.permissions;
322
431
  const { approved, grantedPermissions } = await this.config.onConnectionRequest(
323
432
  dapp,
324
- requestedPermissions
433
+ requestedPermissions,
434
+ msg.silent
325
435
  );
326
436
  if (!approved) {
327
437
  this.sendHandshakeResponse([], void 0, void 0);
@@ -371,8 +481,12 @@ var ConnectHost = class {
371
481
  return;
372
482
  }
373
483
  if (msg.method === RPC_METHODS.DISCONNECT) {
484
+ const disconnectedSession = this.session;
374
485
  this.revokeSession();
375
486
  this.sendResult(msg.id, { disconnected: true });
487
+ if (disconnectedSession && this.config.onDisconnect) {
488
+ Promise.resolve(this.config.onDisconnect(disconnectedSession)).catch((err) => logger.warn("Connect", "onDisconnect handler error", err));
489
+ }
376
490
  return;
377
491
  }
378
492
  if (!hasMethodPermission(this.grantedPermissions, msg.method)) {
@@ -443,17 +557,17 @@ var ConnectHost = class {
443
557
  return this.sphere.payments.getHistory();
444
558
  case RPC_METHODS.L1_GET_BALANCE:
445
559
  if (!this.sphere.payments.l1) {
446
- throw new Error("L1 module not available");
560
+ throw new SphereError("L1 module not available", "MODULE_NOT_AVAILABLE");
447
561
  }
448
562
  return this.sphere.payments.l1.getBalance();
449
563
  case RPC_METHODS.L1_GET_HISTORY:
450
564
  if (!this.sphere.payments.l1) {
451
- throw new Error("L1 module not available");
565
+ throw new SphereError("L1 module not available", "MODULE_NOT_AVAILABLE");
452
566
  }
453
567
  return this.sphere.payments.l1.getHistory(params.limit);
454
568
  case RPC_METHODS.RESOLVE:
455
569
  if (!params.identifier) {
456
- throw new Error("Missing required parameter: identifier");
570
+ throw new SphereError("Missing required parameter: identifier", "VALIDATION_ERROR");
457
571
  }
458
572
  return this.sphere.resolve(params.identifier);
459
573
  case RPC_METHODS.SUBSCRIBE:
@@ -461,7 +575,7 @@ var ConnectHost = class {
461
575
  case RPC_METHODS.UNSUBSCRIBE:
462
576
  return this.handleUnsubscribe(params.event);
463
577
  case RPC_METHODS.GET_CONVERSATIONS: {
464
- if (!this.sphere.communications) throw new Error("Communications module not available");
578
+ if (!this.sphere.communications) throw new SphereError("Communications module not available", "MODULE_NOT_AVAILABLE");
465
579
  const convos = this.sphere.communications.getConversations();
466
580
  const result = [];
467
581
  const needsResolve = [];
@@ -484,7 +598,10 @@ var ConnectHost = class {
484
598
  if (needsResolve.length > 0) {
485
599
  const resolved = await Promise.all(
486
600
  needsResolve.map(
487
- ({ peerPubkey }) => this.sphere.communications.resolvePeerNametag(peerPubkey).catch(() => void 0)
601
+ ({ peerPubkey }) => this.sphere.communications.resolvePeerNametag(peerPubkey).catch((err) => {
602
+ logger.debug("Connect", "Peer nametag resolution failed", err);
603
+ return void 0;
604
+ })
488
605
  )
489
606
  );
490
607
  for (let i = 0; i < needsResolve.length; i++) {
@@ -497,8 +614,8 @@ var ConnectHost = class {
497
614
  return result;
498
615
  }
499
616
  case RPC_METHODS.GET_MESSAGES: {
500
- if (!this.sphere.communications) throw new Error("Communications module not available");
501
- if (!params.peerPubkey) throw new Error("Missing required parameter: peerPubkey");
617
+ if (!this.sphere.communications) throw new SphereError("Communications module not available", "MODULE_NOT_AVAILABLE");
618
+ if (!params.peerPubkey) throw new SphereError("Missing required parameter: peerPubkey", "VALIDATION_ERROR");
502
619
  return this.sphere.communications.getConversationPage(
503
620
  params.peerPubkey,
504
621
  {
@@ -508,7 +625,7 @@ var ConnectHost = class {
508
625
  );
509
626
  }
510
627
  case RPC_METHODS.GET_DM_UNREAD_COUNT: {
511
- if (!this.sphere.communications) throw new Error("Communications module not available");
628
+ if (!this.sphere.communications) throw new SphereError("Communications module not available", "MODULE_NOT_AVAILABLE");
512
629
  return {
513
630
  unreadCount: this.sphere.communications.getUnreadCount(
514
631
  params.peerPubkey
@@ -516,22 +633,22 @@ var ConnectHost = class {
516
633
  };
517
634
  }
518
635
  case RPC_METHODS.MARK_AS_READ: {
519
- if (!this.sphere.communications) throw new Error("Communications module not available");
636
+ if (!this.sphere.communications) throw new SphereError("Communications module not available", "MODULE_NOT_AVAILABLE");
520
637
  if (!params.messageIds || !Array.isArray(params.messageIds)) {
521
- throw new Error("Missing required parameter: messageIds (string[])");
638
+ throw new SphereError("Missing required parameter: messageIds (string[])", "VALIDATION_ERROR");
522
639
  }
523
640
  await this.sphere.communications.markAsRead(params.messageIds);
524
641
  return { marked: true, count: params.messageIds.length };
525
642
  }
526
643
  default:
527
- throw new Error(`Unknown method: ${method}`);
644
+ throw new SphereError(`Unknown method: ${method}`, "VALIDATION_ERROR");
528
645
  }
529
646
  }
530
647
  // ===========================================================================
531
648
  // Event Subscriptions
532
649
  // ===========================================================================
533
650
  handleSubscribe(eventName) {
534
- if (!eventName) throw new Error("Missing required parameter: event");
651
+ if (!eventName) throw new SphereError("Missing required parameter: event", "VALIDATION_ERROR");
535
652
  if (this.eventSubscriptions.has(eventName)) {
536
653
  return { subscribed: true, event: eventName };
537
654
  }
@@ -548,7 +665,7 @@ var ConnectHost = class {
548
665
  return { subscribed: true, event: eventName };
549
666
  }
550
667
  handleUnsubscribe(eventName) {
551
- if (!eventName) throw new Error("Missing required parameter: event");
668
+ if (!eventName) throw new SphereError("Missing required parameter: event", "VALIDATION_ERROR");
552
669
  const unsub = this.eventSubscriptions.get(eventName);
553
670
  if (unsub) {
554
671
  unsub();
@@ -639,6 +756,8 @@ var ConnectClient = class {
639
756
  requestedPermissions;
640
757
  timeout;
641
758
  intentTimeout;
759
+ resumeSessionId;
760
+ silent;
642
761
  sessionId = null;
643
762
  grantedPermissions = [];
644
763
  identity = null;
@@ -654,6 +773,8 @@ var ConnectClient = class {
654
773
  this.requestedPermissions = config.permissions ?? [...ALL_PERMISSIONS];
655
774
  this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
656
775
  this.intentTimeout = config.intentTimeout ?? DEFAULT_INTENT_TIMEOUT;
776
+ this.resumeSessionId = config.resumeSessionId ?? null;
777
+ this.silent = config.silent ?? false;
657
778
  }
658
779
  // ===========================================================================
659
780
  // Connection
@@ -673,7 +794,9 @@ var ConnectClient = class {
673
794
  type: "handshake",
674
795
  direction: "request",
675
796
  permissions: this.requestedPermissions,
676
- dapp: this.dapp
797
+ dapp: this.dapp,
798
+ ...this.resumeSessionId ? { sessionId: this.resumeSessionId } : {},
799
+ ...this.silent ? { silent: true } : {}
677
800
  });
678
801
  });
679
802
  }
@@ -708,7 +831,7 @@ var ConnectClient = class {
708
831
  // ===========================================================================
709
832
  /** Send a query request and return the result */
710
833
  async query(method, params) {
711
- if (!this.connected) throw new Error("Not connected");
834
+ if (!this.connected) throw new SphereError("Not connected", "NOT_INITIALIZED");
712
835
  const id = createRequestId();
713
836
  return new Promise((resolve, reject) => {
714
837
  const timer = setTimeout(() => {
@@ -735,7 +858,7 @@ var ConnectClient = class {
735
858
  // ===========================================================================
736
859
  /** Send an intent request. The wallet will open its UI for user confirmation. */
737
860
  async intent(action, params) {
738
- if (!this.connected) throw new Error("Not connected");
861
+ if (!this.connected) throw new SphereError("Not connected", "NOT_INITIALIZED");
739
862
  const id = createRequestId();
740
863
  return new Promise((resolve, reject) => {
741
864
  const timer = setTimeout(() => {
@@ -765,8 +888,7 @@ var ConnectClient = class {
765
888
  if (!this.eventHandlers.has(event)) {
766
889
  this.eventHandlers.set(event, /* @__PURE__ */ new Set());
767
890
  if (this.connected) {
768
- this.query(RPC_METHODS.SUBSCRIBE, { event }).catch(() => {
769
- });
891
+ this.query(RPC_METHODS.SUBSCRIBE, { event }).catch((err) => logger.debug("Connect", "Event subscription failed", err));
770
892
  }
771
893
  }
772
894
  this.eventHandlers.get(event).add(handler);
@@ -777,8 +899,7 @@ var ConnectClient = class {
777
899
  if (handlers.size === 0) {
778
900
  this.eventHandlers.delete(event);
779
901
  if (this.connected) {
780
- this.query(RPC_METHODS.UNSUBSCRIBE, { event }).catch(() => {
781
- });
902
+ this.query(RPC_METHODS.UNSUBSCRIBE, { event }).catch((err) => logger.debug("Connect", "Event unsubscription failed", err));
782
903
  }
783
904
  }
784
905
  }
@@ -806,7 +927,8 @@ var ConnectClient = class {
806
927
  for (const handler of handlers) {
807
928
  try {
808
929
  handler(msg.data);
809
- } catch {
930
+ } catch (err) {
931
+ logger.debug("Connect", "Event handler error", err);
810
932
  }
811
933
  }
812
934
  }