@dintero/checkout-web-sdk 0.10.2 → 0.11.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.
@@ -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.1",
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.10",
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.16",
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.16",
52
+ webdriverio: "9.22.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
  */
@@ -97,7 +153,8 @@ const getSessionUrl = options => {
97
153
  language,
98
154
  ui,
99
155
  shouldCallValidateSession,
100
- popOut
156
+ popOut,
157
+ redirect
101
158
  } = options;
102
159
  if (!endpoint) {
103
160
  throw new Error("Invalid endpoint");
@@ -116,7 +173,9 @@ const getSessionUrl = options => {
116
173
  if (popOut) {
117
174
  params.append("role", "pop_out_launcher");
118
175
  }
119
- if (options.hasOwnProperty("hideTestMessage") && options["hideTestMessage"] !== undefined && options["hideTestMessage"] === true) {
176
+ if (
177
+ // biome-ignore lint/suspicious/noPrototypeBuiltins: test
178
+ options.hasOwnProperty("hideTestMessage") && options.hideTestMessage !== undefined && options.hideTestMessage === true) {
120
179
  params.append("hide_test_message", "true");
121
180
  }
122
181
  const hostname = getHostname();
@@ -124,7 +183,7 @@ const getSessionUrl = options => {
124
183
  params.append("sdk_hostname", hostname);
125
184
  }
126
185
  if (!redirect && !hostnameIsTop()) {
127
- params.append("sdk_not_top_level", "false");
186
+ params.append("sdk_not_top_level", "true");
128
187
  }
129
188
  if (endpoint === "https://checkout.dintero.com") {
130
189
  // Default endpoint will redirect via the view endpoint
@@ -166,7 +225,7 @@ const getHostname = () => {
166
225
  hostname
167
226
  } = window.location;
168
227
  return hostname;
169
- } catch (error) {
228
+ } catch (_) {
170
229
  return undefined;
171
230
  }
172
231
  };
@@ -178,7 +237,7 @@ const hostnameIsTop = () => {
178
237
  const hostname = getHostname();
179
238
  const topHostname = window.top.location.hostname;
180
239
  return topHostname && hostname && hostname === topHostname;
181
- } catch (error) {
240
+ } catch (_) {
182
241
  return false;
183
242
  }
184
243
  };
@@ -188,226 +247,142 @@ const url = {
188
247
  windowLocationAssign
189
248
  };
190
249
 
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
- });
250
+ const createPopOutWindow = (sid, url, width, height) => {
251
+ return new Promise(resolve => {
252
+ try {
253
+ // Creates a centered pop up window
254
+ const left = window.screenX + (window.outerWidth - width) / 2;
255
+ const top = window.screenY + (window.outerHeight - height) / 2;
256
+ const features = `width=${width},height=${height},left=${left},top=${top},location=no,menubar=no,toolbar=no,status=no`;
257
+ let popOut;
258
+ let timeout = -1;
259
+ // Set up listener for application loaded message from pop out window
260
+ const handleAppLoaded = event => {
261
+ const correctSource = event.source === popOut;
262
+ const correctOrigin = event.origin === new URL(url).origin;
263
+ const correctMessage = event.data && event.data.type === "AppLoaded";
264
+ const correctContext = event.data.context === "popOut";
265
+ const correctSid = event.data.sid === sid;
266
+ if (correctSource && correctOrigin && correctMessage && correctContext && correctSid) {
267
+ clearTimeout(timeout);
268
+ resolve(popOut);
269
+ window.removeEventListener("message", handleAppLoaded);
270
+ }
271
+ };
272
+ window.addEventListener("message", handleAppLoaded);
273
+ // Open pop out
274
+ popOut = window.open(url, "dintero-checkout", features);
275
+ // Check that pop out was opened
276
+ if (!popOut) {
277
+ console.log("createPopOutWindow no popOut");
278
+ resolve(undefined);
279
+ return;
280
+ }
281
+ // Trigger timeout if pop out is not loaded
282
+ timeout = window.setTimeout(() => {
283
+ console.log("createPopOutWindow timeout");
284
+ resolve(undefined);
285
+ }, 10000);
286
+ } catch (_err) {
287
+ resolve(undefined);
242
288
  }
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
- }
289
+ });
271
290
  };
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
- }, "*");
291
+ const openPopOut = async options => {
292
+ let unsubscribe;
293
+ let intervalId = -1;
294
+ let popOutWindow;
295
+ if (popOutWindow && !popOutWindow.closed) {
296
+ // Skip if already open.
297
+ return;
283
298
  }
284
- };
285
299
 
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
- };
300
+ // Open popup window
301
+ const popOutUrl = url.getPopOutUrl(options);
302
+ popOutWindow = await createPopOutWindow(options.sid, popOutUrl, Math.min(480, window.screen.width), Math.min(840, window.screen.height));
303
+ const focusPopOut = () => {
304
+ if (popOutWindow) {
305
+ popOutWindow.focus();
306
+ }
307
+ };
308
+ const cleanUpClosed = () => {
309
+ window.clearInterval(intervalId);
310
+ intervalId = -1;
311
+ window.removeEventListener("beforeunload", closePopOut);
312
+ popOutWindow = undefined;
313
+ options.onClose();
314
+ if (unsubscribe) {
315
+ unsubscribe();
316
+ }
317
+ };
318
+ const closePopOut = () => {
319
+ if (popOutWindow) {
320
+ popOutWindow.close();
321
+ }
322
+ cleanUpClosed();
323
+ };
324
+ const checkIfPopupClosed = () => {
325
+ if (popOutWindow?.closed) {
326
+ cleanUpClosed();
327
+ }
328
+ };
297
329
 
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
- };
330
+ // Close pop out if current window is closed
331
+ window.addEventListener("beforeunload", closePopOut);
310
332
 
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
- };
333
+ // Check if checkout is still open
334
+ intervalId = window.setInterval(checkIfPopupClosed, 200);
322
335
 
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
- }
336
+ // Set up pub/sub of messages from pop out to SDK
337
+ unsubscribe = options.onOpen(popOutWindow);
338
+ return {
339
+ close: closePopOut,
340
+ focus: focusPopOut,
341
+ popOutWindow
342
+ };
333
343
  };
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
- }, "*");
344
+ const postPopOutSessionLock = (popOutWindow, sid) => {
345
+ try {
346
+ if (popOutWindow) {
347
+ popOutWindow.postMessage({
348
+ type: "LockSession",
349
+ sid
350
+ }, "*");
351
+ }
352
+ } catch (e) {
353
+ console.error(e);
344
354
  }
345
355
  };
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
- }, "*");
356
+ const postPopOutSessionRefresh = (popOutWindow, sid) => {
357
+ try {
358
+ if (popOutWindow) {
359
+ popOutWindow.postMessage({
360
+ type: "RefreshSession",
361
+ sid
362
+ }, "*");
363
+ }
364
+ } catch (e) {
365
+ console.error(e);
356
366
  }
357
367
  };
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
- }, "*");
368
+ const postPopOutActivePaymentProductType = (popOutWindow, sid, paymentProductType) => {
369
+ try {
370
+ if (popOutWindow) {
371
+ popOutWindow.postMessage({
372
+ type: "SetActivePaymentProductType",
373
+ sid,
374
+ payment_product_type: paymentProductType
375
+ }, "*");
376
+ }
377
+ } catch (e) {
378
+ console.error(e);
369
379
  }
370
380
  };
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
- };
381
+ const popOutModule = {
382
+ openPopOut,
383
+ postPopOutSessionLock,
384
+ postPopOutSessionRefresh,
385
+ postPopOutActivePaymentProductType
411
386
  };
