@asseteragmbh/wallet-connector 0.2.3 → 0.2.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.
package/dist/index.cjs CHANGED
@@ -10,6 +10,7 @@ function isSignInHint(value) {
10
10
  return SIGN_IN_HINTS.includes(value);
11
11
  }
12
12
  var MESSAGE_PREFIX = "assetera-wallet:";
13
+ var HOLD_MAX_MS = 3e5;
13
14
  var SUPPORTED_METHODS = [
14
15
  "eth_requestAccounts",
15
16
  "eth_accounts",
@@ -95,6 +96,9 @@ function isRequestMessage(value) {
95
96
  function isDisconnectMessage(value) {
96
97
  return hasType(value, "disconnect");
97
98
  }
99
+ function isHoldMessage(value) {
100
+ return hasType(value, "hold") && typeof value.hold === "boolean";
101
+ }
98
102
  var EVM_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
99
103
  function isOnboardedMessage(value) {
100
104
  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.
@@ -180,6 +184,9 @@ function createAsseteraWalletProvider(options) {
180
184
  const listeners = /* @__PURE__ */ new Map();
181
185
  let closeTimer = null;
182
186
  let requestsSent = 0;
187
+ let popupReady = false;
188
+ const holds = /* @__PURE__ */ new Set();
189
+ const preparedHolds = /* @__PURE__ */ new Map();
183
190
  function currentChainId() {
184
191
  return chainIdHex ?? toHexChainId(defaultChainId);
185
192
  }
@@ -214,6 +221,34 @@ function createAsseteraWalletProvider(options) {
214
221
  function removeEventListener(event, listener) {
215
222
  listeners.get(event)?.delete(listener);
216
223
  }
224
+ function postHold(hold) {
225
+ const active = popup;
226
+ if (!active || active.closed || !popupReady) return;
227
+ const message = {
228
+ type: `${MESSAGE_PREFIX}hold`,
229
+ v: PROTOCOL_VERSION,
230
+ hold
231
+ };
232
+ active.postMessage(message, walletOrigin);
233
+ }
234
+ function addHold(token) {
235
+ const wasEmpty = holds.size === 0;
236
+ holds.add(token);
237
+ if (wasEmpty) postHold(true);
238
+ }
239
+ function removeHold(token) {
240
+ if (!holds.delete(token)) return false;
241
+ return holds.size === 0;
242
+ }
243
+ function endPreparedHolds(sequence) {
244
+ let emptied = false;
245
+ for (const [token, registeredAt] of preparedHolds) {
246
+ if (registeredAt >= sequence) continue;
247
+ preparedHolds.delete(token);
248
+ if (removeHold(token)) emptied = true;
249
+ }
250
+ if (emptied) postHold(false);
251
+ }
217
252
  function stopClosePolling() {
218
253
  if (closeTimer !== null) {
219
254
  clearInterval(closeTimer);
@@ -227,7 +262,10 @@ function createAsseteraWalletProvider(options) {
227
262
  }, CLOSE_POLL_MS);
228
263
  }
229
264
  function rejectAllPending(error) {
230
- for (const request2 of pending.values()) request2.reject(error);
265
+ for (const request2 of pending.values()) {
266
+ request2.reject(error);
267
+ endPreparedHolds(request2.sequence);
268
+ }
231
269
  pending.clear();
232
270
  }
233
271
  function handlePopupClosed() {
@@ -235,6 +273,7 @@ function createAsseteraWalletProvider(options) {
235
273
  stopClosePolling();
236
274
  const closedPopup = popup;
237
275
  popup = null;
276
+ popupReady = false;
238
277
  const closedReady = ready;
239
278
  ready = createDeferred();
240
279
  const error = disconnectedError();
@@ -257,12 +296,13 @@ function createAsseteraWalletProvider(options) {
257
296
  );
258
297
  if (!opened) return null;
259
298
  popup = opened;
299
+ popupReady = false;
260
300
  ready = createDeferred();
261
301
  walletDisconnected = false;
262
302
  startClosePolling();
263
303
  return opened;
264
304
  }
265
- function sendRequest(method, params) {
305
+ function sendRequest(method, params, sequence) {
266
306
  const active = popup;
267
307
  if (!active || active.closed) return Promise.reject(disconnectedError());
268
308
  const id = crypto.randomUUID();
@@ -274,7 +314,7 @@ function createAsseteraWalletProvider(options) {
274
314
  ...params === void 0 ? {} : { params }
275
315
  };
276
316
  return new Promise((resolve, reject) => {
277
- pending.set(id, { resolve, reject });
317
+ pending.set(id, { resolve, reject, sequence });
278
318
  active.postMessage(message, walletOrigin);
279
319
  });
280
320
  }
@@ -282,6 +322,7 @@ function createAsseteraWalletProvider(options) {
282
322
  const request2 = pending.get(message.id);
283
323
  if (!request2) return;
284
324
  pending.delete(message.id);
325
+ endPreparedHolds(request2.sequence);
285
326
  if ("error" in message) {
286
327
  request2.reject(new ProviderRpcError(message.error.code, message.error.message, message.error.data));
287
328
  } else {
@@ -322,6 +363,8 @@ function createAsseteraWalletProvider(options) {
322
363
  if (popup === null || event.source !== popup) return;
323
364
  const data = event.data;
324
365
  if (isReadyMessage(data)) {
366
+ popupReady = true;
367
+ if (holds.size > 0) postHold(true);
325
368
  ready.resolve(void 0);
326
369
  return;
327
370
  }
@@ -361,9 +404,10 @@ function createAsseteraWalletProvider(options) {
361
404
  if (method === "eth_chainId") return Promise.resolve(currentChainId());
362
405
  if (method === "eth_requestAccounts") {
363
406
  requestsSent += 1;
407
+ const sequence2 = requestsSent;
364
408
  const active2 = openPopup();
365
409
  if (!active2) return Promise.reject(blockedError());
366
- return ready.promise.then(() => sendRequest("eth_requestAccounts", params)).then((result) => {
410
+ return ready.promise.then(() => sendRequest("eth_requestAccounts", params, sequence2)).then((result) => {
367
411
  accounts = Array.isArray(result) ? result : [];
368
412
  emit("accountsChanged", [...accounts]);
369
413
  emit("connect", { chainId: currentChainId() });
@@ -379,10 +423,11 @@ function createAsseteraWalletProvider(options) {
379
423
  );
380
424
  }
381
425
  requestsSent += 1;
426
+ const sequence = requestsSent;
382
427
  const active = openPopup();
383
428
  if (!active) return Promise.reject(blockedError());
384
429
  if (SIGNING_METHODS.has(method)) active.focus();
385
- return ready.promise.then(() => sendRequest(method, params));
430
+ return ready.promise.then(() => sendRequest(method, params, sequence));
386
431
  }
387
432
  function disconnect() {
388
433
  const active = popup;
@@ -396,6 +441,9 @@ function createAsseteraWalletProvider(options) {
396
441
  }
397
442
  stopClosePolling();
398
443
  popup = null;
444
+ popupReady = false;
445
+ holds.clear();
446
+ preparedHolds.clear();
399
447
  const closedReady = ready;
400
448
  ready = createDeferred();
401
449
  closedReady.reject(disconnectedError("Disconnected from the Assetera Wallet."));
@@ -411,16 +459,49 @@ function createAsseteraWalletProvider(options) {
411
459
  if (!active) return { opened: false, release: () => {
412
460
  } };
413
461
  const sentAtPrepare = requestsSent;
462
+ const token = /* @__PURE__ */ Symbol("assetera-wallet-prepare");
463
+ preparedHolds.set(token, sentAtPrepare);
464
+ addHold(token);
414
465
  let released = false;
415
466
  return {
416
467
  opened: true,
417
468
  release() {
418
469
  if (released) return;
419
470
  released = true;
420
- if (existed || popup !== active || active.closed) return;
421
- if (requestsSent !== sentAtPrepare || pending.size > 0) return;
422
- active.close();
423
- handlePopupClosed();
471
+ preparedHolds.delete(token);
472
+ const emptied = removeHold(token);
473
+ const canClose = emptied && !existed && popup === active && !active.closed && requestsSent === sentAtPrepare && pending.size === 0;
474
+ if (canClose) {
475
+ active.close();
476
+ handlePopupClosed();
477
+ return;
478
+ }
479
+ if (emptied) postHold(false);
480
+ }
481
+ };
482
+ }
483
+ function holdPopup() {
484
+ const existed = popup !== null && !popup.closed;
485
+ const active = typeof window === "undefined" ? null : openPopup();
486
+ if (!active) return { opened: false, release: () => {
487
+ } };
488
+ const token = /* @__PURE__ */ Symbol("assetera-wallet-hold");
489
+ const sentAtHold = requestsSent;
490
+ addHold(token);
491
+ let released = false;
492
+ return {
493
+ opened: true,
494
+ release() {
495
+ if (released) return;
496
+ released = true;
497
+ const emptied = removeHold(token);
498
+ const canClose = emptied && !existed && popup === active && !active.closed && requestsSent === sentAtHold && pending.size === 0;
499
+ if (canClose) {
500
+ active.close();
501
+ handlePopupClosed();
502
+ return;
503
+ }
504
+ if (emptied) postHold(false);
424
505
  }
425
506
  };
426
507
  }
@@ -443,7 +524,8 @@ function createAsseteraWalletProvider(options) {
443
524
  disconnect,
444
525
  lastSecurityStatus,
445
526
  lastAccountNames,
446
- prepareSigning
527
+ prepareSigning,
528
+ holdPopup
447
529
  };
448
530
  }
449
531
 
@@ -479,6 +561,48 @@ var DEFAULT_LABELS = {
479
561
  cancel: "Cancel",
480
562
  blocked: "Your browser blocked the Assetera Wallet window. Allow pop-ups for this site and try again."
481
563
  };
564
+ var LIGHT_THEME = {
565
+ scheme: "light",
566
+ backdrop: "rgba(15,20,30,0.6)",
567
+ surface: "#ffffff",
568
+ text: "#101828",
569
+ mutedText: "#475467",
570
+ primary: "#101828",
571
+ primaryText: "#ffffff",
572
+ border: "none",
573
+ radius: "16px",
574
+ buttonRadius: "10px",
575
+ fontFamily: "system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif"
576
+ };
577
+ var DARK_THEME = {
578
+ scheme: "dark",
579
+ backdrop: "rgba(8,10,14,0.72)",
580
+ surface: "#1b1d24",
581
+ text: "#f7f8fa",
582
+ mutedText: "#9aa2b1",
583
+ primary: "#ffffff",
584
+ primaryText: "#101828",
585
+ border: "1px solid rgba(255,255,255,0.14)",
586
+ radius: "16px",
587
+ buttonRadius: "10px",
588
+ fontFamily: "system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif"
589
+ };
590
+ function resolveTheme(theme) {
591
+ const base = theme?.scheme === "dark" ? DARK_THEME : LIGHT_THEME;
592
+ return {
593
+ scheme: base.scheme,
594
+ backdrop: theme?.backdrop ?? base.backdrop,
595
+ surface: theme?.surface ?? base.surface,
596
+ text: theme?.text ?? base.text,
597
+ mutedText: theme?.mutedText ?? base.mutedText,
598
+ primary: theme?.primary ?? base.primary,
599
+ primaryText: theme?.primaryText ?? base.primaryText,
600
+ border: theme?.border ?? base.border,
601
+ radius: theme?.radius ?? base.radius,
602
+ buttonRadius: theme?.buttonRadius ?? base.buttonRadius,
603
+ fontFamily: theme?.fontFamily ?? base.fontFamily
604
+ };
605
+ }
482
606
  function resolveLabels(options) {
483
607
  const overrides = typeof options.overlay === "object" && options.overlay !== null ? options.overlay : {};
484
608
  const partnerName = options.partnerName;
@@ -491,6 +615,10 @@ function resolveLabels(options) {
491
615
  blocked: overrides.blocked ?? DEFAULT_LABELS.blocked
492
616
  };
493
617
  }
618
+ function resolveThemeOption(options) {
619
+ const overlay = typeof options.overlay === "object" && options.overlay !== null ? options.overlay : {};
620
+ return resolveTheme(overlay.theme);
621
+ }
494
622
  function cancelCode(reason) {
495
623
  if (reason === "user") return "cancelled";
496
624
  if (reason === "refused") return "refused";
@@ -504,7 +632,7 @@ function cancelMessage(reason) {
504
632
  function copyAccountNames(accounts) {
505
633
  return accounts.map((account) => ({ address: account.address, name: account.name }));
506
634
  }
507
- function showOverlay(win, labels, handlers) {
635
+ function showOverlay(win, labels, theme, handlers) {
508
636
  const doc = win.document;
509
637
  const root = doc.createElement("div");
510
638
  root.setAttribute("role", "dialog");
@@ -518,40 +646,43 @@ function showOverlay(win, labels, handlers) {
518
646
  "justify-content: center",
519
647
  "padding: 24px",
520
648
  "box-sizing: border-box",
521
- "background: rgba(15,20,30,0.6)",
522
- `z-index: ${OVERLAY_Z_INDEX}`,
523
- "font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif"
649
+ `z-index: ${OVERLAY_Z_INDEX}`
524
650
  ].join("; ");
651
+ root.style.setProperty("background-color", theme.backdrop);
652
+ root.style.setProperty("font-family", theme.fontFamily);
525
653
  const card = doc.createElement("div");
526
654
  card.style.cssText = [
527
655
  "box-sizing: border-box",
528
656
  "width: 100%",
529
657
  "max-width: 420px",
530
- "border-radius: 16px",
531
658
  "padding: 24px",
532
- "background: #ffffff",
533
- "color: #101828",
534
659
  "box-shadow: 0 20px 50px rgba(0, 0, 0, 0.35)"
535
660
  ].join("; ");
661
+ card.style.setProperty("background-color", theme.surface);
662
+ card.style.setProperty("color", theme.text);
663
+ card.style.setProperty("border", theme.border);
664
+ card.style.setProperty("border-radius", theme.radius);
536
665
  const title = doc.createElement("h2");
537
666
  title.textContent = labels.title;
538
667
  title.style.cssText = "margin: 0 0 10px; font-size: 18px; line-height: 1.3; font-weight: 600;";
668
+ title.style.setProperty("color", theme.text);
539
669
  const body = doc.createElement("p");
540
670
  body.textContent = labels.body;
541
- body.style.cssText = "margin: 0 0 20px; font-size: 14px; line-height: 1.5; color: #475467;";
671
+ body.style.cssText = "margin: 0 0 20px; font-size: 14px; line-height: 1.5;";
672
+ body.style.setProperty("color", theme.mutedText);
542
673
  const focus = doc.createElement("button");
543
674
  focus.type = "button";
544
675
  focus.textContent = labels.focus;
545
676
  focus.style.cssText = [
546
677
  "padding: 10px 16px",
547
678
  "border: 0",
548
- "border-radius: 10px",
549
- "background: #101828",
550
- "color: #ffffff",
551
679
  "font: inherit",
552
680
  "font-weight: 600",
553
681
  "cursor: pointer"
554
682
  ].join("; ");
683
+ focus.style.setProperty("background-color", theme.primary);
684
+ focus.style.setProperty("color", theme.primaryText);
685
+ focus.style.setProperty("border-radius", theme.buttonRadius);
555
686
  focus.addEventListener("click", handlers.onFocus);
556
687
  const cancel = doc.createElement("button");
557
688
  cancel.type = "button";
@@ -561,10 +692,11 @@ function showOverlay(win, labels, handlers) {
561
692
  "border: 0",
562
693
  "border-radius: 10px",
563
694
  "background: transparent",
564
- "color: #475467",
565
695
  "font: inherit",
566
696
  "cursor: pointer"
567
697
  ].join("; ");
698
+ cancel.style.setProperty("color", theme.mutedText);
699
+ cancel.style.setProperty("border-radius", theme.buttonRadius);
568
700
  cancel.addEventListener("click", handlers.onCancel);
569
701
  const actions = doc.createElement("div");
570
702
  actions.style.cssText = "display: flex; gap: 8px; align-items: center;";
@@ -599,6 +731,7 @@ function openAsseteraOnboarding(options) {
599
731
  );
600
732
  }
601
733
  const labels = resolveLabels(options);
734
+ const theme = resolveThemeOption(options);
602
735
  const overlayEnabled = options.overlay !== false;
603
736
  const popup = targetWindow.open(
604
737
  onboardUrl(options.walletUrl, targetWindow.location.origin, {
@@ -653,6 +786,16 @@ function openAsseteraOnboarding(options) {
653
786
  activePopup.close();
654
787
  fail(new OnboardingError("cancelled", "The user cancelled the Assetera Wallet onboarding."));
655
788
  }
789
+ const controls = {
790
+ focus() {
791
+ if (finished) return;
792
+ activePopup.focus();
793
+ },
794
+ cancel() {
795
+ if (finished) return;
796
+ cancel();
797
+ }
798
+ };
656
799
  function signMessage(message) {
657
800
  if (finished || activePopup.closed) {
658
801
  return Promise.reject(new OnboardingError("closed", "The Assetera Wallet window was closed."));
@@ -731,11 +874,12 @@ function openAsseteraOnboarding(options) {
731
874
  );
732
875
  }, CLOSE_POLL_MS2);
733
876
  if (overlayEnabled) {
734
- overlay = showOverlay(targetWindow, labels, {
735
- onFocus: () => activePopup.focus(),
736
- onCancel: cancel
877
+ overlay = showOverlay(targetWindow, labels, theme, {
878
+ onFocus: () => controls.focus(),
879
+ onCancel: () => controls.cancel()
737
880
  });
738
881
  }
882
+ options.onControls?.(controls);
739
883
  });
740
884
  }
741
885
 
@@ -834,6 +978,7 @@ function openAsseteraWalletManager(options) {
834
978
  }
835
979
 
836
980
  exports.CONNECT_PATH = CONNECT_PATH;
981
+ exports.HOLD_MAX_MS = HOLD_MAX_MS;
837
982
  exports.MANAGE_PATH = MANAGE_PATH;
838
983
  exports.MESSAGE_PREFIX = MESSAGE_PREFIX;
839
984
  exports.ManagerError = ManagerError;
@@ -850,6 +995,7 @@ exports.isAccountsMessage = isAccountsMessage;
850
995
  exports.isCaip2EvmChain = isCaip2EvmChain;
851
996
  exports.isDisconnectMessage = isDisconnectMessage;
852
997
  exports.isEventMessage = isEventMessage;
998
+ exports.isHoldMessage = isHoldMessage;
853
999
  exports.isOnboardCancelledMessage = isOnboardCancelledMessage;
854
1000
  exports.isOnboardedMessage = isOnboardedMessage;
855
1001
  exports.isReadyMessage = isReadyMessage;