@swype-org/deposit 0.3.19 → 0.3.22

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
@@ -98,7 +98,19 @@ function parseIframeReady(data) {
98
98
  if (msg.type !== "blink:iframe-ready") {
99
99
  return null;
100
100
  }
101
- return { type: "blink:iframe-ready" };
101
+ return { type: "blink:iframe-ready", capabilities: parseCapabilities(msg.capabilities) };
102
+ }
103
+ function parseCapabilities(value) {
104
+ if (!Array.isArray(value)) {
105
+ return [];
106
+ }
107
+ return value.filter((entry) => typeof entry === "string");
108
+ }
109
+ function buildViewportMessage(viewportLvh, safeAreaBottom) {
110
+ return { type: "blink:viewport", viewportLvh, safeAreaBottom };
111
+ }
112
+ function buildRevealMessage() {
113
+ return { type: "blink:reveal" };
102
114
  }
103
115
  function buildSignedPayloadMessage(merchantId, payload, signature) {
104
116
  return {
@@ -505,6 +517,34 @@ function normalizeProviderError(err) {
505
517
  return { code: -32603, message: typeof err === "string" ? err : "Provider error" };
506
518
  }
507
519
 
520
+ // src/viewportMetrics.ts
521
+ function measureViewportMetrics() {
522
+ const unavailable = { viewportLvh: 0, safeAreaBottom: 0 };
523
+ if (typeof document === "undefined" || typeof window === "undefined" || typeof window.getComputedStyle !== "function" || !document.body) {
524
+ return unavailable;
525
+ }
526
+ try {
527
+ const probe = document.createElement("div");
528
+ probe.style.cssText = "position:fixed;top:0;left:0;width:0;visibility:hidden;pointer-events:none;height:100lvh;padding-bottom:env(safe-area-inset-bottom,0px)";
529
+ document.body.appendChild(probe);
530
+ let viewportLvh = 0;
531
+ let safeAreaBottom = 0;
532
+ try {
533
+ const computed = window.getComputedStyle(probe);
534
+ viewportLvh = Math.round(Number.parseFloat(computed.height) || 0);
535
+ safeAreaBottom = Math.round(Number.parseFloat(computed.paddingBottom) || 0);
536
+ } finally {
537
+ probe.remove();
538
+ }
539
+ if (viewportLvh <= 0) {
540
+ viewportLvh = Math.round(window.innerHeight || 0);
541
+ }
542
+ return { viewportLvh, safeAreaBottom };
543
+ } catch {
544
+ return unavailable;
545
+ }
546
+ }
547
+
508
548
  // src/iframe.ts
509
549
  var STYLE_ID = "blink-deposit-styles";
510
550
  var CLOSE_DURATION_MS = 280;
@@ -525,6 +565,8 @@ var STYLES = `
525
565
  [data-blink-overlay][data-blink-mobile-sheet] [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)}
526
566
  [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}
527
567
  @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}}
568
+ [data-blink-overlay][data-blink-fluid] [data-blink-container],[data-blink-overlay][data-blink-fluid][data-blink-mobile-sheet] [data-blink-container]{width:100%;max-width:100%;height:100%;max-height:100%;border-radius:0;box-shadow:none;animation:none;background:transparent;padding-bottom:0}
569
+ [data-blink-overlay][data-blink-fluid][data-blink-closing] [data-blink-container]{animation:none;transition:none;opacity:1}
528
570
  `;
529
571
  function createIframe(url, containerElement, options) {
530
572
  ensureStyles();
@@ -536,6 +578,9 @@ function createIframe(url, containerElement, options) {
536
578
  if (shouldUseMobileSheetLayout()) {
537
579
  overlay.setAttribute("data-blink-mobile-sheet", "");
538
580
  }
581
+ if (options?.fluid) {
582
+ overlay.setAttribute("data-blink-fluid", "");
583
+ }
539
584
  if (hidden) {
540
585
  overlay.style.display = "none";
541
586
  }
@@ -564,6 +609,37 @@ function createIframe(url, containerElement, options) {
564
609
  };
565
610
  attachBridge();
566
611
  iframe.addEventListener("load", attachBridge);
612
+ function postReveal() {
613
+ if (closed) return;
614
+ iframe.contentWindow?.postMessage(buildRevealMessage(), iframeOrigin);
615
+ }
616
+ function postViewportMetrics() {
617
+ if (closed || !options?.fluid) return;
618
+ const metrics = measureViewportMetrics();
619
+ if (metrics.viewportLvh <= 0) return;
620
+ iframe.contentWindow?.postMessage(
621
+ buildViewportMessage(metrics.viewportLvh, metrics.safeAreaBottom),
622
+ iframeOrigin
623
+ );
624
+ }
625
+ let viewportPostScheduled = false;
626
+ const onViewportChange = () => {
627
+ if (viewportPostScheduled) return;
628
+ viewportPostScheduled = true;
629
+ requestAnimationFrame(() => {
630
+ viewportPostScheduled = false;
631
+ postViewportMetrics();
632
+ });
633
+ };
634
+ if (options?.fluid) {
635
+ window.addEventListener("resize", onViewportChange);
636
+ window.addEventListener("orientationchange", onViewportChange);
637
+ }
638
+ const onLoadReveal = () => {
639
+ postViewportMetrics();
640
+ if (!hidden) postReveal();
641
+ };
642
+ iframe.addEventListener("load", onLoadReveal);
567
643
  let savedOverflow = "";
568
644
  let scrollLocked = false;
569
645
  const onBackdropClick = (event) => {
@@ -594,6 +670,8 @@ function createIframe(url, containerElement, options) {
594
670
  overlay.removeEventListener("click", onBackdropClick);
595
671
  overlay.removeEventListener("touchmove", onTouchMove);
596
672
  document.removeEventListener("keydown", onKeyDown);
673
+ window.removeEventListener("resize", onViewportChange);
674
+ window.removeEventListener("orientationchange", onViewportChange);
597
675
  }
598
676
  function unlockScroll() {
599
677
  if (!scrollLocked) return;
@@ -619,6 +697,7 @@ function createIframe(url, containerElement, options) {
619
697
  }
620
698
  function detachBridge() {
621
699
  iframe.removeEventListener("load", attachBridge);
700
+ iframe.removeEventListener("load", onLoadReveal);
622
701
  if (rpcHostHandle) {
623
702
  rpcHostHandle.detach();
624
703
  rpcHostHandle = null;
@@ -649,6 +728,14 @@ function createIframe(url, containerElement, options) {
649
728
  overlay.style.display = "";
650
729
  lockScroll();
651
730
  attachDismissalListeners();
731
+ postViewportMetrics();
732
+ postReveal();
733
+ },
734
+ postReveal() {
735
+ postReveal();
736
+ },
737
+ downgradeToFixed() {
738
+ overlay.removeAttribute("data-blink-fluid");
652
739
  },
653
740
  onClose(callback) {
654
741
  closeCallback = callback;
@@ -824,6 +911,8 @@ var DEFAULT_WEBVIEW_BASE_URL = "https://pay.blink.cash";
824
911
  var SANDBOX_WEBVIEW_BASE_URL = "https://pay-sandbox.blink.cash";
825
912
  var IFRAME_READY_TIMEOUT_MS = 2e3;
826
913
  var WARM_IFRAME_READY_TIMEOUT_MS = 6e4;
914
+ var LAYOUT_FLUID_CAPABILITY = "layout-fluid";
915
+ var IFRAME_NOT_READY = { ready: false, fluidCapable: false };
827
916
  function resolveWebviewBaseUrl(config) {
828
917
  if (config.webviewBaseUrl) return config.webviewBaseUrl;
829
918
  return config.environment === "sandbox" ? SANDBOX_WEBVIEW_BASE_URL : DEFAULT_WEBVIEW_BASE_URL;
@@ -894,7 +983,10 @@ var Deposit = class {
894
983
  const webviewBaseUrl = resolveWebviewBaseUrl(this.config);
895
984
  this.hostedOrigin = this.config.hostedFlowOrigin ?? new URL(webviewBaseUrl).origin;
896
985
  const preloadUrl = this.buildPreloadUrl(webviewBaseUrl);
897
- const iframe = createIframe(preloadUrl, this.config.containerElement, { hidden: true });
986
+ const iframe = createIframe(preloadUrl, this.config.containerElement, {
987
+ hidden: true,
988
+ fluid: this.isFluidLayout()
989
+ });
898
990
  const ready = this.waitForIframeReady(iframe, WARM_IFRAME_READY_TIMEOUT_MS);
899
991
  this.warmIframe = iframe;
900
992
  this.warmIframeReady = ready.promise;
@@ -930,6 +1022,8 @@ var Deposit = class {
930
1022
  preloadUrl.searchParams.set("merchantId", this.config.merchantId);
931
1023
  }
932
1024
  this.applyFullWidgetParam(preloadUrl);
1025
+ this.applyLayoutParam(preloadUrl);
1026
+ this.applyViewportParams(preloadUrl);
933
1027
  return preloadUrl.toString();
934
1028
  }
935
1029
  /**
@@ -1075,6 +1169,35 @@ var Deposit = class {
1075
1169
  url.searchParams.set("enableFullWidget", "false");
1076
1170
  }
1077
1171
  }
1172
+ /** Fluid is the default; `layout: 'fixed'` opts back into the legacy container. */
1173
+ isFluidLayout() {
1174
+ return this.config.layout !== "fixed";
1175
+ }
1176
+ /**
1177
+ * Marks the hosted-flow URL as fluid-layout: the iframe spans the full
1178
+ * viewport and the hosted flow renders the card/sheet chrome itself.
1179
+ * Only applied to preload URLs — the URL-param fallback path exists
1180
+ * precisely because the webview never signalled ready, so it cannot be
1181
+ * assumed to understand fluid layout and gets the fixed container.
1182
+ */
1183
+ applyLayoutParam(url) {
1184
+ if (this.isFluidLayout()) {
1185
+ url.searchParams.set("layout", "fluid");
1186
+ }
1187
+ }
1188
+ /**
1189
+ * Seeds the merchant page's large-viewport height and safe-area bottom
1190
+ * inset onto the fluid preload URL so the hosted flow can size the sheet
1191
+ * before its JS boots. `blink:viewport` messages keep the values fresh
1192
+ * afterwards (rotation, window resize) — see iframe.ts.
1193
+ */
1194
+ applyViewportParams(url) {
1195
+ if (!this.isFluidLayout()) return;
1196
+ const metrics = measureViewportMetrics();
1197
+ if (metrics.viewportLvh <= 0) return;
1198
+ url.searchParams.set("viewportLvh", String(metrics.viewportLvh));
1199
+ url.searchParams.set("safeAreaBottom", String(metrics.safeAreaBottom));
1200
+ }
1078
1201
  setStatus(status) {
1079
1202
  if (this._status === status) return;
1080
1203
  this.log(`Status: ${this._status} \u2192 ${status}`);
@@ -1098,7 +1221,7 @@ var Deposit = class {
1098
1221
  iframeReadyPromise = Promise.race([
1099
1222
  warm.ready,
1100
1223
  new Promise(
1101
- (resolve) => setTimeout(() => resolve(false), IFRAME_READY_TIMEOUT_MS)
1224
+ (resolve) => setTimeout(() => resolve(IFRAME_NOT_READY), IFRAME_READY_TIMEOUT_MS)
1102
1225
  )
1103
1226
  ]).then((ready) => {
1104
1227
  warm.cancelReady();
@@ -1108,7 +1231,8 @@ var Deposit = class {
1108
1231
  } else {
1109
1232
  preloadIframe = createIframe(
1110
1233
  this.buildPreloadUrl(webviewBaseUrl),
1111
- this.config.containerElement
1234
+ this.config.containerElement,
1235
+ { fluid: this.isFluidLayout() }
1112
1236
  );
1113
1237
  iframeReadyPromise = this.waitForIframeReady(preloadIframe).promise;
1114
1238
  }
@@ -1138,7 +1262,11 @@ var Deposit = class {
1138
1262
  this.log("Request superseded or iframe dismissed during signer call");
1139
1263
  return;
1140
1264
  }
1141
- if (iframeReady) {
1265
+ if (iframeReady.ready) {
1266
+ if (this.isFluidLayout() && !iframeReady.fluidCapable) {
1267
+ preloadIframe.downgradeToFixed();
1268
+ this.log("Webview lacks layout-fluid capability \u2014 downgraded to fixed container");
1269
+ }
1142
1270
  const contentWindow = preloadIframe.contentWindow;
1143
1271
  if (contentWindow) {
1144
1272
  contentWindow.postMessage(
@@ -1193,8 +1321,10 @@ var Deposit = class {
1193
1321
  }
1194
1322
  }
1195
1323
  /**
1196
- * Resolves true when the iframe posts `blink:iframe-ready`, false on
1197
- * timeout. `cancel()` settles the promise as false immediately and releases
1324
+ * Resolves `{ ready: true, ... }` when the iframe posts
1325
+ * `blink:iframe-ready` (with `fluidCapable` reflecting its advertised
1326
+ * capabilities), or `{ ready: false, fluidCapable: false }` on timeout.
1327
+ * `cancel()` settles the promise as not-ready immediately and releases
1198
1328
  * the listener + timer — used to reap the generous warm-up wait once it no
1199
1329
  * longer matters (warm iframe consumed or discarded).
1200
1330
  */
@@ -1205,25 +1335,29 @@ var Deposit = class {
1205
1335
  const handler = (event) => {
1206
1336
  if (event.origin !== this.hostedOrigin) return;
1207
1337
  if (event.source !== iframeHandle.contentWindow) return;
1208
- if (parseIframeReady(event.data)) {
1209
- finish(true);
1338
+ const readyMessage = parseIframeReady(event.data);
1339
+ if (readyMessage) {
1340
+ finish({
1341
+ ready: true,
1342
+ fluidCapable: readyMessage.capabilities.includes(LAYOUT_FLUID_CAPABILITY)
1343
+ });
1210
1344
  }
1211
1345
  };
1212
1346
  window.addEventListener("message", handler);
1213
1347
  const timeout = setTimeout(() => {
1214
1348
  this.log("Iframe ready timeout \u2014 falling back to URL params");
1215
- finish(false);
1349
+ finish(IFRAME_NOT_READY);
1216
1350
  }, timeoutMs);
1217
1351
  let settled = false;
1218
- finish = (ready) => {
1352
+ finish = (result) => {
1219
1353
  if (settled) return;
1220
1354
  settled = true;
1221
1355
  window.removeEventListener("message", handler);
1222
1356
  clearTimeout(timeout);
1223
- resolve(ready);
1357
+ resolve(result);
1224
1358
  };
1225
1359
  });
1226
- return { promise, cancel: () => finish(false) };
1360
+ return { promise, cancel: () => finish(IFRAME_NOT_READY) };
1227
1361
  }
1228
1362
  startMessageListener(iframeHandle, onComplete) {
1229
1363
  const handler = (event) => {