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