@asseteragmbh/wallet-connector 0.1.2 → 0.2.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/dist/index.cjs CHANGED
@@ -4,6 +4,11 @@
4
4
  var PROTOCOL_VERSION = 1;
5
5
  var CONNECT_PATH = "/connect";
6
6
  var ONBOARD_PATH = "/onboard";
7
+ var MANAGE_PATH = "/manage";
8
+ var SIGN_IN_HINTS = ["assetera-marketplace"];
9
+ function isSignInHint(value) {
10
+ return SIGN_IN_HINTS.includes(value);
11
+ }
7
12
  var MESSAGE_PREFIX = "assetera-wallet:";
8
13
  var SUPPORTED_METHODS = [
9
14
  "eth_requestAccounts",
@@ -48,6 +53,21 @@ function isResponseMessage(value) {
48
53
  function isEventMessage(value) {
49
54
  return hasType(value, "event") && (value.event === "accountsChanged" || value.event === "chainChanged" || value.event === "disconnect");
50
55
  }
56
+ var WALLET_PROTECTIONS = [
57
+ "unknown",
58
+ "add-factor",
59
+ "save-codes",
60
+ "create-codes",
61
+ "protected"
62
+ ];
63
+ function isCount(value) {
64
+ return typeof value === "number" && Number.isInteger(value) && value >= 0;
65
+ }
66
+ function isSecurityMessage(value) {
67
+ if (!hasType(value, "security")) return false;
68
+ const data = value.data;
69
+ return isRecord(data) && typeof data.protection === "string" && WALLET_PROTECTIONS.includes(data.protection) && isCount(data.passkeys) && isCount(data.authenticatorApps);
70
+ }
51
71
  function isRequestMessage(value) {
52
72
  return hasType(value, "request") && typeof value.id === "string" && typeof value.method === "string";
53
73
  }
@@ -56,7 +76,8 @@ function isDisconnectMessage(value) {
56
76
  }
57
77
  var EVM_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
58
78
  function isOnboardedMessage(value) {
59
- return hasType(value, "onboarded") && typeof value.address === "string" && EVM_ADDRESS_RE.test(value.address) && typeof value.chain === "string" && (value.status === "approved" || value.status === "pending");
79
+ return hasType(value, "onboarded") && typeof value.address === "string" && EVM_ADDRESS_RE.test(value.address) && typeof value.chain === "string" && // `created` is new; an older wallet sent `status` instead and always created or reused a wallet.
80
+ (typeof value.created === "boolean" || value.status === "approved" || value.status === "pending");
60
81
  }
61
82
  function isOnboardCancelledMessage(value) {
62
83
  return hasType(value, "onboard-cancelled") && (value.reason === "user" || value.reason === "refused" || value.reason === "error");
@@ -64,10 +85,11 @@ function isOnboardCancelledMessage(value) {
64
85
  function isCaip2EvmChain(value) {
65
86
  return /^eip155:\d{1,32}$/.test(value);
66
87
  }
67
- function connectUrl(walletUrl, partnerOrigin) {
88
+ function connectUrl(walletUrl, partnerOrigin, signIn) {
68
89
  const url = new URL(CONNECT_PATH, walletUrl);
69
90
  url.searchParams.set("origin", partnerOrigin);
70
91
  url.searchParams.set("v", String(PROTOCOL_VERSION));
92
+ if (signIn !== void 0) url.searchParams.set("signIn", signIn);
71
93
  return url.toString();
72
94
  }
73
95
  function onboardUrl(walletUrl, partnerOrigin, options) {
@@ -76,6 +98,16 @@ function onboardUrl(walletUrl, partnerOrigin, options) {
76
98
  url.searchParams.set("v", String(PROTOCOL_VERSION));
77
99
  url.searchParams.set("chain", options.chain);
78
100
  if (options.locale !== void 0) url.searchParams.set("lang", options.locale);
101
+ if (options.add) url.searchParams.set("add", "1");
102
+ if (options.signIn !== void 0) url.searchParams.set("signIn", options.signIn);
103
+ return url.toString();
104
+ }
105
+ function manageUrl(walletUrl, partnerOrigin, options = {}) {
106
+ const url = new URL(MANAGE_PATH, walletUrl);
107
+ url.searchParams.set("origin", partnerOrigin);
108
+ url.searchParams.set("v", String(PROTOCOL_VERSION));
109
+ if (options.locale !== void 0) url.searchParams.set("lang", options.locale);
110
+ if (options.signIn !== void 0) url.searchParams.set("signIn", options.signIn);
79
111
  return url.toString();
80
112
  }
81
113
 
@@ -121,6 +153,7 @@ function createAsseteraWalletProvider(options) {
121
153
  let accounts = [];
122
154
  let chainIdHex = null;
123
155
  let walletDisconnected = false;
156
+ let lastSecurity;
124
157
  const pending = /* @__PURE__ */ new Map();
125
158
  const listeners = /* @__PURE__ */ new Map();
126
159
  let closeTimer = null;
@@ -195,7 +228,7 @@ function createAsseteraWalletProvider(options) {
195
228
  }
196
229
  if (typeof window === "undefined") return null;
197
230
  const opened = window.open(
198
- connectUrl(options.walletUrl, window.location.origin),
231
+ connectUrl(options.walletUrl, window.location.origin, options.signIn),
199
232
  POPUP_NAME,
200
233
  popupFeatures
201
234
  );
@@ -246,6 +279,14 @@ function createAsseteraWalletProvider(options) {
246
279
  walletDisconnected = true;
247
280
  emit("disconnect", new ProviderRpcError(message.data.code, message.data.message, message.data.data));
248
281
  }
282
+ function handleSecurityMessage(message) {
283
+ lastSecurity = {
284
+ protection: message.data.protection,
285
+ passkeys: message.data.passkeys,
286
+ authenticatorApps: message.data.authenticatorApps
287
+ };
288
+ emit("assetera_security", lastSecurity);
289
+ }
249
290
  function onWindowMessage(event) {
250
291
  if (event.origin !== walletOrigin) return;
251
292
  if (popup === null || event.source !== popup) return;
@@ -258,6 +299,10 @@ function createAsseteraWalletProvider(options) {
258
299
  handleResponse(data);
259
300
  return;
260
301
  }
302
+ if (isSecurityMessage(data)) {
303
+ handleSecurityMessage(data);
304
+ return;
305
+ }
261
306
  if (isEventMessage(data)) handleEventMessage(data);
262
307
  }
263
308
  if (typeof window !== "undefined") window.addEventListener("message", onWindowMessage);
@@ -330,7 +375,23 @@ function createAsseteraWalletProvider(options) {
330
375
  function removeListener(event, listener) {
331
376
  removeEventListener(event, listener);
332
377
  }
333
- return { request, on, removeListener, disconnect };
378
+ function lastSecurityStatus() {
379
+ return lastSecurity;
380
+ }
381
+ return { request, on, removeListener, disconnect, lastSecurityStatus };
382
+ }
383
+
384
+ // src/popup.ts
385
+ var POPUP_WIDTH = 440;
386
+ var POPUP_HEIGHT = 760;
387
+ function defaultPopupFeatures(win) {
388
+ const screenX = Number.isFinite(win.screenX) ? win.screenX : 0;
389
+ const screenY = Number.isFinite(win.screenY) ? win.screenY : 0;
390
+ const outerWidth = Number.isFinite(win.outerWidth) ? win.outerWidth : POPUP_WIDTH;
391
+ const outerHeight = Number.isFinite(win.outerHeight) ? win.outerHeight : POPUP_HEIGHT;
392
+ const left = Math.max(0, Math.round(screenX + (outerWidth - POPUP_WIDTH) / 2));
393
+ const top = Math.max(0, Math.round(screenY + (outerHeight - POPUP_HEIGHT) / 2));
394
+ return `popup,width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top}`;
334
395
  }
335
396
 
336
397
  // src/onboarding.ts
@@ -343,8 +404,6 @@ var OnboardingError = class extends Error {
343
404
  }
344
405
  };
345
406
  var POPUP_NAME2 = "assetera-wallet-onboarding";
346
- var POPUP_WIDTH = 440;
347
- var POPUP_HEIGHT = 760;
348
407
  var CLOSE_POLL_MS2 = 500;
349
408
  var OVERLAY_Z_INDEX = 2147483e3;
350
409
  var DEFAULT_LABELS = {
@@ -354,15 +413,6 @@ var DEFAULT_LABELS = {
354
413
  cancel: "Cancel",
355
414
  blocked: "Your browser blocked the Assetera Wallet window. Allow pop-ups for this site and try again."
356
415
  };
357
- function defaultPopupFeatures(win) {
358
- const screenX = Number.isFinite(win.screenX) ? win.screenX : 0;
359
- const screenY = Number.isFinite(win.screenY) ? win.screenY : 0;
360
- const outerWidth = Number.isFinite(win.outerWidth) ? win.outerWidth : POPUP_WIDTH;
361
- const outerHeight = Number.isFinite(win.outerHeight) ? win.outerHeight : POPUP_HEIGHT;
362
- const left = Math.max(0, Math.round(screenX + (outerWidth - POPUP_WIDTH) / 2));
363
- const top = Math.max(0, Math.round(screenY + (outerHeight - POPUP_HEIGHT) / 2));
364
- return `popup,width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top}`;
365
- }
366
416
  function resolveLabels(options) {
367
417
  const overrides = typeof options.overlay === "object" && options.overlay !== null ? options.overlay : {};
368
418
  const partnerName = options.partnerName;
@@ -484,7 +534,9 @@ function openAsseteraOnboarding(options) {
484
534
  const popup = targetWindow.open(
485
535
  onboardUrl(options.walletUrl, targetWindow.location.origin, {
486
536
  chain: options.chain,
487
- ...options.locale === void 0 ? {} : { locale: options.locale }
537
+ ...options.locale === void 0 ? {} : { locale: options.locale },
538
+ ...options.addWallet ? { add: true } : {},
539
+ ...options.signIn === void 0 ? {} : { signIn: options.signIn }
488
540
  }),
489
541
  POPUP_NAME2,
490
542
  options.popupFeatures ?? defaultPopupFeatures(targetWindow)
@@ -494,10 +546,13 @@ function openAsseteraOnboarding(options) {
494
546
  }
495
547
  const activePopup = popup;
496
548
  return new Promise((resolve, reject) => {
497
- let settled = false;
549
+ let onboarded = false;
550
+ let finished = false;
498
551
  let closeTimer = null;
499
552
  let overlay = null;
553
+ const pending = /* @__PURE__ */ new Map();
500
554
  function cleanup() {
555
+ finished = true;
501
556
  targetWindow.removeEventListener("message", onMessage);
502
557
  if (closeTimer !== null) {
503
558
  clearInterval(closeTimer);
@@ -506,44 +561,98 @@ function openAsseteraOnboarding(options) {
506
561
  overlay?.remove();
507
562
  overlay = null;
508
563
  }
509
- function settle(action) {
510
- if (settled) return;
511
- settled = true;
564
+ function failPending(error) {
565
+ for (const entry of pending.values()) entry.reject(error);
566
+ pending.clear();
567
+ }
568
+ function close() {
569
+ if (finished) return;
570
+ failPending(new OnboardingError("closed", "The Assetera Wallet window was closed."));
512
571
  cleanup();
513
- action();
572
+ if (!activePopup.closed) activePopup.close();
573
+ }
574
+ function fail(error) {
575
+ if (onboarded || finished) return;
576
+ cleanup();
577
+ reject(error);
514
578
  }
515
579
  function cancel() {
580
+ if (onboarded) {
581
+ close();
582
+ return;
583
+ }
516
584
  activePopup.close();
517
- settle(
518
- () => reject(new OnboardingError("cancelled", "The user cancelled the Assetera Wallet onboarding."))
519
- );
585
+ fail(new OnboardingError("cancelled", "The user cancelled the Assetera Wallet onboarding."));
586
+ }
587
+ function signMessage(message) {
588
+ if (finished || activePopup.closed) {
589
+ return Promise.reject(new OnboardingError("closed", "The Assetera Wallet window was closed."));
590
+ }
591
+ const id = globalThis.crypto.randomUUID();
592
+ const request = {
593
+ type: `${MESSAGE_PREFIX}request`,
594
+ v: PROTOCOL_VERSION,
595
+ id,
596
+ method: "personal_sign",
597
+ params: [message, address]
598
+ };
599
+ return new Promise((resolveSign, rejectSign) => {
600
+ pending.set(id, { resolve: resolveSign, reject: rejectSign });
601
+ activePopup.focus();
602
+ activePopup.postMessage(request, walletOrigin);
603
+ });
520
604
  }
605
+ let address;
521
606
  function onMessage(event) {
522
607
  if (event.origin !== walletOrigin) return;
523
608
  if (event.source !== activePopup) return;
524
609
  const data = event.data;
525
- if (isOnboardedMessage(data)) {
526
- settle(() => resolve({ address: data.address, chain: data.chain, status: data.status }));
610
+ if (!onboarded) {
611
+ if (isOnboardedMessage(data)) {
612
+ onboarded = true;
613
+ address = data.address;
614
+ resolve({
615
+ address: data.address,
616
+ chain: data.chain,
617
+ created: data.created ?? true,
618
+ signMessage,
619
+ close
620
+ });
621
+ return;
622
+ }
623
+ if (isOnboardCancelledMessage(data)) {
624
+ fail(new OnboardingError(cancelCode(data.reason), data.message ?? cancelMessage(data.reason)));
625
+ }
527
626
  return;
528
627
  }
529
- if (isOnboardCancelledMessage(data)) {
530
- settle(
531
- () => reject(new OnboardingError(cancelCode(data.reason), data.message ?? cancelMessage(data.reason)))
532
- );
628
+ if (isResponseMessage(data)) {
629
+ const entry = pending.get(data.id);
630
+ if (!entry) return;
631
+ pending.delete(data.id);
632
+ if ("error" in data) {
633
+ entry.reject(
634
+ new OnboardingError(
635
+ data.error.code === RpcErrorCode.UserRejected ? "cancelled" : "failed",
636
+ data.error.message
637
+ )
638
+ );
639
+ } else if (typeof data.result === "string" && /^0x[0-9a-fA-F]+$/.test(data.result)) {
640
+ entry.resolve(data.result);
641
+ } else {
642
+ entry.reject(new OnboardingError("failed", "The Assetera Wallet returned no signature."));
643
+ }
533
644
  }
534
645
  }
535
646
  targetWindow.addEventListener("message", onMessage);
536
647
  closeTimer = setInterval(() => {
537
- if (activePopup.closed) {
538
- settle(
539
- () => reject(
540
- new OnboardingError(
541
- "closed",
542
- "The Assetera Wallet window was closed before onboarding finished."
543
- )
544
- )
545
- );
648
+ if (!activePopup.closed) return;
649
+ if (onboarded) {
650
+ close();
651
+ return;
546
652
  }
653
+ fail(
654
+ new OnboardingError("closed", "The Assetera Wallet window was closed before onboarding finished.")
655
+ );
547
656
  }, CLOSE_POLL_MS2);
548
657
  if (overlayEnabled) {
549
658
  overlay = showOverlay(targetWindow, labels, {
@@ -554,13 +663,99 @@ function openAsseteraOnboarding(options) {
554
663
  });
555
664
  }
556
665
 
666
+ // src/manage.ts
667
+ var ManagerError = class extends Error {
668
+ code;
669
+ constructor(code, message) {
670
+ super(message);
671
+ this.name = "ManagerError";
672
+ this.code = code;
673
+ }
674
+ };
675
+ var POPUP_NAME3 = "assetera-wallet-manage";
676
+ var CLOSE_POLL_MS3 = 500;
677
+ function openAsseteraWalletManager(options) {
678
+ const requestedWindow = options.window ?? (typeof window === "undefined" ? void 0 : window);
679
+ if (!requestedWindow) {
680
+ throw new ManagerError("invalid_options", "The Assetera Wallet manager can only run in a browser.");
681
+ }
682
+ const targetWindow = requestedWindow;
683
+ let walletOrigin;
684
+ try {
685
+ walletOrigin = new URL(options.walletUrl).origin;
686
+ } catch {
687
+ throw new ManagerError("invalid_options", `"${options.walletUrl}" is not a valid wallet URL.`);
688
+ }
689
+ const popup = targetWindow.open(
690
+ manageUrl(options.walletUrl, targetWindow.location.origin, {
691
+ ...options.locale === void 0 ? {} : { locale: options.locale },
692
+ ...options.signIn === void 0 ? {} : { signIn: options.signIn }
693
+ }),
694
+ POPUP_NAME3,
695
+ options.popupFeatures ?? defaultPopupFeatures(targetWindow)
696
+ );
697
+ if (!popup) {
698
+ throw new ManagerError(
699
+ "popup_blocked",
700
+ "Your browser blocked the Assetera Wallet window. Allow pop-ups for this site and try again."
701
+ );
702
+ }
703
+ const activePopup = popup;
704
+ let finished = false;
705
+ let lastSecurity;
706
+ let closeTimer = null;
707
+ let resolveClosed;
708
+ const closed = new Promise((resolve) => {
709
+ resolveClosed = resolve;
710
+ });
711
+ function cleanup() {
712
+ if (finished) return;
713
+ finished = true;
714
+ targetWindow.removeEventListener("message", onMessage);
715
+ if (closeTimer !== null) {
716
+ clearInterval(closeTimer);
717
+ closeTimer = null;
718
+ }
719
+ resolveClosed();
720
+ }
721
+ function close() {
722
+ cleanup();
723
+ if (!activePopup.closed) activePopup.close();
724
+ }
725
+ function onMessage(event) {
726
+ if (event.origin !== walletOrigin) return;
727
+ if (event.source !== activePopup) return;
728
+ const data = event.data;
729
+ if (!isSecurityMessage(data)) return;
730
+ const status = {
731
+ protection: data.data.protection,
732
+ passkeys: data.data.passkeys,
733
+ authenticatorApps: data.data.authenticatorApps
734
+ };
735
+ lastSecurity = status;
736
+ options.onSecurity?.(status);
737
+ }
738
+ targetWindow.addEventListener("message", onMessage);
739
+ closeTimer = setInterval(() => {
740
+ if (activePopup.closed) cleanup();
741
+ }, CLOSE_POLL_MS3);
742
+ return {
743
+ close,
744
+ closed,
745
+ lastSecurity: () => lastSecurity
746
+ };
747
+ }
748
+
557
749
  exports.CONNECT_PATH = CONNECT_PATH;
750
+ exports.MANAGE_PATH = MANAGE_PATH;
558
751
  exports.MESSAGE_PREFIX = MESSAGE_PREFIX;
752
+ exports.ManagerError = ManagerError;
559
753
  exports.ONBOARD_PATH = ONBOARD_PATH;
560
754
  exports.OnboardingError = OnboardingError;
561
755
  exports.PROTOCOL_VERSION = PROTOCOL_VERSION;
562
756
  exports.ProviderRpcError = ProviderRpcError;
563
757
  exports.RpcErrorCode = RpcErrorCode;
758
+ exports.SIGN_IN_HINTS = SIGN_IN_HINTS;
564
759
  exports.SUPPORTED_METHODS = SUPPORTED_METHODS;
565
760
  exports.connectUrl = connectUrl;
566
761
  exports.createAsseteraWalletProvider = createAsseteraWalletProvider;
@@ -572,8 +767,12 @@ exports.isOnboardedMessage = isOnboardedMessage;
572
767
  exports.isReadyMessage = isReadyMessage;
573
768
  exports.isRequestMessage = isRequestMessage;
574
769
  exports.isResponseMessage = isResponseMessage;
770
+ exports.isSecurityMessage = isSecurityMessage;
771
+ exports.isSignInHint = isSignInHint;
575
772
  exports.isSupportedMethod = isSupportedMethod;
773
+ exports.manageUrl = manageUrl;
576
774
  exports.onboardUrl = onboardUrl;
577
775
  exports.openAsseteraOnboarding = openAsseteraOnboarding;
776
+ exports.openAsseteraWalletManager = openAsseteraWalletManager;
578
777
  //# sourceMappingURL=index.cjs.map
579
778
  //# sourceMappingURL=index.cjs.map