@swype-org/deposit 0.3.15 → 0.3.19

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/README.md CHANGED
@@ -98,10 +98,20 @@ const deposit = new Deposit({
98
98
  containerElement: document.getElementById('deposit-root')!,
99
99
  signerTimeoutMs: 15_000,
100
100
  flowTimeoutMs: 300_000,
101
+ enableFullWidget: true,
101
102
  debug: false,
102
103
  });
103
104
  ```
104
105
 
106
+ ### `enableFullWidget`
107
+
108
+ Controls whether the hosted flow may show the full widget (the Deposit Options
109
+ entry screen for unauthenticated users). Defaults to `true`. The full widget
110
+ only appears when this option is enabled **and** the merchant's backend config
111
+ has it enabled — disabling either turns it off. Set it to `false` to force the
112
+ hosted flow straight to the standard deposit flow regardless of merchant
113
+ config.
114
+
105
115
  ## Error Handling
106
116
 
107
117
  Every error is a `DepositError` with a machine-readable `code`:
@@ -161,7 +161,13 @@ var ALLOWED_RPC_METHODS = /* @__PURE__ */ new Set([
161
161
  "wallet_addEthereumChain",
162
162
  "wallet_switchEthereumChain",
163
163
  "wallet_sendCalls",
164
- "wallet_watchAsset"
164
+ "wallet_watchAsset",
165
+ // EIP-2255: lets the SDK revoke the dApp's `eth_accounts` grant before
166
+ // re-prompting on "+ Add wallet" / back-from-setup, so MetaMask shows the
167
+ // account picker instead of silently reusing the previously-permitted
168
+ // account. Wallets that don't implement it return method-not-supported,
169
+ // which the SDK swallows.
170
+ "wallet_revokePermissions"
165
171
  ]);
166
172
  var FORWARDED_PROVIDER_EVENTS = [
167
173
  "accountsChanged",
@@ -518,15 +524,19 @@ var STYLES = `
518
524
  [data-blink-overlay][data-blink-mobile-sheet][data-blink-closing] [data-blink-container]{opacity:1;animation:blink-slide-down-full ${CLOSE_DURATION_MS}ms ease-in forwards}
519
525
  @media(max-width:${MOBILE_SHEET_MAX_VIEWPORT_PX}px){[data-blink-overlay]{align-items:flex-end;touch-action:none;overscroll-behavior:contain}[data-blink-container]{width:100%;max-width:100%;height:79vh;border-radius:24px 24px 0 0;box-shadow:0 -8px 40px rgba(0,0,0,.25);animation:blink-slide-up-full .35s cubic-bezier(.32,.72,0,1);padding-bottom:env(safe-area-inset-bottom,0px)}[data-blink-overlay][data-blink-closing] [data-blink-container]{opacity:1;animation:blink-slide-down-full ${CLOSE_DURATION_MS}ms ease-in forwards}}