412
387
 
413
388
  const getBackdropZIndex = () => {
@@ -416,8 +391,8 @@ const getBackdropZIndex = () => {
416
391
  const highest = Array.from(elements).reduce((acc, element) => {
417
392
  try {
418
393
  const zIndexStr = document.defaultView.getComputedStyle(element, null).getPropertyValue("z-index");
419
- const zIndex = parseInt(zIndexStr || "0");
420
- if (!isNaN(zIndex) && zIndex > acc) {
394
+ const zIndex = Number.parseInt(zIndexStr || "0", 10);
395
+ if (!Number.isNaN(zIndex) && zIndex > acc) {
421
396
  return zIndex;
422
397
  }
423
398
  } catch (e) {
@@ -757,9 +732,9 @@ const configureButton = (button, {
757
732
 
758
733
  // Position
759
734
  button.style.position = "absolute";
760
- button.style.top = top + "px";
761
- button.style.left = left + "px";
762
- button.style.right = right + "px";
735
+ button.style.top = `${top}px`;
736
+ button.style.left = `${left}px`;
737
+ button.style.right = `${right}px`;
763
738
 
764
739
  // Appearance from checkout
765
740
  const {
@@ -787,7 +762,7 @@ const addHoverAndFocusVisibleStyles = (stylesHover, stylesFocusVisible) => {
787
762
  }
788
763
  const style = document.createElement("style");
789
764
  style.setAttribute("id", styleId);
790
- let content = [];
765
+ const content = [];
791
766
  if (stylesHover) {
792
767
  content.push(toCssEntity(`#${OPEN_POP_OUT_BUTTON_ID}:hover:not(:disabled)`, stylesHover));
793
768
  }
@@ -806,182 +781,211 @@ const toCssParameters = keyValues => {
806
781
  const slugify = str => {
807
782
  return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
808
783
  };
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);
784
+ const addPopOutButton = options => {
785
+ // Will add or update existing button
786
+ const {
787
+ container
788
+ } = options;
789
+ const exists = document.getElementById(OPEN_POP_OUT_BUTTON_ID);
790
+ const button = exists || document.createElement("button");
791
+ configureButton(button, options);
792
+ if (!exists) {
793
+ container.appendChild(button);
794
+ }
795
+ };
796
+ const setPopOutButtonDisabled = disabled => {
797
+ try {
798
+ const button = document.getElementById(OPEN_POP_OUT_BUTTON_ID);
799
+ if (button) {
800
+ if (disabled) {
801
+ button.setAttribute("disabled", disabled.toString());
802
+ } else {
803
+ button.removeAttribute("disabled");
804
+ }
805
+ }
806
+ } catch (e) {
807
+ // Ignore error and continue
808
+ console.error(e);
809
+ }
810
+ };
811
+ const removePopOutButton = () => {
812
+ try {
813
+ const button = document.getElementById(OPEN_POP_OUT_BUTTON_ID);
814
+ if (button) {
815
+ button.remove();
816
+ }
817
+ } catch (e) {
818
+ // Ignore error and continue
819
+ console.error(e);
820
+ }
821
+ };
822
+
823
+ /**
824
+ * Unsubscribe handler from event(s).
825
+ */
826
+
827
+ /**
828
+ * Post a message acknowledgement to the checkout iframe.
829
+ */
830
+ const postAck = (source, event) => {
831
+ if (event.data.mid && source) {
832
+ source.postMessage({
833
+ ack: event.data.mid
834
+ }, event.origin || "*");
835
+ }
836
+ };
837
+
838
+ /**
839
+ * Post a SessionLock-event to the checkout iframe.
840
+ */
841
+ const postSessionLock = (iframe, sid) => {
842
+ if (iframe.contentWindow) {
843
+ iframe.contentWindow.postMessage({
844
+ type: "LockSession",
845
+ sid
846
+ }, "*");
847
+ }
848
+ };
849
+
850
+ /**
851
+ * Post the validation result to the checkout iframe
852
+ */
853
+ const postValidationResult = (iframe, sid, result) => {
854
+ if (iframe.contentWindow) {
855
+ iframe.contentWindow.postMessage({
856
+ type: "ValidationResult",
857
+ sid,
858
+ ...result
859
+ }, "*");
860
+ }
861
+ };
862
+
863
+ /**
864
+ * Post RefreshSession-event to the checkout iframe.
865
+ */
866
+ const postSessionRefresh = (iframe, sid) => {
867
+ if (iframe.contentWindow) {
868
+ iframe.contentWindow.postMessage({
869
+ type: "RefreshSession",
870
+ sid
871
+ }, "*");
872
+ }
873
+ };
874
+
875
+ /**
876
+ * Post SetActivePaymentProductType-event to the checkout iframe.
877
+ */
878
+ const postActivePaymentProductType = (iframe, sid, paymentProductType) => {
879
+ if (iframe.contentWindow) {
880
+ iframe.contentWindow.postMessage({
881
+ type: "SetActivePaymentProductType",
882
+ sid,
883
+ payment_product_type: paymentProductType
884
+ }, "*");
885
+ }
886
+ };
887
+
888
+ /**
889
+ * Post ClosePopOut-event to the checkout iframe.
890
+ */
891
+ const postValidatePopOutEvent = (iframe, sid) => {
892
+ if (iframe.contentWindow) {
893
+ iframe.contentWindow.postMessage({
894
+ type: "ValidatingPopOut",
895
+ sid
896
+ }, "*");
819
897
  }
820
898
  };
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);
899
+
900
+ /**
901
+ * Post OpenPopOutFailed-event to the checkout iframe.
902
+ */
903
+ const postOpenPopOutFailedEvent = (iframe, sid) => {
904
+ if (iframe.contentWindow) {
905
+ iframe.contentWindow.postMessage({
906
+ type: "OpenPopOutFailed",
907
+ sid
908
+ }, "*");
834
909
  }
835
910
  };
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);
911
+
912
+ /**
913
+ * Post OpenedPopOut-event to the checkout iframe.
914
+ */
915
+ const postOpenPopOutEvent = (iframe, sid) => {
916
+ if (iframe.contentWindow) {
917
+ iframe.contentWindow.postMessage({
918
+ type: "OpenedPopOut",
919
+ sid
920
+ }, "*");
845
921
  }
846
922
  };
847
923
 
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
- });
924
+ /**
925
+ * Post ClosePopOut-event to the checkout iframe.
926
+ */
927
+ const postClosePopOutEvent = (iframe, sid) => {
928
+ if (iframe.contentWindow) {
929
+ iframe.contentWindow.postMessage({
930
+ type: "ClosedPopOut",
931
+ sid
932
+ }, "*");
933
+ }
888
934
  };
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;
935
+
936
+ /**
937
+ * Post SetLanguage-event to the checkout iframe.
938
+ */
939
+ const postSetLanguage = (iframe, sid, language) => {
940
+ if (iframe.contentWindow) {
941
+ iframe.contentWindow.postMessage({
942
+ type: "SetLanguage",
943
+ sid,
944
+ language
945
+ }, "*");
896
946
  }
947
+ };
897
948
 
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();
949
+ /**
950
+ * Subscribe to events from an iframe given a handler and a set
951
+ * of event types.
952
+ */
953
+ const subscribe = options => {
954
+ const {
955
+ sid,
956
+ endpoint,
957
+ handler,
958
+ eventTypes,
959
+ checkout
960
+ } = options;
961
+
962
+ // Wrap event handler in a function that checks for correct origin and
963
+ // filters on event type(s) in the event data.
964
+ const endpointUrl = new URL(endpoint);
965
+ const wrappedHandler = event => {
966
+ const correctOrigin = event.origin === endpointUrl.origin;
967
+ const correctWindow = event.source === checkout.iframe.contentWindow;
968
+ const correctSid = event.data && event.data.sid === sid;
969
+ const correctMessageType = eventTypes.indexOf(event.data?.type) !== -1;
970
+ if (correctOrigin && correctWindow && correctSid && correctMessageType) {
971
+ postAck(checkout.iframe.contentWindow, event);
972
+ handler(event.data, checkout);
925
973
  }
926
974
  };
927
975
 
928
- // Close pop out if current window is closed
929
- window.addEventListener("beforeunload", closePopOut);
976
+ // Add event listener to the iframe.
977
+ window.addEventListener("message", wrappedHandler, false);
930
978
 
931
- // Check if checkout is still open
932
- intervalId = window.setInterval(checkIfPopupClosed, 200);
979
+ // Function to remove the event listener from the iframe.
980
+ const unsubscribe = () => {
981
+ window.removeEventListener("message", wrappedHandler, false);
982
+ };
933
983
 
934
- // Set up pub/sub of messages from pop out to SDK
935
- unsubscribe = options.onOpen(popOutWindow);
984
+ // Return object with unsubscribe function.
936
985
  return {
937
- close: closePopOut,
938
- focus: focusPopOut,
939
- popOutWindow
986
+ unsubscribe
940
987
  };
941
988
  };
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
989
 
986
990
  /**
987
991
  * An event handler that navigates to the href in the event.
@@ -1006,7 +1010,7 @@ const setIframeHeight = (event, checkout) => {
1006
1010
  * An event handler that scrolls to the top of the iframe. This is useful when the user
1007
1011
  * is navigated to another page.
1008
1012
  */
1009
- const scrollToIframeTop = (event, checkout) => {
1013
+ const scrollToIframeTop = (_event, checkout) => {
1010
1014
  try {
1011
1015
  checkout.iframe.scrollIntoView({
1012
1016
  block: "start",
@@ -1057,7 +1061,7 @@ const createPopOutMessageHandler = (source, checkout) => {
1057
1061
  const popOutCompletedHandler = {
1058
1062
  internalPopOutHandler: true,
1059
1063
  eventTypes: paymentCompletedEvents,
1060
- handler: (eventData, checkout) => {
1064
+ handler: (eventData, _checkout) => {
1061
1065
  if (eventData.href) {
1062
1066
  // Remove open pop out button rendered by SDK
1063
1067
  removePopOutButton();
@@ -1079,18 +1083,18 @@ const createPopOutMessageHandler = (source, checkout) => {
1079
1083
  // Check that we should handle the message
1080
1084
  if (event.source === source && event.data.context === "popOut" && event.data.sid === checkout.options.sid) {
1081
1085
  // Check if handler matches incoming event and trigger the handler if so.
1082
- [
1086
+ for (const handlerObject of [
1083
1087
  // SDK events for managing the pop out flow.
1084
1088
  popOutChangedLanguageHandler, popOutCompletedHandler,
1085
1089
  // Events configured when the checkout was embedded.
1086
- ...checkout.handlers].forEach(handlerObject => {
1090
+ ...checkout.handlers]) {
1087
1091
  if (handlerObject.eventTypes.includes(event.data.type) && handlerObject.handler) {
1088
1092
  // Invoking the handler function if the event type matches the handler.
1089
1093
  safelyInvoke(() => {
1090
1094
  handlerObject.handler(event.data, checkout);
1091
1095
  });
1092
1096
  }
1093
- });
1097
+ }
1094
1098
  }
1095
1099
  };
1096
1100
  // Add messageRouter event listener to the Pop Out
@@ -1231,7 +1235,7 @@ const handleShowButton = (event, checkout) => {
1231
1235
  /**
1232
1236
  * Remove the pop out button above the embedded iframe
1233
1237
  */
1234
- const handleRemoveButton = (event, checkout) => {
1238
+ const handleRemoveButton = (event, _checkout) => {
1235
1239
  if (event.type === InternalCheckoutEvents.HidePopOutButton) {
1236
1240
  removePopOutButton();
1237
1241
  }
@@ -1294,15 +1298,16 @@ const embed = async options => {
1294
1298
  const {
1295
1299
  iframe,
1296
1300
  initiate
1297
- } = createIframeAsync(innerContainer, endpoint, url.getSessionUrl({
1301
+ } = createIframeAsync(innerContainer, url.getSessionUrl({
1298
1302
  sid,
1299
1303
  endpoint,
1300
1304
  language,
1301
1305
  ui: options.ui || "inline",
1302
1306
  shouldCallValidateSession: onValidateSession !== undefined,
1303
1307
  popOut,
1308
+ // biome-ignore lint/suspicious/noPrototypeBuiltins: test
1304
1309
  ...(options.hasOwnProperty("hideTestMessage") && {
1305
- hideTestMessage: options["hideTestMessage"]
1310
+ hideTestMessage: options.hideTestMessage
1306
1311
  })
1307
1312
  }));
1308
1313
 
@@ -1316,7 +1321,9 @@ const embed = async options => {
1316
1321
  // Try to remove backdrop if it exists
1317
1322
  removeBackdrop();
1318
1323
  }
1319
- subscriptions.forEach(sub => sub.unsubscribe());
1324
+ for (const sub of subscriptions) {
1325
+ sub.unsubscribe();
1326
+ }
1320
1327
  if (iframe.parentElement) {
1321
1328
  innerContainer.removeChild(iframe);
1322
1329
  }
@@ -1340,7 +1347,9 @@ const embed = async options => {
1340
1347
  sid,
1341
1348
  endpoint,
1342
1349
  handler: sessionEvent => {
1343
- eventSubscriptions.forEach(sub => sub.unsubscribe());
1350
+ for (const sub of eventSubscriptions) {
1351
+ sub.unsubscribe();
1352
+ }
1344
1353
  resolve(sessionEvent);
1345
1354
  },
1346
1355
  eventTypes: [resolveEvent],
@@ -1351,7 +1360,9 @@ const embed = async options => {
1351
1360
  sid,
1352
1361
  endpoint,
1353
1362
  handler: () => {
1354
- eventSubscriptions.forEach(sub => sub.unsubscribe());
1363
+ for (const sub of eventSubscriptions) {
1364
+ sub.unsubscribe();
1365
+ }
1355
1366
  reject(`Received unexpected event: ${rejectEvent}`);
1356
1367
  },
1357
1368
  eventTypes: [rejectEvent],
@@ -1391,7 +1402,7 @@ const embed = async options => {
1391
1402
  * error message. Only used when the embed function in the SDK has a dedicated handler for onPayment, onError etc.
1392
1403
  * If no custom handler is set the followHref handler is used instead.
1393
1404
  */
1394
- const handleWithResult = (sid, endpoint, handler) => {
1405
+ const handleWithResult = (_sid, endpoint, handler) => {
1395
1406
  return (event, checkout) => {
1396
1407
  if (!has_delivered_final_event) {
1397
1408
  has_delivered_final_event = true;
@@ -1403,7 +1414,7 @@ const embed = async options => {
1403
1414
  }
1404
1415
  pairs.push(["language", checkout.language]);
1405
1416
  pairs.push(["sdk", pkg.version]);
1406
- const urlQuery = pairs.filter(([key, value]) => value).map(([key, value]) => `${key}=${value}`).join("&");
1417
+ const urlQuery = pairs.filter(([_key, value]) => value).map(([key, value]) => `${key}=${value}`).join("&");
1407
1418
  checkout.iframe.setAttribute("src", composeUrl(endpoint, "embedResult/", urlQuery));
1408
1419
  handler(event, checkout);
1409
1420
  }
@@ -1448,6 +1459,9 @@ const embed = async options => {
1448
1459
  }, {
1449
1460
  handler: scrollToIframeTop,
1450
1461
  eventTypes: [InternalCheckoutEvents.ScrollToTop]
1462
+ }, {
1463
+ handler: followHref,
1464
+ eventTypes: [InternalCheckoutEvents.TopLevelNavigation]
1451
1465
  }, {
1452
1466
  handler: wrappedOnLoadedOrUpdated,
1453
1467
  eventTypes: [CheckoutEvents.SessionLoaded, CheckoutEvents.SessionUpdated]
@@ -1499,10 +1513,10 @@ const embed = async options => {
1499
1513
  session: undefined,
1500
1514
  popOutWindow: undefined
1501
1515
  };
1502
- handlers.forEach(({
1516
+ for (const {
1503
1517
  handler,
1504
1518
  eventTypes
1505
- }) => {
1519
+ } of handlers) {
1506
1520
  if (handler) {
1507
1521
  subscriptions.push(subscribe({
1508
1522
  sid,
@@ -1513,7 +1527,7 @@ const embed = async options => {
1513
1527
  source: checkout.iframe.contentWindow
1514
1528
  }));
1515
1529
  }
1516
- });
1530
+ }
1517
1531
 
1518
1532
  // Add iframe to DOM
1519
1533
  await initiate();