@dintero/checkout-web-sdk 0.10.2 → 0.11.0

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.
@@ -6,7 +6,7 @@ require('native-promise-only');
6
6
 
7
7
  var pkg = {
8
8
  name: "@dintero/checkout-web-sdk",
9
- version: "0.10.2",
9
+ version: "0.11.0",
10
10
  description: "Dintero Checkout SDK for web frontends",
11
11
  main: "dist/dintero-checkout-web-sdk.cjs.js",
12
12
  module: "dist/dintero-checkout-web-sdk.esm.js",
@@ -21,7 +21,7 @@ var pkg = {
21
21
  },
22
22
  scripts: {
23
23
  build: "yarn tsc --noEmit && preconstruct build",
24
- lint: "prettier --cache --log-level warn -c --config .prettierrc.yml .",
24
+ lint: "biome check",
25
25
  test: "vitest --config .vitest.config.mts",
26
26
  "semantic-release": "semantic-release",
27
27
  prepublishOnly: "yarn run build"
@@ -40,16 +40,16 @@ var pkg = {
40
40
  devDependencies: {
41
41
  "@babel/core": "7.28.5",
42
42
  "@babel/preset-typescript": "7.28.5",
43
+ "@biomejs/biome": "2.3.8",
43
44
  "@preconstruct/cli": "2.8.12",
44
45
  "@semantic-release/exec": "7.1.0",
45
46
  "@semantic-release/git": "10.0.1",
46
- "@vitest/browser": "4.0.8",
47
+ "@vitest/browser": "4.0.15",
47
48
  "@vitest/browser-webdriverio": "^4.0.4",
48
- prettier: "3.6.2",
49
49
  "semantic-release": "25.0.2",
50
50
  typescript: "5.9.3",
51
- vitest: "4.0.8",
52
- webdriverio: "9.20.0"
51
+ vitest: "4.0.15",
52
+ webdriverio: "9.21.0"
53
53
  },
54
54
  dependencies: {
55
55
  "native-promise-only": "0.8.1"
@@ -76,9 +76,65 @@ let InternalCheckoutEvents = /*#__PURE__*/function (InternalCheckoutEvents) {
76
76
  InternalCheckoutEvents["ScrollToTop"] = "ScrollToTop";
77
77
  InternalCheckoutEvents["ShowPopOutButton"] = "ShowPopOutButton";
78
78
  InternalCheckoutEvents["HidePopOutButton"] = "HidePopOutButton";
79
+ InternalCheckoutEvents["TopLevelNavigation"] = "TopLevelNavigation";
79
80
  return InternalCheckoutEvents;
80
81
  }({});
81
82
 
83
+ /**
84
+ * Creates an iframe and adds it to the container.
85
+ *
86
+ * Returns a promise that resolves to the iframe when the iframe has loaded.
87
+ * Rejects the promise if there is a problem loading the iframe.
88
+ */
89
+ const createIframeAsync = (container, url) => {
90
+ if (!container || !container.appendChild) {
91
+ throw new Error("Invalid container");
92
+ }
93
+ const iframe = document.createElement("iframe");
94
+
95
+ // No border, transparent and stretch to 100% of the container width.
96
+ iframe.setAttribute("frameborder", "0");
97
+ iframe.setAttribute("allowTransparency", "true");
98
+ iframe.setAttribute("style", "width:100%; height:0;");
99
+
100
+ // TODO: Get this to work as expected, might be tricky with current
101
+ // tests since they will require the csp to be "unsafe-inline".
102
+ // The server needs to add the same property in the Content Security
103
+ // Policy headers in the response for this to work. A CSP header set by
104
+ // a meta tag in the iframe target will not be detected as a valid
105
+ // CSP from the iframe host.
106
+ // Content Security Policy, should be limited to "endpoint".
107
+ // iframe.setAttribute("csp", `default-src ${endpoint}`);
108
+
109
+ // Apply extra restrictions to the content in the iframe.
110
+ // allow popups is needed to open terms in new window
111
+ iframe.setAttribute("sandbox", "allow-scripts allow-forms allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-top-navigation");
112
+
113
+ // Needed for to allow apple pay from iframe
114
+ iframe.setAttribute("allow", "payment; clipboard-write *");
115
+
116
+ // The download priority of the resource in the <iframe>'s src attribute.
117
+ iframe.setAttribute("importance", "high");
118
+
119
+ // Set the iframe source to the url.
120
+ iframe.setAttribute("src", url);
121
+
122
+ // Resolve or reject promise when iframe loads.
123
+
124
+ // // Add iframe to the container.
125
+ // container.appendChild(iframe);
126
+ return {
127
+ iframe,
128
+ initiate: async () => {
129
+ return new Promise((resolve, reject) => {
130
+ iframe.onload = () => resolve();
131
+ iframe.onerror = () => reject();
132
+ container.appendChild(iframe);
133
+ });
134
+ }
135
+ };
136
+ };
137
+
82
138
  /**
83
139
  * Wraps window.location.assign()
84
140
  */
@@ -116,7 +172,9 @@ const getSessionUrl = options => {
116
172
  if (popOut) {
117
173
  params.append("role", "pop_out_launcher");
118
174
  }
119
- if (options.hasOwnProperty("hideTestMessage") && options["hideTestMessage"] !== undefined && options["hideTestMessage"] === true) {
175
+ if (
176
+ // biome-ignore lint/suspicious/noPrototypeBuiltins: test
177
+ options.hasOwnProperty("hideTestMessage") && options.hideTestMessage !== undefined && options.hideTestMessage === true) {
120
178
  params.append("hide_test_message", "true");
121
179
  }
122
180
  const hostname = getHostname();
@@ -166,7 +224,7 @@ const getHostname = () => {
166
224
  hostname
167
225
  } = window.location;
168
226
  return hostname;
169
- } catch (error) {
227
+ } catch (_) {
170
228
  return undefined;
171
229
  }
172
230
  };
@@ -178,7 +236,7 @@ const hostnameIsTop = () => {
178
236
  const hostname = getHostname();
179
237
  const topHostname = window.top.location.hostname;
180
238
  return topHostname && hostname && hostname === topHostname;
181
- } catch (error) {
239
+ } catch (_) {
182
240
  return false;
183
241
  }
184
242
  };
@@ -188,226 +246,142 @@ const url = {
188
246
  windowLocationAssign
189
247
  };
190
248
 
191
- /**
192
- * Creates an iframe and adds it to the container.
193
- *
194
- * Returns a promise that resolves to the iframe when the iframe has loaded.
195
- * Rejects the promise if there is a problem loading the iframe.
196
- */
197
- const createIframeAsync = (container, endpoint, url) => {
198
- if (!container || !container.appendChild) {
199
- throw new Error("Invalid container");
200
- }
201
- const iframe = document.createElement("iframe");
202
-
203
- // No border, transparent and stretch to 100% of the container width.
204
- iframe.setAttribute("frameborder", "0");
205
- iframe.setAttribute("allowTransparency", "true");
206
- iframe.setAttribute("style", "width:100%; height:0;");
207
-
208
- // TODO: Get this to work as expected, might be tricky with current
209
- // tests since they will require the csp to be "unsafe-inline".
210
- // The server needs to add the same property in the Content Security
211
- // Policy headers in the response for this to work. A CSP header set by
212
- // a meta tag in the iframe target will not be detected as a valid
213
- // CSP from the iframe host.
214
- // Content Security Policy, should be limited to "endpoint".
215
- // iframe.setAttribute("csp", `default-src ${endpoint}`);
216
-
217
- // Apply extra restrictions to the content in the iframe.
218
- // allow popups is needed to open terms in new window
219
- iframe.setAttribute("sandbox", "allow-scripts allow-forms allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-top-navigation");
220
-
221
- // Needed for to allow apple pay from iframe
222
- iframe.setAttribute("allow", "payment; clipboard-write *");
223
-
224
- // The download priority of the resource in the <iframe>'s src attribute.
225
- iframe.setAttribute("importance", "high");
226
-
227
- // Set the iframe source to the url.
228
- iframe.setAttribute("src", url);
229
-
230
- // Resolve or reject promise when iframe loads.
231
-
232
- // // Add iframe to the container.
233
- // container.appendChild(iframe);
234
- return {
235
- iframe,
236
- initiate: async () => {
237
- return new Promise((resolve, reject) => {
238
- iframe.onload = () => resolve();
239
- iframe.onerror = () => reject();
240
- container.appendChild(iframe);
241
- });
249
+ const createPopOutWindow = (sid, url, width, height) => {
250
+ return new Promise(resolve => {
251
+ try {
252
+ // Creates a centered pop up window
253
+ const left = window.screenX + (window.outerWidth - width) / 2;
254
+ const top = window.screenY + (window.outerHeight - height) / 2;
255
+ const features = `width=${width},height=${height},left=${left},top=${top},location=no,menubar=no,toolbar=no,status=no`;
256
+ let popOut;
257
+ let timeout = -1;
258
+ // Set up listener for application loaded message from pop out window
259
+ const handleAppLoaded = event => {
260
+ const correctSource = event.source === popOut;
261
+ const correctOrigin = event.origin === new URL(url).origin;
262
+ const correctMessage = event.data && event.data.type === "AppLoaded";
263
+ const correctContext = event.data.context === "popOut";
264
+ const correctSid = event.data.sid === sid;
265
+ if (correctSource && correctOrigin && correctMessage && correctContext && correctSid) {
266
+ clearTimeout(timeout);
267
+ resolve(popOut);
268
+ window.removeEventListener("message", handleAppLoaded);
269
+ }
270
+ };
271
+ window.addEventListener("message", handleAppLoaded);
272
+ // Open pop out
273
+ popOut = window.open(url, "dintero-checkout", features);
274
+ // Check that pop out was opened
275
+ if (!popOut) {
276
+ console.log("createPopOutWindow no popOut");
277
+ resolve(undefined);
278
+ return;
279
+ }
280
+ // Trigger timeout if pop out is not loaded
281
+ timeout = window.setTimeout(() => {
282
+ console.log("createPopOutWindow timeout");
283
+ resolve(undefined);
284
+ }, 10000);
285
+ } catch (_err) {
286
+ resolve(undefined);
242
287
  }
243
- };
244
- };
245
-
246
- /**
247
- * Unsubscribe handler from event(s).
248
- */
249
-
250
- /**
251
- * Post a message acknowledgement to the checkout iframe.
252
- */
253
- const postAck = (source, event) => {
254
- if (event.data.mid && source) {
255
- source.postMessage({
256
- ack: event.data.mid
257
- }, event.origin || "*");
258
- }
259
- };
260
-
261
- /**
262
- * Post a SessionLock-event to the checkout iframe.
263
- */
264
- const postSessionLock = (iframe, sid) => {
265
- if (iframe.contentWindow) {
266
- iframe.contentWindow.postMessage({
267
- type: "LockSession",
268
- sid
269
- }, "*");
270
- }
288
+ });
271
289
  };
272
-
273
- /**
274
- * Post the validation result to the checkout iframe
275
- */
276
- const postValidationResult = (iframe, sid, result) => {
277
- if (iframe.contentWindow) {
278
- iframe.contentWindow.postMessage({
279
- type: "ValidationResult",
280
- sid,
281
- ...result
282
- }, "*");
290
+ const openPopOut = async options => {
291
+ let unsubscribe;
292
+ let intervalId = -1;
293
+ let popOutWindow;
294
+ if (popOutWindow && !popOutWindow.closed) {
295
+ // Skip if already open.
296
+ return;
283
297
  }
284
- };
285
298
 
286
- /**
287
- * Post RefreshSession-event to the checkout iframe.
288
- */
289
- const postSessionRefresh = (iframe, sid) => {
290
- if (iframe.contentWindow) {
291
- iframe.contentWindow.postMessage({
292
- type: "RefreshSession",
293
- sid
294
- }, "*");
295
- }
296
- };
299
+ // Open popup window
300
+ const popOutUrl = url.getPopOutUrl(options);
301
+ popOutWindow = await createPopOutWindow(options.sid, popOutUrl, Math.min(480, window.screen.width), Math.min(840, window.screen.height));
302
+ const focusPopOut = () => {
303
+ if (popOutWindow) {
304
+ popOutWindow.focus();
305
+ }
306
+ };
307
+ const cleanUpClosed = () => {
308
+ window.clearInterval(intervalId);
309
+ intervalId = -1;
310
+ window.removeEventListener("beforeunload", closePopOut);
311
+ popOutWindow = undefined;
312
+ options.onClose();
313
+ if (unsubscribe) {
314
+ unsubscribe();
315
+ }
316
+ };
317
+ const closePopOut = () => {
318
+ if (popOutWindow) {
319
+ popOutWindow.close();
320
+ }
321
+ cleanUpClosed();
322
+ };
323
+ const checkIfPopupClosed = () => {
324
+ if (popOutWindow?.closed) {
325
+ cleanUpClosed();
326
+ }
327
+ };
297
328
 
298
- /**
299
- * Post SetActivePaymentProductType-event to the checkout iframe.
300
- */
301
- const postActivePaymentProductType = (iframe, sid, paymentProductType) => {
302
- if (iframe.contentWindow) {
303
- iframe.contentWindow.postMessage({
304
- type: "SetActivePaymentProductType",
305
- sid,
306
- payment_product_type: paymentProductType
307
- }, "*");
308
- }
309
- };
329
+ // Close pop out if current window is closed
330
+ window.addEventListener("beforeunload", closePopOut);
310
331
 
311
- /**
312
- * Post ClosePopOut-event to the checkout iframe.
313
- */
314
- const postValidatePopOutEvent = (iframe, sid) => {
315
- if (iframe.contentWindow) {
316
- iframe.contentWindow.postMessage({
317
- type: "ValidatingPopOut",
318
- sid
319
- }, "*");
320
- }
321
- };
332
+ // Check if checkout is still open
333
+ intervalId = window.setInterval(checkIfPopupClosed, 200);
322
334
 
323
- /**
324
- * Post OpenPopOutFailed-event to the checkout iframe.
325
- */
326
- const postOpenPopOutFailedEvent = (iframe, sid) => {
327
- if (iframe.contentWindow) {
328
- iframe.contentWindow.postMessage({
329
- type: "OpenPopOutFailed",
330
- sid
331
- }, "*");
332
- }
335
+ // Set up pub/sub of messages from pop out to SDK
336
+ unsubscribe = options.onOpen(popOutWindow);
337
+ return {
338
+ close: closePopOut,
339
+ focus: focusPopOut,
340
+ popOutWindow
341
+ };
333
342
  };
334
-
335
- /**
336
- * Post OpenedPopOut-event to the checkout iframe.
337
- */
338
- const postOpenPopOutEvent = (iframe, sid) => {
339
- if (iframe.contentWindow) {
340
- iframe.contentWindow.postMessage({
341
- type: "OpenedPopOut",
342
- sid
343
- }, "*");
343
+ const postPopOutSessionLock = (popOutWindow, sid) => {
344
+ try {
345
+ if (popOutWindow) {
346
+ popOutWindow.postMessage({
347
+ type: "LockSession",
348
+ sid
349
+ }, "*");
350
+ }
351
+ } catch (e) {
352
+ console.error(e);
344
353
  }
345
354
  };
346
-
347
- /**
348
- * Post ClosePopOut-event to the checkout iframe.
349
- */
350
- const postClosePopOutEvent = (iframe, sid) => {
351
- if (iframe.contentWindow) {
352
- iframe.contentWindow.postMessage({
353
- type: "ClosedPopOut",
354
- sid
355
- }, "*");
355
+ const postPopOutSessionRefresh = (popOutWindow, sid) => {
356
+ try {
357
+ if (popOutWindow) {
358
+ popOutWindow.postMessage({
359
+ type: "RefreshSession",
360
+ sid
361
+ }, "*");
362
+ }
363
+ } catch (e) {
364
+ console.error(e);
356
365
  }
357
366
  };
358
-
359
- /**
360
- * Post SetLanguage-event to the checkout iframe.
361
- */
362
- const postSetLanguage = (iframe, sid, language) => {
363
- if (iframe.contentWindow) {
364
- iframe.contentWindow.postMessage({
365
- type: "SetLanguage",
366
- sid,
367
- language
368
- }, "*");
367
+ const postPopOutActivePaymentProductType = (popOutWindow, sid, paymentProductType) => {
368
+ try {
369
+ if (popOutWindow) {
370
+ popOutWindow.postMessage({
371
+ type: "SetActivePaymentProductType",
372
+ sid,
373
+ payment_product_type: paymentProductType
374
+ }, "*");
375
+ }
376
+ } catch (e) {
377
+ console.error(e);
369
378
  }
370
379
  };
371
-
372
- /**
373
- * Subscribe to events from an iframe given a handler and a set
374
- * of event types.
375
- */
376
- const subscribe = options => {
377
- const {
378
- sid,
379
- endpoint,
380
- handler,
381
- eventTypes,
382
- checkout
383
- } = options;
384
-
385
- // Wrap event handler in a function that checks for correct origin and
386
- // filters on event type(s) in the event data.
387
- const endpointUrl = new URL(endpoint);
388
- const wrappedHandler = event => {
389
- const correctOrigin = event.origin === endpointUrl.origin;
390
- const correctWindow = event.source === checkout.iframe.contentWindow;
391
- const correctSid = event.data && event.data.sid === sid;
392
- const correctMessageType = eventTypes.indexOf(event.data && event.data.type) !== -1;
393
- if (correctOrigin && correctWindow && correctSid && correctMessageType) {
394
- postAck(checkout.iframe.contentWindow, event);
395
- handler(event.data, checkout);
396
- }
397
- };
398
-
399
- // Add event listener to the iframe.
400
- window.addEventListener("message", wrappedHandler, false);
401
-
402
- // Function to remove the event listener from the iframe.
403
- const unsubscribe = () => {
404
- window.removeEventListener("message", wrappedHandler, false);
405
- };
406
-
407
- // Return object with unsubscribe function.
408
- return {
409
- unsubscribe
410
- };
380
+ const popOutModule = {
381
+ openPopOut,
382
+ postPopOutSessionLock,
383
+ postPopOutSessionRefresh,
384
+ postPopOutActivePaymentProductType
411
385
  };
412
386
 
413
387
  const getBackdropZIndex = () => {
@@ -416,8 +390,8 @@ const getBackdropZIndex = () => {
416
390
  const highest = Array.from(elements).reduce((acc, element) => {
417
391
  try {
418
392
  const zIndexStr = document.defaultView.getComputedStyle(element, null).getPropertyValue("z-index");
419
- const zIndex = parseInt(zIndexStr || "0");
420
- if (!isNaN(zIndex) && zIndex > acc) {
393
+ const zIndex = Number.parseInt(zIndexStr || "0", 10);
394
+ if (!Number.isNaN(zIndex) && zIndex > acc) {
421
395
  return zIndex;
422
396
  }
423
397
  } catch (e) {
@@ -757,9 +731,9 @@ const configureButton = (button, {
757
731
 
758
732
  // Position
759
733
  button.style.position = "absolute";
760
- button.style.top = top + "px";
761
- button.style.left = left + "px";
762
- button.style.right = right + "px";
734
+ button.style.top = `${top}px`;
735
+ button.style.left = `${left}px`;
736
+ button.style.right = `${right}px`;
763
737
 
764
738
  // Appearance from checkout
765
739
  const {
@@ -787,7 +761,7 @@ const addHoverAndFocusVisibleStyles = (stylesHover, stylesFocusVisible) => {
787
761
  }
788
762
  const style = document.createElement("style");
789
763
  style.setAttribute("id", styleId);
790
- let content = [];
764
+ const content = [];
791
765
  if (stylesHover) {
792
766
  content.push(toCssEntity(`#${OPEN_POP_OUT_BUTTON_ID}:hover:not(:disabled)`, stylesHover));
793
767
  }
@@ -806,182 +780,211 @@ const toCssParameters = keyValues => {
806
780
  const slugify = str => {
807
781
  return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
808
782
  };
809
- const addPopOutButton = options => {
810
- // Will add or update existing button
811
- const {
812
- container
813
- } = options;
814
- const exists = document.getElementById(OPEN_POP_OUT_BUTTON_ID);
815
- const button = exists || document.createElement("button");
816
- configureButton(button, options);
817
- if (!exists) {
818
- container.appendChild(button);
783
+ const addPopOutButton = options => {
784
+ // Will add or update existing button
785
+ const {
786
+ container
787
+ } = options;
788
+ const exists = document.getElementById(OPEN_POP_OUT_BUTTON_ID);
789
+ const button = exists || document.createElement("button");
790
+ configureButton(button, options);
791
+ if (!exists) {
792
+ container.appendChild(button);
793
+ }
794
+ };
795
+ const setPopOutButtonDisabled = disabled => {
796
+ try {
797
+ const button = document.getElementById(OPEN_POP_OUT_BUTTON_ID);
798
+ if (button) {
799
+ if (disabled) {
800
+ button.setAttribute("disabled", disabled.toString());
801
+ } else {
802
+ button.removeAttribute("disabled");
803
+ }
804
+ }
805
+ } catch (e) {
806
+ // Ignore error and continue
807
+ console.error(e);
808
+ }
809
+ };
810
+ const removePopOutButton = () => {
811
+ try {
812
+ const button = document.getElementById(OPEN_POP_OUT_BUTTON_ID);
813
+ if (button) {
814
+ button.remove();
815
+ }
816
+ } catch (e) {
817
+ // Ignore error and continue
818
+ console.error(e);
819
+ }
820
+ };
821
+
822
+ /**
823
+ * Unsubscribe handler from event(s).
824
+ */
825
+
826
+ /**
827
+ * Post a message acknowledgement to the checkout iframe.
828
+ */
829
+ const postAck = (source, event) => {
830
+ if (event.data.mid && source) {
831
+ source.postMessage({
832
+ ack: event.data.mid
833
+ }, event.origin || "*");
834
+ }
835
+ };
836
+
837
+ /**
838
+ * Post a SessionLock-event to the checkout iframe.
839
+ */
840
+ const postSessionLock = (iframe, sid) => {
841
+ if (iframe.contentWindow) {
842
+ iframe.contentWindow.postMessage({
843
+ type: "LockSession",
844
+ sid
845
+ }, "*");
846
+ }
847
+ };
848
+
849
+ /**
850
+ * Post the validation result to the checkout iframe
851
+ */
852
+ const postValidationResult = (iframe, sid, result) => {
853
+ if (iframe.contentWindow) {
854
+ iframe.contentWindow.postMessage({
855
+ type: "ValidationResult",
856
+ sid,
857
+ ...result
858
+ }, "*");
859
+ }
860
+ };
861
+
862
+ /**
863
+ * Post RefreshSession-event to the checkout iframe.
864
+ */
865
+ const postSessionRefresh = (iframe, sid) => {
866
+ if (iframe.contentWindow) {
867
+ iframe.contentWindow.postMessage({
868
+ type: "RefreshSession",
869
+ sid
870
+ }, "*");
871
+ }
872
+ };
873
+
874
+ /**
875
+ * Post SetActivePaymentProductType-event to the checkout iframe.
876
+ */
877
+ const postActivePaymentProductType = (iframe, sid, paymentProductType) => {
878
+ if (iframe.contentWindow) {
879
+ iframe.contentWindow.postMessage({
880
+ type: "SetActivePaymentProductType",
881
+ sid,
882
+ payment_product_type: paymentProductType
883
+ }, "*");
884
+ }
885
+ };
886
+
887
+ /**
888
+ * Post ClosePopOut-event to the checkout iframe.
889
+ */
890
+ const postValidatePopOutEvent = (iframe, sid) => {
891
+ if (iframe.contentWindow) {
892
+ iframe.contentWindow.postMessage({
893
+ type: "ValidatingPopOut",
894
+ sid
895
+ }, "*");
819
896
  }
820
897
  };
821
- const setPopOutButtonDisabled = disabled => {
822
- try {
823
- const button = document.getElementById(OPEN_POP_OUT_BUTTON_ID);
824
- if (button) {
825
- if (disabled) {
826
- button.setAttribute("disabled", disabled.toString());
827
- } else {
828
- button.removeAttribute("disabled");
829
- }
830
- }
831
- } catch (e) {
832
- // Ignore error and continue
833
- console.error(e);
898
+
899
+ /**
900
+ * Post OpenPopOutFailed-event to the checkout iframe.
901
+ */
902
+ const postOpenPopOutFailedEvent = (iframe, sid) => {
903
+ if (iframe.contentWindow) {
904
+ iframe.contentWindow.postMessage({
905
+ type: "OpenPopOutFailed",
906
+ sid
907
+ }, "*");
834
908
  }
835
909
  };
836
- const removePopOutButton = () => {
837
- try {
838
- const button = document.getElementById(OPEN_POP_OUT_BUTTON_ID);
839
- if (button) {
840
- button.remove();
841
- }
842
- } catch (e) {
843
- // Ignore error and continue
844
- console.error(e);
910
+
911
+ /**
912
+ * Post OpenedPopOut-event to the checkout iframe.
913
+ */
914
+ const postOpenPopOutEvent = (iframe, sid) => {
915
+ if (iframe.contentWindow) {
916
+ iframe.contentWindow.postMessage({
917
+ type: "OpenedPopOut",
918
+ sid
919
+ }, "*");
845
920
  }
846
921
  };
847
922
 
848
- const createPopOutWindow = (sid, url, width, height) => {
849
- return new Promise(resolve => {
850
- try {
851
- // Creates a centered pop up window
852
- const left = window.screenX + (window.outerWidth - width) / 2;
853
- const top = window.screenY + (window.outerHeight - height) / 2;
854
- const features = `width=${width},height=${height},left=${left},top=${top},location=no,menubar=no,toolbar=no,status=no`;
855
- let popOut;
856
- let timeout = -1;
857
- // Set up listener for application loaded message from pop out window
858
- const handleAppLoaded = event => {
859
- const correctSource = event.source === popOut;
860
- const correctOrigin = event.origin === new URL(url).origin;
861
- const correctMessage = event.data && event.data.type === "AppLoaded";
862
- const correctContext = event.data.context === "popOut";
863
- const correctSid = event.data.sid === sid;
864
- if (correctSource && correctOrigin && correctMessage && correctContext && correctSid) {
865
- clearTimeout(timeout);
866
- resolve(popOut);
867
- window.removeEventListener("message", handleAppLoaded);
868
- }
869
- };
870
- window.addEventListener("message", handleAppLoaded);
871
- // Open pop out
872
- popOut = window.open(url, "dintero-checkout", features);
873
- // Check that pop out was opened
874
- if (!popOut) {
875
- console.log("createPopOutWindow no popOut");
876
- resolve(undefined);
877
- return;
878
- }
879
- // Trigger timeout if pop out is not loaded
880
- timeout = window.setTimeout(() => {
881
- console.log("createPopOutWindow timeout");
882
- resolve(undefined);
883
- }, 10000);
884
- } catch (err) {
885
- resolve(undefined);
886
- }
887
- });
923
+ /**
924
+ * Post ClosePopOut-event to the checkout iframe.
925
+ */
926
+ const postClosePopOutEvent = (iframe, sid) => {
927
+ if (iframe.contentWindow) {
928
+ iframe.contentWindow.postMessage({
929
+ type: "ClosedPopOut",
930
+ sid
931
+ }, "*");
932
+ }
888
933
  };
889
- const openPopOut = async options => {
890
- let unsubscribe;
891
- let intervalId = -1;
892
- let popOutWindow;
893
- if (popOutWindow && !popOutWindow.closed) {
894
- // Skip if already open.
895
- return;
934
+
935
+ /**
936
+ * Post SetLanguage-event to the checkout iframe.
937
+ */
938
+ const postSetLanguage = (iframe, sid, language) => {
939
+ if (iframe.contentWindow) {
940
+ iframe.contentWindow.postMessage({
941
+ type: "SetLanguage",
942
+ sid,
943
+ language
944
+ }, "*");
896
945
  }
946
+ };
897
947
 
898
- // Open popup window
899
- const popOutUrl = url.getPopOutUrl(options);
900
- popOutWindow = await createPopOutWindow(options.sid, popOutUrl, Math.min(480, window.screen.width), Math.min(840, window.screen.height));
901
- const focusPopOut = () => {
902
- if (popOutWindow) {
903
- popOutWindow.focus();
904
- }
905
- };
906
- const cleanUpClosed = () => {
907
- window.clearInterval(intervalId);
908
- intervalId = -1;
909
- window.removeEventListener("beforeunload", closePopOut);
910
- popOutWindow = undefined;
911
- options.onClose();
912
- if (unsubscribe) {
913
- unsubscribe();
914
- }
915
- };
916
- const closePopOut = () => {
917
- if (popOutWindow) {
918
- popOutWindow.close();
919
- }
920
- cleanUpClosed();
921
- };
922
- const checkIfPopupClosed = () => {
923
- if (popOutWindow && popOutWindow.closed) {
924
- cleanUpClosed();
948
+ /**
949
+ * Subscribe to events from an iframe given a handler and a set
950
+ * of event types.
951
+ */
952
+ const subscribe = options => {
953
+ const {
954
+ sid,
955
+ endpoint,
956
+ handler,
957
+ eventTypes,
958
+ checkout
959
+ } = options;
960
+
961
+ // Wrap event handler in a function that checks for correct origin and
962
+ // filters on event type(s) in the event data.
963
+ const endpointUrl = new URL(endpoint);
964
+ const wrappedHandler = event => {
965
+ const correctOrigin = event.origin === endpointUrl.origin;
966
+ const correctWindow = event.source === checkout.iframe.contentWindow;
967
+ const correctSid = event.data && event.data.sid === sid;
968
+ const correctMessageType = eventTypes.indexOf(event.data?.type) !== -1;
969
+ if (correctOrigin && correctWindow && correctSid && correctMessageType) {
970
+ postAck(checkout.iframe.contentWindow, event);
971
+ handler(event.data, checkout);
925
972
  }
926
973
  };
927
974
 
928
- // Close pop out if current window is closed
929
- window.addEventListener("beforeunload", closePopOut);
975
+ // Add event listener to the iframe.
976
+ window.addEventListener("message", wrappedHandler, false);
930
977
 
931
- // Check if checkout is still open
932
- intervalId = window.setInterval(checkIfPopupClosed, 200);
978
+ // Function to remove the event listener from the iframe.
979
+ const unsubscribe = () => {
980
+ window.removeEventListener("message", wrappedHandler, false);
981
+ };
933
982
 
934
- // Set up pub/sub of messages from pop out to SDK
935
- unsubscribe = options.onOpen(popOutWindow);
983
+ // Return object with unsubscribe function.
936
984
  return {
937
- close: closePopOut,
938
- focus: focusPopOut,
939
- popOutWindow
985
+ unsubscribe
940
986
  };
941
987
  };
942
- const postPopOutSessionLock = (popOutWindow, sid) => {
943
- try {
944
- if (popOutWindow) {
945
- popOutWindow.postMessage({
946
- type: "LockSession",
947
- sid
948
- }, "*");
949
- }
950
- } catch (e) {
951
- console.error(e);
952
- }
953
- };
954
- const postPopOutSessionRefresh = (popOutWindow, sid) => {
955
- try {
956
- if (popOutWindow) {
957
- popOutWindow.postMessage({
958
- type: "RefreshSession",
959
- sid
960
- }, "*");
961
- }
962
- } catch (e) {
963
- console.error(e);
964
- }
965
- };
966
- const postPopOutActivePaymentProductType = (popOutWindow, sid, paymentProductType) => {
967
- try {
968
- if (popOutWindow) {
969
- popOutWindow.postMessage({
970
- type: "SetActivePaymentProductType",
971
- sid,
972
- payment_product_type: paymentProductType
973
- }, "*");
974
- }
975
- } catch (e) {
976
- console.error(e);
977
- }
978
- };
979
- const popOutModule = {
980
- openPopOut,
981
- postPopOutSessionLock,
982
- postPopOutSessionRefresh,
983
- postPopOutActivePaymentProductType
984
- };
985
988
 
986
989
  /**
987
990
  * An event handler that navigates to the href in the event.
@@ -1006,7 +1009,7 @@ const setIframeHeight = (event, checkout) => {
1006
1009
  * An event handler that scrolls to the top of the iframe. This is useful when the user
1007
1010
  * is navigated to another page.
1008
1011
  */
1009
- const scrollToIframeTop = (event, checkout) => {
1012
+ const scrollToIframeTop = (_event, checkout) => {
1010
1013
  try {
1011
1014
  checkout.iframe.scrollIntoView({
1012
1015
  block: "start",
@@ -1057,7 +1060,7 @@ const createPopOutMessageHandler = (source, checkout) => {
1057
1060
  const popOutCompletedHandler = {
1058
1061
  internalPopOutHandler: true,
1059
1062
  eventTypes: paymentCompletedEvents,
1060
- handler: (eventData, checkout) => {
1063
+ handler: (eventData, _checkout) => {
1061
1064
  if (eventData.href) {
1062
1065
  // Remove open pop out button rendered by SDK
1063
1066
  removePopOutButton();
@@ -1079,18 +1082,18 @@ const createPopOutMessageHandler = (source, checkout) => {
1079
1082
  // Check that we should handle the message
1080
1083
  if (event.source === source && event.data.context === "popOut" && event.data.sid === checkout.options.sid) {
1081
1084
  // Check if handler matches incoming event and trigger the handler if so.
1082
- [
1085
+ for (const handlerObject of [
1083
1086
  // SDK events for managing the pop out flow.
1084
1087
  popOutChangedLanguageHandler, popOutCompletedHandler,
1085
1088
  // Events configured when the checkout was embedded.
1086
- ...checkout.handlers].forEach(handlerObject => {
1089
+ ...checkout.handlers]) {
1087
1090
  if (handlerObject.eventTypes.includes(event.data.type) && handlerObject.handler) {
1088
1091
  // Invoking the handler function if the event type matches the handler.
1089
1092
  safelyInvoke(() => {
1090
1093
  handlerObject.handler(event.data, checkout);
1091
1094
  });
1092
1095
  }
1093
- });
1096
+ }
1094
1097
  }
1095
1098
  };
1096
1099
  // Add messageRouter event listener to the Pop Out
@@ -1231,7 +1234,7 @@ const handleShowButton = (event, checkout) => {
1231
1234
  /**
1232
1235
  * Remove the pop out button above the embedded iframe
1233
1236
  */
1234
- const handleRemoveButton = (event, checkout) => {
1237
+ const handleRemoveButton = (event, _checkout) => {
1235
1238
  if (event.type === InternalCheckoutEvents.HidePopOutButton) {
1236
1239
  removePopOutButton();
1237
1240
  }
@@ -1294,15 +1297,16 @@ const embed = async options => {
1294
1297
  const {
1295
1298
  iframe,
1296
1299
  initiate
1297
- } = createIframeAsync(innerContainer, endpoint, url.getSessionUrl({
1300
+ } = createIframeAsync(innerContainer, url.getSessionUrl({
1298
1301
  sid,
1299
1302
  endpoint,
1300
1303
  language,
1301
1304
  ui: options.ui || "inline",
1302
1305
  shouldCallValidateSession: onValidateSession !== undefined,
1303
1306
  popOut,
1307
+ // biome-ignore lint/suspicious/noPrototypeBuiltins: test
1304
1308
  ...(options.hasOwnProperty("hideTestMessage") && {
1305
- hideTestMessage: options["hideTestMessage"]
1309
+ hideTestMessage: options.hideTestMessage
1306
1310
  })
1307
1311
  }));
1308
1312
 
@@ -1316,7 +1320,9 @@ const embed = async options => {
1316
1320
  // Try to remove backdrop if it exists
1317
1321
  removeBackdrop();
1318
1322
  }
1319
- subscriptions.forEach(sub => sub.unsubscribe());
1323
+ for (const sub of subscriptions) {
1324
+ sub.unsubscribe();
1325
+ }
1320
1326
  if (iframe.parentElement) {
1321
1327
  innerContainer.removeChild(iframe);
1322
1328
  }
@@ -1340,7 +1346,9 @@ const embed = async options => {
1340
1346
  sid,
1341
1347
  endpoint,
1342
1348
  handler: sessionEvent => {
1343
- eventSubscriptions.forEach(sub => sub.unsubscribe());
1349
+ for (const sub of eventSubscriptions) {
1350
+ sub.unsubscribe();
1351
+ }
1344
1352
  resolve(sessionEvent);
1345
1353
  },
1346
1354
  eventTypes: [resolveEvent],
@@ -1351,7 +1359,9 @@ const embed = async options => {
1351
1359
  sid,
1352
1360
  endpoint,
1353
1361
  handler: () => {
1354
- eventSubscriptions.forEach(sub => sub.unsubscribe());
1362
+ for (const sub of eventSubscriptions) {
1363
+ sub.unsubscribe();
1364
+ }
1355
1365
  reject(`Received unexpected event: ${rejectEvent}`);
1356
1366
  },
1357
1367
  eventTypes: [rejectEvent],
@@ -1391,7 +1401,7 @@ const embed = async options => {
1391
1401
  * error message. Only used when the embed function in the SDK has a dedicated handler for onPayment, onError etc.
1392
1402
  * If no custom handler is set the followHref handler is used instead.
1393
1403
  */
1394
- const handleWithResult = (sid, endpoint, handler) => {
1404
+ const handleWithResult = (_sid, endpoint, handler) => {
1395
1405
  return (event, checkout) => {
1396
1406
  if (!has_delivered_final_event) {
1397
1407
  has_delivered_final_event = true;
@@ -1403,7 +1413,7 @@ const embed = async options => {
1403
1413
  }
1404
1414
  pairs.push(["language", checkout.language]);
1405
1415
  pairs.push(["sdk", pkg.version]);
1406
- const urlQuery = pairs.filter(([key, value]) => value).map(([key, value]) => `${key}=${value}`).join("&");
1416
+ const urlQuery = pairs.filter(([_key, value]) => value).map(([key, value]) => `${key}=${value}`).join("&");
1407
1417
  checkout.iframe.setAttribute("src", composeUrl(endpoint, "embedResult/", urlQuery));
1408
1418
  handler(event, checkout);
1409
1419
  }
@@ -1448,6 +1458,9 @@ const embed = async options => {
1448
1458
  }, {
1449
1459
  handler: scrollToIframeTop,
1450
1460
  eventTypes: [InternalCheckoutEvents.ScrollToTop]
1461
+ }, {
1462
+ handler: followHref,
1463
+ eventTypes: [InternalCheckoutEvents.TopLevelNavigation]
1451
1464
  }, {
1452
1465
  handler: wrappedOnLoadedOrUpdated,
1453
1466
  eventTypes: [CheckoutEvents.SessionLoaded, CheckoutEvents.SessionUpdated]
@@ -1499,10 +1512,10 @@ const embed = async options => {
1499
1512
  session: undefined,
1500
1513
  popOutWindow: undefined
1501
1514
  };
1502
- handlers.forEach(({
1515
+ for (const {
1503
1516
  handler,
1504
1517
  eventTypes
1505
- }) => {
1518
+ } of handlers) {
1506
1519
  if (handler) {
1507
1520
  subscriptions.push(subscribe({
1508
1521
  sid,
@@ -1513,7 +1526,7 @@ const embed = async options => {
1513
1526
  source: checkout.iframe.contentWindow
1514
1527
  }));
1515
1528
  }
1516
- });
1529
+ }
1517
1530
 
1518
1531
  // Add iframe to DOM
1519
1532
  await initiate();