520
526
  `;
521
- function createIframe(url, containerElement) {
527
+ function createIframe(url, containerElement, options) {
522
528
  ensureStyles();
523
529
  let closed = false;
530
+ let hidden = options?.hidden === true;
524
531
  let closeCallback = null;
525
532
  const overlay = document.createElement("div");
526
533
  overlay.setAttribute("data-blink-overlay", "");
527
534
  if (shouldUseMobileSheetLayout()) {
528
535
  overlay.setAttribute("data-blink-mobile-sheet", "");
529
536
  }
537
+ if (hidden) {
538
+ overlay.style.display = "none";
539
+ }
530
540
  const container = document.createElement("div");
531
541
  container.setAttribute("data-blink-container", "");
532
542
  const iframe = document.createElement("iframe");
@@ -552,8 +562,8 @@ function createIframe(url, containerElement) {
552
562
  };
553
563
  attachBridge();
554
564
  iframe.addEventListener("load", attachBridge);
555
- const savedOverflow = document.body.style.overflow;
556
- document.body.style.overflow = "hidden";
565
+ let savedOverflow = "";
566
+ let scrollLocked = false;
557
567
  const onBackdropClick = (event) => {
558
568
  if (event.target === overlay) triggerClose();
559
569
  };
@@ -563,15 +573,29 @@ function createIframe(url, containerElement) {
563
573
  const onTouchMove = (event) => {
564
574
  if (event.target === overlay) event.preventDefault();
565
575
  };
566
- overlay.addEventListener("click", onBackdropClick);
567
- overlay.addEventListener("touchmove", onTouchMove, { passive: false });
568
- document.addEventListener("keydown", onKeyDown);
576
+ function lockScroll() {
577
+ if (scrollLocked) return;
578
+ scrollLocked = true;
579
+ savedOverflow = document.body.style.overflow;
580
+ document.body.style.overflow = "hidden";
581
+ }
582
+ function attachDismissalListeners() {
583
+ overlay.addEventListener("click", onBackdropClick);
584
+ overlay.addEventListener("touchmove", onTouchMove, { passive: false });
585
+ document.addEventListener("keydown", onKeyDown);
586
+ }
587
+ if (!hidden) {
588
+ lockScroll();
589
+ attachDismissalListeners();
590
+ }
569
591
  function removeListeners() {
570
592
  overlay.removeEventListener("click", onBackdropClick);
571
593
  overlay.removeEventListener("touchmove", onTouchMove);
572
594
  document.removeEventListener("keydown", onKeyDown);
573
595
  }
574
596
  function unlockScroll() {
597
+ if (!scrollLocked) return;
598
+ scrollLocked = false;
575
599
  document.body.style.overflow = savedOverflow;
576
600
  }
577
601
  function triggerClose() {
@@ -609,6 +633,21 @@ function createIframe(url, containerElement) {
609
633
  isClosed() {
610
634
  return closed;
611
635
  },
636
+ isHidden() {
637
+ return hidden;
638
+ },
639
+ reveal() {
640
+ if (closed || !hidden) return;
641
+ hidden = false;
642
+ if (shouldUseMobileSheetLayout()) {
643
+ overlay.setAttribute("data-blink-mobile-sheet", "");
644
+ } else {
645
+ overlay.removeAttribute("data-blink-mobile-sheet");
646
+ }
647
+ overlay.style.display = "";
648
+ lockScroll();
649
+ attachDismissalListeners();
650
+ },
612
651
  onClose(callback) {
613
652
  closeCallback = callback;
614
653
  },
@@ -780,6 +819,13 @@ function buildSignerRequest(request, webviewBaseUrl) {
780
819
 
781
820
  // src/checkout.ts
782
821
  var DEFAULT_WEBVIEW_BASE_URL = "https://pay.blink.cash";
822
+ var SANDBOX_WEBVIEW_BASE_URL = "https://pay-sandbox.blink.cash";
823
+ var IFRAME_READY_TIMEOUT_MS = 2e3;
824
+ var WARM_IFRAME_READY_TIMEOUT_MS = 6e4;
825
+ function resolveWebviewBaseUrl(config) {
826
+ if (config.webviewBaseUrl) return config.webviewBaseUrl;
827
+ return config.environment === "sandbox" ? SANDBOX_WEBVIEW_BASE_URL : DEFAULT_WEBVIEW_BASE_URL;
828
+ }
783
829
  var Deposit = class {
784
830
  config;
785
831
  iframe = null;
@@ -792,6 +838,9 @@ var Deposit = class {
792
838
  _error = null;
793
839
  flowTimer = null;
794
840
  lastSignerResponse = null;
841
+ warmIframe = null;
842
+ warmIframeReady = null;
843
+ warmIframeReadyCancel = null;
795
844
  listeners = {
796
845
  complete: /* @__PURE__ */ new Set(),
797
846
  error: /* @__PURE__ */ new Set(),
@@ -822,6 +871,93 @@ var Deposit = class {
822
871
  this.log("Deposit instance created", {
823
872
  signer: typeof config.signer === "string" ? config.signer : "<function>"
824
873
  });
874
+ if (config.preload !== false) {
875
+ this.schedulePreload();
876
+ }
877
+ }
878
+ /**
879
+ * Warm up the hosted payment flow in a hidden iframe so `requestDeposit`
880
+ * opens an already-loaded page (JS bundle, auth session restore, and —
881
+ * when {@link DepositConfig.merchantId} is set — merchant config prefetch
882
+ * all happen in the background).
883
+ *
884
+ * Runs automatically after construction unless `preload: false`; call it
885
+ * manually to control the timing (e.g. on deposit-button hover). No-op
886
+ * when a warm iframe already exists or a deposit flow is active.
887
+ */
888
+ preload() {
889
+ if (this.destroyed || this.isActive) return;
890
+ if (this.warmIframe && !this.warmIframe.isClosed()) return;
891
+ if (typeof document === "undefined" || typeof window === "undefined") return;
892
+ const webviewBaseUrl = resolveWebviewBaseUrl(this.config);
893
+ this.hostedOrigin = this.config.hostedFlowOrigin ?? new URL(webviewBaseUrl).origin;
894
+ const preloadUrl = this.buildPreloadUrl(webviewBaseUrl);
895
+ const iframe = createIframe(preloadUrl, this.config.containerElement, { hidden: true });
896
+ const ready = this.waitForIframeReady(iframe, WARM_IFRAME_READY_TIMEOUT_MS);
897
+ this.warmIframe = iframe;
898
+ this.warmIframeReady = ready.promise;
899
+ this.warmIframeReadyCancel = ready.cancel;
900
+ this.log("Warm-up iframe created", { url: preloadUrl });
901
+ }
902
+ // Defer the automatic warm-up until the host page has finished its own
903
+ // load and the main thread is idle — constructing a Deposit instance must
904
+ // never slow the merchant page down.
905
+ schedulePreload() {
906
+ if (typeof window === "undefined" || typeof document === "undefined") return;
907
+ const start = () => {
908
+ if (!this.destroyed && !this.isActive) this.preload();
909
+ };
910
+ const whenIdle = () => {
911
+ const w = window;
912
+ if (typeof w.requestIdleCallback === "function") {
913
+ w.requestIdleCallback(start, { timeout: 3e3 });
914
+ } else {
915
+ setTimeout(start, 250);
916
+ }
917
+ };
918
+ if (document.readyState === "complete") {
919
+ whenIdle();
920
+ } else {
921
+ window.addEventListener("load", whenIdle, { once: true });
922
+ }
923
+ }
924
+ buildPreloadUrl(webviewBaseUrl) {
925
+ const preloadUrl = new URL(webviewBaseUrl);
926
+ preloadUrl.searchParams.set("preload", "true");
927
+ if (this.config.merchantId) {
928
+ preloadUrl.searchParams.set("merchantId", this.config.merchantId);
929
+ }
930
+ this.applyFullWidgetParam(preloadUrl);
931
+ return preloadUrl.toString();
932
+ }
933
+ /**
934
+ * Detach the warm iframe for use by a starting flow. Returns null (and
935
+ * cleans up) when there is none or it was closed in the meantime.
936
+ */
937
+ takeWarmIframe() {
938
+ const iframe = this.warmIframe;
939
+ const ready = this.warmIframeReady;
940
+ const cancelReady = this.warmIframeReadyCancel ?? (() => {
941
+ });
942
+ this.warmIframe = null;
943
+ this.warmIframeReady = null;
944
+ this.warmIframeReadyCancel = null;
945
+ if (!iframe || !ready) return null;
946
+ if (iframe.isClosed()) {
947
+ cancelReady();
948
+ iframe.destroy();
949
+ return null;
950
+ }
951
+ return { iframe, ready, cancelReady };
952
+ }
953
+ discardWarmIframe() {
954
+ this.warmIframeReadyCancel?.();
955
+ this.warmIframeReadyCancel = null;
956
+ this.warmIframeReady = null;
957
+ if (this.warmIframe) {
958
+ this.warmIframe.destroy();
959
+ this.warmIframe = null;
960
+ }
825
961
  }
826
962
  /**
827
963
  * Open the hosted payment flow for the given deposit.
@@ -909,9 +1045,10 @@ var Deposit = class {
909
1045
  /** Tear down the instance and release all resources. */
910
1046
  destroy() {
911
1047
  this.log("destroy() called");
1048
+ this.destroyed = true;
912
1049
  this.cleanup();
1050
+ this.discardWarmIframe();
913
1051
  this.setStatus("idle");
914
- this.destroyed = true;
915
1052
  for (const set of Object.values(this.listeners)) {
916
1053
  set.clear();
917
1054
  }
@@ -925,6 +1062,17 @@ var Deposit = class {
925
1062
  console.debug(`[BlinkDeposit] ${message}`);
926
1063
  }
927
1064
  }
1065
+ /**
1066
+ * Appends `enableFullWidget=false` to the hosted-flow URL when the merchant
1067
+ * integration disables the full widget. Absence of the param means enabled
1068
+ * (the default) — the hosted flow ANDs this with the merchant's backend
1069
+ * config, so the full widget renders only when both allow it.
1070
+ */
1071
+ applyFullWidgetParam(url) {
1072
+ if (this.config.enableFullWidget === false) {
1073
+ url.searchParams.set("enableFullWidget", "false");
1074
+ }
1075
+ }
928
1076
  setStatus(status) {
929
1077
  if (this._status === status) return;
930
1078
  this.log(`Status: ${this._status} \u2192 ${status}`);
@@ -933,15 +1081,35 @@ var Deposit = class {
933
1081
  }
934
1082
  async runSignerFlow(request, requestId, onComplete, onError) {
935
1083
  try {
936
- const webviewBaseUrl = this.config.webviewBaseUrl ?? DEFAULT_WEBVIEW_BASE_URL;
1084
+ const webviewBaseUrl = resolveWebviewBaseUrl(this.config);
937
1085
  this.hostedOrigin = this.config.hostedFlowOrigin ?? new URL(webviewBaseUrl).origin;
938
1086
  this.log("Calling signer", {
939
1087
  signer: typeof this.config.signer === "string" ? this.config.signer : "<function>"
940
1088
  });
941
1089
  const signerRequest = buildSignerRequest(request, webviewBaseUrl);
942
- const preloadUrl = new URL(webviewBaseUrl);
943
- preloadUrl.searchParams.set("preload", "true");
944
- const preloadIframe = createIframe(preloadUrl.toString(), this.config.containerElement);
1090
+ let preloadIframe;
1091
+ let iframeReadyPromise;
1092
+ const warm = this.takeWarmIframe();
1093
+ if (warm) {
1094
+ warm.iframe.reveal();
1095
+ preloadIframe = warm.iframe;
1096
+ iframeReadyPromise = Promise.race([
1097
+ warm.ready,
1098
+ new Promise(
1099
+ (resolve) => setTimeout(() => resolve(false), IFRAME_READY_TIMEOUT_MS)
1100
+ )
1101
+ ]).then((ready) => {
1102
+ warm.cancelReady();
1103
+ return ready;
1104
+ });
1105
+ this.log("Reusing warm preload iframe");
1106
+ } else {
1107
+ preloadIframe = createIframe(
1108
+ this.buildPreloadUrl(webviewBaseUrl),
1109
+ this.config.containerElement
1110
+ );
1111
+ iframeReadyPromise = this.waitForIframeReady(preloadIframe).promise;
1112
+ }
945
1113
  this.iframe = preloadIframe;
946
1114
  preloadIframe.onClose(() => {
947
1115
  onError(
@@ -958,7 +1126,6 @@ var Deposit = class {
958
1126
  signerRequest,
959
1127
  this.config.signerTimeoutMs
960
1128
  );
961
- const iframeReadyPromise = this.waitForIframeReady(preloadIframe);
962
1129
  const [signerResponse, iframeReady] = await Promise.all([
963
1130
  signerPromise,
964
1131
  iframeReadyPromise
@@ -990,6 +1157,7 @@ var Deposit = class {
990
1157
  hostedUrl.searchParams.set("merchantId", signerResponse.merchantId);
991
1158
  hostedUrl.searchParams.set("payload", signerResponse.payload);
992
1159
  hostedUrl.searchParams.set("signature", signerResponse.signature);
1160
+ this.applyFullWidgetParam(hostedUrl);
993
1161
  const targetUrl = hostedUrl.toString();
994
1162
  const iframeHandle = createIframe(targetUrl, this.config.containerElement);
995
1163
  this.iframe = iframeHandle;
@@ -1022,24 +1190,38 @@ var Deposit = class {
1022
1190
  }
1023
1191
  }
1024
1192
  }
1025
- waitForIframeReady(iframeHandle) {
1026
- return new Promise((resolve) => {
1193
+ /**
1194
+ * Resolves true when the iframe posts `blink:iframe-ready`, false on
1195
+ * timeout. `cancel()` settles the promise as false immediately and releases
1196
+ * the listener + timer — used to reap the generous warm-up wait once it no
1197
+ * longer matters (warm iframe consumed or discarded).
1198
+ */
1199
+ waitForIframeReady(iframeHandle, timeoutMs = IFRAME_READY_TIMEOUT_MS) {
1200
+ let finish = () => {
1201
+ };
1202
+ const promise = new Promise((resolve) => {
1027
1203
  const handler = (event) => {
1028
1204
  if (event.origin !== this.hostedOrigin) return;
1029
1205
  if (event.source !== iframeHandle.contentWindow) return;
1030
1206
  if (parseIframeReady(event.data)) {
1031
- window.removeEventListener("message", handler);
1032
- clearTimeout(timeout);
1033
- resolve(true);
1207
+ finish(true);
1034
1208
  }
1035
1209
  };
1036
1210
  window.addEventListener("message", handler);
1037
1211
  const timeout = setTimeout(() => {
1038
- window.removeEventListener("message", handler);
1039
1212
  this.log("Iframe ready timeout \u2014 falling back to URL params");
1040
- resolve(false);
1041
- }, 2e3);
1213
+ finish(false);
1214
+ }, timeoutMs);
1215
+ let settled = false;
1216
+ finish = (ready) => {
1217
+ if (settled) return;
1218
+ settled = true;
1219
+ window.removeEventListener("message", handler);
1220
+ clearTimeout(timeout);
1221
+ resolve(ready);
1222
+ };
1042
1223
  });
1224
+ return { promise, cancel: () => finish(false) };
1043
1225
  }
1044
1226
  startMessageListener(iframeHandle, onComplete) {
1045
1227
  const handler = (event) => {
@@ -1099,6 +1281,9 @@ var Deposit = class {
1099
1281
  }
1100
1282
  this.hostedOrigin = null;
1101
1283
  this.lastSignerResponse = null;
1284
+ if (!this.destroyed && this.config.preload !== false) {
1285
+ this.schedulePreload();
1286
+ }
1102
1287
  }
1103
1288
  emit(event, ...args) {
1104
1289
  for (const handler of this.listeners[event]) {
@@ -1111,6 +1296,6 @@ var Deposit = class {
1111
1296
  };
1112
1297
  var Checkout = Deposit;
1113
1298
 
1114
- export { ALLOWED_RPC_METHODS, BRIDGE_PROTOCOL_VERSION, Checkout, CheckoutError, DEFAULT_WEBVIEW_BASE_URL, Deposit, DepositError, attachRpcHost, createWalletDiscoverer, getDisplayMessage, parseBridgeMessage };
1115
- //# sourceMappingURL=chunk-MA7CVDFD.js.map
1116
- //# sourceMappingURL=chunk-MA7CVDFD.js.map
1299
+ export { ALLOWED_RPC_METHODS, BRIDGE_PROTOCOL_VERSION, Checkout, CheckoutError, DEFAULT_WEBVIEW_BASE_URL, Deposit, DepositError, SANDBOX_WEBVIEW_BASE_URL, attachRpcHost, createWalletDiscoverer, getDisplayMessage, parseBridgeMessage };
1300
+ //# sourceMappingURL=chunk-VMXCXXVN.js.map
1301
+ //# sourceMappingURL=chunk-VMXCXXVN.js.map