@paydock/client-sdk 1.145.0-beta → 1.148.0-beta

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.
Files changed (43) hide show
  1. package/README.md +129 -1
  2. package/bundles/index.cjs +935 -103
  3. package/bundles/index.cjs.d.ts +629 -336
  4. package/bundles/index.mjs +935 -104
  5. package/bundles/index.mjs.d.ts +629 -336
  6. package/bundles/types/api/api-base.d.ts +6 -0
  7. package/bundles/types/api/api-base.d.ts.map +1 -1
  8. package/bundles/types/api/api-charge-internal.d.ts +4 -3
  9. package/bundles/types/api/api-charge-internal.d.ts.map +1 -1
  10. package/bundles/types/canvas-3ds/canvas-3ds-event-map.d.ts +65 -0
  11. package/bundles/types/canvas-3ds/canvas-3ds-event-map.d.ts.map +1 -1
  12. package/bundles/types/canvas-3ds/canvas-3ds.d.ts +34 -5
  13. package/bundles/types/canvas-3ds/canvas-3ds.d.ts.map +1 -1
  14. package/bundles/types/canvas-3ds/index.d.ts +1 -1
  15. package/bundles/types/canvas-3ds/index.d.ts.map +1 -1
  16. package/bundles/types/canvas-3ds/services/gpayments-service.d.ts +151 -2
  17. package/bundles/types/canvas-3ds/services/gpayments-service.d.ts.map +1 -1
  18. package/bundles/types/canvas-3ds/services/standalone3ds-service.d.ts +16 -0
  19. package/bundles/types/canvas-3ds/services/standalone3ds-service.d.ts.map +1 -1
  20. package/bundles/types/components/iframe-event.d.ts +4 -3
  21. package/bundles/types/components/iframe-event.d.ts.map +1 -1
  22. package/bundles/types/components/iframe.d.ts +5 -0
  23. package/bundles/types/components/iframe.d.ts.map +1 -1
  24. package/bundles/types/components/param.d.ts +19 -0
  25. package/bundles/types/components/param.d.ts.map +1 -1
  26. package/bundles/types/index-cba.d.ts +1 -1
  27. package/bundles/types/index-cba.d.ts.map +1 -1
  28. package/bundles/types/index.d.ts +2 -2
  29. package/bundles/types/index.d.ts.map +1 -1
  30. package/bundles/types/widget/multi-widget.d.ts +19 -0
  31. package/bundles/types/widget/multi-widget.d.ts.map +1 -1
  32. package/bundles/widget.umd.js +935 -103
  33. package/bundles/widget.umd.js.d.ts +629 -336
  34. package/bundles/widget.umd.js.min.d.ts +629 -336
  35. package/bundles/widget.umd.min.js +1 -1
  36. package/docs/api-canvas3ds.md +41 -8
  37. package/docs/api-checkout-button.md +15 -0
  38. package/docs/api-ps-widget.md +15 -0
  39. package/docs/api-widget.md +15 -0
  40. package/docs/canvas3ds-examples.md +43 -1
  41. package/docs/widget-examples.md +41 -0
  42. package/package.json +20 -11
  43. package/slate.md +84 -1
package/bundles/index.cjs CHANGED
@@ -1865,7 +1865,7 @@ SDK.headerKeys = Object.freeze({
1865
1865
  version: 'x-sdk-version',
1866
1866
  type: 'x-sdk-type'
1867
1867
  });
1868
- SDK._version = 'v1.145.0-beta';
1868
+ SDK._version = 'v1.148.0-beta';
1869
1869
 
1870
1870
  var ACCESS_HEADER_NAME = {
1871
1871
  accessToken: 'x-access-token',
@@ -4721,6 +4721,15 @@ var ApiBase = /*#__PURE__*/function () {
4721
4721
  this.env.setEnv(env, alias);
4722
4722
  return this;
4723
4723
  }
4724
+ /**
4725
+ * Origin (`scheme://host[:port]`) of the API this client talks to, for the current env/alias.
4726
+ * Used to validate the `origin` of postMessages the API itself sends to the page.
4727
+ */
4728
+ }, {
4729
+ key: "getApiOrigin",
4730
+ value: function getApiOrigin() {
4731
+ return ApiBase.toOrigin(this.env.getConf().url);
4732
+ }
4724
4733
  }, {
4725
4734
  key: "requestObservable",
4726
4735
  value: function requestObservable(method, url, requestBody) {
@@ -4933,6 +4942,18 @@ var ApiBase = /*#__PURE__*/function () {
4933
4942
  environment: this.env.getEnv()
4934
4943
  };
4935
4944
  }
4945
+ }], [{
4946
+ key: "toOrigin",
4947
+ value: function toOrigin(url) {
4948
+ try {
4949
+ return new URL(url).origin;
4950
+ } catch (_a) {
4951
+ // Not a parsable URL: the returned string is not an origin and will never match a
4952
+ // MessageEvent.origin — surface the misconfiguration instead of failing silently.
4953
+ console.warn("[paydock/client-sdk] cannot derive an origin from configured URL \"".concat(url, "\""));
4954
+ return url.replace(/\/+$/, '');
4955
+ }
4956
+ }
4936
4957
  }]);
4937
4958
  }();
4938
4959
 
@@ -5010,6 +5031,7 @@ var ApiChargeInternal = /*#__PURE__*/function () {
5010
5031
  value: function walletCallback(payload) {
5011
5032
  return this.api.getClientPromise('POST', WALLET_CALLBACK_LINK).send(payload);
5012
5033
  }
5034
+ /** Two-step mode: runs the deferred Standalone 3DS authentication (AReq). Charge-token auth, like `standalone3dsHandle`. */
5013
5035
  }, {
5014
5036
  key: "standalone3dsProcess",
5015
5037
  value: function standalone3dsProcess(payload) {
@@ -5153,6 +5175,10 @@ var IFrame = /*#__PURE__*/function () {
5153
5175
  _classCallCheck(this, IFrame);
5154
5176
  this.container = container;
5155
5177
  }
5178
+ /**
5179
+ * @param options.onLoad Called on every `load` of the iframe (initial document and each
5180
+ * subsequent navigation). Attached before `src` is set so a fast load is never missed.
5181
+ */
5156
5182
  return _createClass(IFrame, [{
5157
5183
  key: "load",
5158
5184
  value: function load(link) {
@@ -5161,6 +5187,9 @@ var IFrame = /*#__PURE__*/function () {
5161
5187
  if (!this.container.isExist() || this.isExist()) return;
5162
5188
  var iFrame = document.createElement('iframe');
5163
5189
  if (options.title) iFrame.title = options.title;
5190
+ if (options.onLoad) {
5191
+ iFrame.addEventListener('load', options.onLoad);
5192
+ }
5164
5193
  if (iframeClass) iFrame.classList.add(iframeClass);
5165
5194
  // Set additional attributes before src and DOM insertion so that
5166
5195
  // Permissions-Policy (e.g. allow="payment") is in place when the
@@ -5301,14 +5330,14 @@ var IFrameEvent = /*#__PURE__*/function () {
5301
5330
  data = JSON.parse(event.data);
5302
5331
  } catch (error) {}
5303
5332
  if (!data) return;
5304
- _this.emit(data);
5333
+ _this.emit(data, event.origin);
5305
5334
  });
5306
5335
  }
5307
5336
  return _createClass(IFrameEvent, [{
5308
5337
  key: "emit",
5309
- value: function emit(data) {
5338
+ value: function emit(data, origin) {
5310
5339
  for (var key in this.listeners) {
5311
- if (this.listeners[key].event === data.event && data.widget_id === this.listeners[key].widget_id) this.listeners[key].listener.apply(this, [data]);
5340
+ if (this.listeners[key].event === data.event && data.widget_id === this.listeners[key].widget_id) this.listeners[key].listener.apply(this, [data, origin]);
5312
5341
  }
5313
5342
  }
5314
5343
  }, {
@@ -5368,6 +5397,51 @@ var IFRAME_STATUS;
5368
5397
  IFRAME_STATUS["TIMEOUT"] = "AuthTimedOut";
5369
5398
  IFRAME_STATUS["INVALID"] = "invalid_event";
5370
5399
  })(IFRAME_STATUS || (IFRAME_STATUS = {}));
5400
+ /**
5401
+ * Maps a `/process` rejection onto the error object the one-step callback delivers for the same outcome
5402
+ * (`{ error_code, error_description, gateway_specific_description, process_error_code }`), so `error`
5403
+ * carries the same payload in both flows. Absent fields are left out.
5404
+ */
5405
+ function toOutcomeError(err) {
5406
+ var _a, _b;
5407
+ if (err instanceof Error) {
5408
+ // no API envelope to map (the body was not the API's JSON): report it as unreachable, so the
5409
+ // merchant can tell this retryable case apart from a final API answer by `error_code` alone
5410
+ return {
5411
+ error_code: ERROR_CAUSE.SERVICE_UNREACHABLE,
5412
+ error_description: 'Network request failed: the service did not answer with a valid response.'
5413
+ };
5414
+ }
5415
+ var apiError = err !== null && err !== void 0 ? err : {};
5416
+ var gateway = (_b = (_a = apiError.details) === null || _a === void 0 ? void 0 : _a.messages) === null || _b === void 0 ? void 0 : _b[0];
5417
+ var fields = {
5418
+ error_code: apiError.code,
5419
+ error_description: apiError.message,
5420
+ gateway_specific_description: gateway === null || gateway === void 0 ? void 0 : gateway.gateway_specific_description,
5421
+ process_error_code: gateway === null || gateway === void 0 ? void 0 : gateway.gateway_specific_code
5422
+ };
5423
+ var outcomeError = {};
5424
+ for (var _i = 0, _Object$keys = Object.keys(fields); _i < _Object$keys.length; _i++) {
5425
+ var key = _Object$keys[_i];
5426
+ if (fields[key]) {
5427
+ outcomeError[key] = fields[key];
5428
+ }
5429
+ }
5430
+ return outcomeError;
5431
+ }
5432
+ /**
5433
+ * `true` when `/process` produced no API answer: the request never got a response (network, timeout, abort)
5434
+ * or the response body was not the API's JSON (e.g. an HTML page from a proxy in between).
5435
+ */
5436
+ function isTransportFailure(err) {
5437
+ if (err instanceof Error) {
5438
+ return true;
5439
+ }
5440
+ var code = err === null || err === void 0 ? void 0 : err.code;
5441
+ return code === ERROR_CAUSE.SERVICE_UNREACHABLE || code === ERROR_CAUSE.ABORTED;
5442
+ }
5443
+ /** Callback `process_status` of a two-step charge: fingerprint done, authentication not started. */
5444
+ var PROCESS_STATUS_DEFERRED = 'deferred';
5371
5445
  /**
5372
5446
  * Event name constants emitted during the Standalone 3DS (GPayments) flow.
5373
5447
  * Used internally by `GPaymentsService` and re-exported publicly as `CANVAS_3DS_EVENT`.
@@ -5381,6 +5455,25 @@ var GPAYMENTS_EVENT = {
5381
5455
  DECOUPLED: 'chargeAuthDecoupled',
5382
5456
  /** Challenge required — issuer demands user interaction. Payload: `Canvas3dsAuthPayload`. */
5383
5457
  CHALLENGE: 'chargeAuthChallenge',
5458
+ /**
5459
+ * Two-step mode only: fingerprint done, SDK waiting for `Canvas3ds.authenticate()`. Emitted once, before any
5460
+ * outcome event (`INFO` may precede it); never for a one-step charge. Payload: `Canvas3dsReadyPayload`.
5461
+ */
5462
+ READY: 'chargeAuthReady',
5463
+ /**
5464
+ * The challenge iframe fired its FIRST `load` after `chargeAuthChallenge` (the ACS page has painted),
5465
+ * or the `CHALLENGE_LOADED_FALLBACK_MS` safety fallback elapsed without one. Emitted exactly once per
5466
+ * challenge; never emitted for a decoupled challenge (no challenge iframe). Payload: `Canvas3dsChallengeLoadedPayload`.
5467
+ */
5468
+ CHALLENGE_LOADED: 'chargeAuthChallengeLoaded',
5469
+ /**
5470
+ * The issuer delivered the final authentication result to the 3DS Server (the shopper finished the
5471
+ * challenge, or approved a decoupled authentication) and the SDK is now obtaining the outcome. Emitted at
5472
+ * most once per authentication, always before the terminal event. Detected through the `secondary_url`
5473
+ * result monitor (`source: 'poll'`) or, when the callback message arrives first, right before the outcome
5474
+ * it carries (`source: 'callback'`). Payload: `Canvas3dsChallengeCompletedPayload`.
5475
+ */
5476
+ CHALLENGE_COMPLETED: 'chargeAuthChallengeCompleted',
5384
5477
  /** Informational signal emitted by the GPayments flow. Internal — not part of the public typed surface. */
5385
5478
  INFO: 'chargeAuthInfo',
5386
5479
  /** Error during the flow (network, parsing, etc.). Payload: `Canvas3dsErrorPayload`. */
@@ -5392,13 +5485,64 @@ var GPAYMENTS_EVENT = {
5392
5485
  * backwards-compatible internal usage.
5393
5486
  */
5394
5487
  var CANVAS_3DS_EVENT = GPAYMENTS_EVENT;
5488
+ /**
5489
+ * Upper bound on how long `chargeAuthChallengeLoaded` waits for the challenge iframe's first `load`.
5490
+ * When it elapses the event is emitted with `reason: 'timeout'` so a merchant loader is never held
5491
+ * forever; the real `load` arriving afterwards is the paint and is simply ignored.
5492
+ */
5493
+ var CHALLENGE_LOADED_FALLBACK_MS = 2500;
5494
+ /**
5495
+ * Interval of the `secondary_url` (GPayments `resultMonUrl`) poll while an ACS challenge is on screen.
5496
+ * The monitor flips to `AuthResultReady` when the issuer's RReq reaches the 3DS Server — before the ACS has
5497
+ * even returned its final page to the browser — so this poll is what makes `chargeAuthChallengeCompleted`
5498
+ * land before the shopper sees any post-submit page. GPayments recommends 2 s and leaves the interval to the
5499
+ * requestor; 1 s matches the cadence of GPayments' own monitoring frame.
5500
+ */
5501
+ var CHALLENGE_RESULT_POLL_INTERVAL_MS = 1000;
5502
+ /**
5503
+ * Interval of the `secondary_url` poll for a decoupled challenge (shopper approves out-of-band, possibly
5504
+ * minutes later; nothing to cover on screen) — the GPayments-recommended 2 s.
5505
+ */
5506
+ var DECOUPLED_RESULT_POLL_INTERVAL_MS = 2000;
5507
+ /** The result-monitor request failed at transport level (network, abort, timeout) — retried, not fatal. */
5508
+ var ResultMonitorTransportError = /*#__PURE__*/function (_Error) {
5509
+ function ResultMonitorTransportError() {
5510
+ var _this;
5511
+ _classCallCheck(this, ResultMonitorTransportError);
5512
+ _this = _callSuper(this, ResultMonitorTransportError, ['Standalone 3DS result monitor request failed']);
5513
+ _this.name = 'ResultMonitorTransportError';
5514
+ return _this;
5515
+ }
5516
+ _inherits(ResultMonitorTransportError, _Error);
5517
+ return _createClass(ResultMonitorTransportError);
5518
+ }(/*#__PURE__*/_wrapNativeSuper(Error));
5395
5519
  var GPaymentsService = /*#__PURE__*/function () {
5396
5520
  function GPaymentsService(container, api, eventEmitter) {
5521
+ var trustedOrigins = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
5397
5522
  _classCallCheck(this, GPaymentsService);
5398
5523
  this.container = container;
5399
5524
  this.api = api;
5400
5525
  this.eventEmitter = eventEmitter;
5526
+ this.trustedOrigins = trustedOrigins;
5401
5527
  this.resultRead = false;
5528
+ /** `chargeAuthChallengeLoaded` already emitted for the current challenge (by `load` or by the fallback). */
5529
+ this.challengeLoadedEmitted = false;
5530
+ /** `chargeAuthChallengeCompleted` already emitted for the current authentication (poll or callback). */
5531
+ this.challengeCompletedEmitted = false;
5532
+ /** Set once a terminal event was emitted: no lifecycle event may follow (reset when a new challenge starts). */
5533
+ this.challengeLifecycleClosed = false;
5534
+ this.challengeLoadedTimer = null;
5535
+ this.resultPollTimer = null;
5536
+ /** One-shot guard for the `chargeAuth` leg — the callback message may be delivered more than once. */
5537
+ this.authHandled = false;
5538
+ /** Two-step mode: `charge_3ds_id` of the deferred callback message; `null` until it arrives (always for one-step). */
5539
+ this.deferredCharge3dsId = null;
5540
+ /** `authenticate()` latch; consumed when the deferred message arrives, reset only after a `/process` transport failure. */
5541
+ this.authenticateRequested = false;
5542
+ /** One `/process` call per widget instance (a transport failure allows one more `authenticate()`). */
5543
+ this.authenticationStarted = false;
5544
+ /** The single `/process` retry after a transport failure has been granted; the next failure is final. */
5545
+ this.authenticationRetryUsed = false;
5402
5546
  this.iFrameEvent = new IFrameEvent(window);
5403
5547
  }
5404
5548
  return _createClass(GPaymentsService, [{
@@ -5418,6 +5562,7 @@ var GPaymentsService = /*#__PURE__*/function () {
5418
5562
  key: "initializeIFrames",
5419
5563
  value: function initializeIFrames(initializationUrl, secondaryUrl, title) {
5420
5564
  var hideAuthorization = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
5565
+ var onLoad = arguments.length > 4 ? arguments[4] : undefined;
5421
5566
  var container = this.container.getElement();
5422
5567
  if (!container) {
5423
5568
  return;
@@ -5428,7 +5573,8 @@ var GPaymentsService = /*#__PURE__*/function () {
5428
5573
  this.browserAndChallengeContainer = new Container('#paydock_authorization_iframe');
5429
5574
  this.iFrameAuthorization = new IFrame(this.browserAndChallengeContainer);
5430
5575
  this.iFrameAuthorization.load(initializationUrl, {
5431
- title: title
5576
+ title: title,
5577
+ onLoad: onLoad
5432
5578
  });
5433
5579
  if (secondaryUrl) {
5434
5580
  var divSecondaryURL = document.createElement('div');
@@ -5462,25 +5608,250 @@ var GPaymentsService = /*#__PURE__*/function () {
5462
5608
  }, {
5463
5609
  key: "setupIFrameEvents",
5464
5610
  value: function setupIFrameEvents(widgetId) {
5465
- var _this = this;
5466
- this.iFrameEvent.on(EVENT$4.CHARGE_AUTH, widgetId, function (data) {
5611
+ var _this2 = this;
5612
+ this.iFrameEvent.on(EVENT$4.CHARGE_AUTH, widgetId, function (data, origin) {
5467
5613
  if (data.status === IFRAME_STATUS.SKIPPED || data.status === IFRAME_STATUS.FINISHED) {
5614
+ // this channel is spoofable — any frame that knows the charge_3ds_id can post to it.
5615
+ // A message from an unknown sender must never terminate a live authentication: ignore it.
5616
+ if (!_this2.isTrustedOrigin(origin)) {
5617
+ return;
5618
+ }
5619
+ // one-shot: the callback message can be delivered more than once (it is posted to both
5620
+ // `parent` and `top`) — a duplicate must not re-emit events or duplicate challenge iframes
5621
+ if (_this2.authHandled) {
5622
+ return;
5623
+ }
5624
+ _this2.authHandled = true;
5468
5625
  if (data.info) {
5469
- _this.eventEmitter.emit(GPAYMENTS_EVENT.INFO, {
5626
+ _this2.eventEmitter.emit(GPAYMENTS_EVENT.INFO, {
5470
5627
  info: data.info
5471
5628
  });
5472
5629
  }
5473
- _this.performAuthentication(data);
5630
+ _this2.removeIframes();
5631
+ if (data.process_status === PROCESS_STATUS_DEFERRED) {
5632
+ // two-step: announce readiness; authenticate now only if `authenticate()` was already called
5633
+ _this2.deferredCharge3dsId = data.charge_3ds_id;
5634
+ _this2.eventEmitter.emit(GPAYMENTS_EVENT.READY, {
5635
+ charge_3ds_id: data.charge_3ds_id
5636
+ });
5637
+ if (_this2.authenticateRequested) {
5638
+ _this2.performAuthentication(data.charge_3ds_id);
5639
+ }
5640
+ } else if (typeof data.process_status === 'string') {
5641
+ // the API already ran the authentication inside the callback: no /process call needed
5642
+ _this2.handleAuthenticationOutcome({
5643
+ status: data.process_status,
5644
+ result: data.result,
5645
+ error: data.error
5646
+ }, data.charge_3ds_id);
5647
+ } else {
5648
+ // The API always runs the authentication inside the callback (PAYRAC-2480). A trusted
5649
+ // message without the outcome means an outdated API — fail, never verify via /process.
5650
+ _this2.finishChallengeLifecycle();
5651
+ _this2.eventEmitter.emit(GPAYMENTS_EVENT.ERROR, _this2.parseError({
5652
+ message: 'Standalone 3DS authentication outcome missing from the callback message'
5653
+ }, data.charge_3ds_id));
5654
+ }
5474
5655
  } else if (data.status === IFRAME_STATUS.TIMEOUT || data.status === IFRAME_STATUS.INVALID) {
5475
- _this.eventEmitter.emit(GPAYMENTS_EVENT.AUTH_ERROR, _this.parseHandleResponse({
5656
+ _this2.finishChallengeLifecycle();
5657
+ _this2.eventEmitter.emit(GPAYMENTS_EVENT.AUTH_ERROR, _this2.parseHandleResponse({
5476
5658
  status: data.status
5477
5659
  }, data.charge_3ds_id));
5478
5660
  }
5479
5661
  });
5480
- this.iFrameEvent.on(EVENT$4.CHARGE_AUTH_SUCCESS, widgetId, function (data) {
5481
- _this.processResult(data.charge_3ds_id);
5662
+ this.iFrameEvent.on(EVENT$4.CHARGE_AUTH_SUCCESS, widgetId, function (data, origin) {
5663
+ // the callback iframe reached Paydock with `AuthResultReady`: the issuer has answered. Announce it
5664
+ // before the outcome so merchants always see completed → terminal, whichever transport won.
5665
+ _this2.emitChallengeCompleted(data.charge_3ds_id, 'callback');
5666
+ if (_this2.hasChainedOutcome(data, origin)) {
5667
+ // the API already fetched the challenge result inside the callback: no GET /handle needed
5668
+ _this2.handleResultOutcome({
5669
+ status: data.process_status,
5670
+ result: data.result,
5671
+ error: data.error
5672
+ }, data.charge_3ds_id);
5673
+ } else {
5674
+ _this2.processResult(data.charge_3ds_id);
5675
+ }
5482
5676
  });
5483
5677
  }
5678
+ /**
5679
+ * Two-step mode: run the deferred authentication (`POST /standalone-3ds/process`). Never throws: runs now if
5680
+ * the deferred message already arrived, otherwise as soon as it does; a no-op on one-step charges, repeated
5681
+ * calls or after a terminal outcome. The outcome flows through the usual events.
5682
+ */
5683
+ }, {
5684
+ key: "authenticate",
5685
+ value: function authenticate() {
5686
+ if (this.authenticateRequested) {
5687
+ return;
5688
+ }
5689
+ this.authenticateRequested = true;
5690
+ if (this.deferredCharge3dsId !== null) {
5691
+ this.performAuthentication(this.deferredCharge3dsId);
5692
+ }
5693
+ }
5694
+ /**
5695
+ * Calls `/process` once per instance (reachable from both the deferred message and `authenticate()`). The
5696
+ * response has the callback-outcome shape, so the challenge / decoupled / result legs are shared with
5697
+ * one-step. A rejection surfaces as `error`, with the same error object shape as the one-step callback
5698
+ * (`toOutcomeError`). An API answer (stale session, rejected authentication…) is terminal. The first transport
5699
+ * failure is not: the AReq may have run with its response lost, so the latches are reset and the merchant may
5700
+ * call `authenticate()` once more — `/process` is idempotent and replays the stored outcome. A failure of that
5701
+ * retry, of any kind, is final.
5702
+ */
5703
+ }, {
5704
+ key: "performAuthentication",
5705
+ value: function performAuthentication(charge3dsId) {
5706
+ var _this3 = this;
5707
+ if (this.authenticationStarted) {
5708
+ return;
5709
+ }
5710
+ this.authenticationStarted = true;
5711
+ this.api.charge().standalone3dsProcess({
5712
+ charge_3ds_id: charge3dsId
5713
+ }).then(function (authenticationResult) {
5714
+ return _this3.handleAuthenticationOutcome(authenticationResult, charge3dsId);
5715
+ }, function (err) {
5716
+ if (isTransportFailure(err) && !_this3.authenticationRetryUsed) {
5717
+ _this3.authenticationRetryUsed = true;
5718
+ _this3.authenticateRequested = false;
5719
+ _this3.authenticationStarted = false;
5720
+ } else {
5721
+ _this3.resultRead = true;
5722
+ _this3.finishChallengeLifecycle();
5723
+ }
5724
+ _this3.eventEmitter.emit(GPAYMENTS_EVENT.ERROR, _this3.parseError(toOutcomeError(err), charge3dsId));
5725
+ });
5726
+ }
5727
+ /**
5728
+ * A message carries a processed outcome only when it has `process_status` AND comes from a trusted
5729
+ * origin. Used by the result leg: an untrusted `process_status` is never acted upon — the SDK falls
5730
+ * back to `GET /handle` (still needed for decoupled challenges / polling). The authentication leg
5731
+ * ignores untrusted senders outright, before this check.
5732
+ */
5733
+ }, {
5734
+ key: "hasChainedOutcome",
5735
+ value: function hasChainedOutcome(data, origin) {
5736
+ return typeof data.process_status === 'string' && this.isTrustedOrigin(origin);
5737
+ }
5738
+ }, {
5739
+ key: "isTrustedOrigin",
5740
+ value: function isTrustedOrigin(origin) {
5741
+ // 'null' is the serialized opaque origin (e.g. a sandboxed iframe) — never trust it, even if a
5742
+ // misconfigured env URL were ever to put the literal string 'null' into the trusted list
5743
+ return typeof origin === 'string' && origin !== 'null' && this.trustedOrigins.includes(origin);
5744
+ }
5745
+ }, {
5746
+ key: "removeIframes",
5747
+ value: function removeIframes() {
5748
+ var _a, _b;
5749
+ this.clearChallengeLoadedTimer();
5750
+ (_a = this.iFrameAuthorization) === null || _a === void 0 ? void 0 : _a.remove();
5751
+ (_b = this.iFrameSecondaryUrl) === null || _b === void 0 ? void 0 : _b.remove();
5752
+ }
5753
+ }, {
5754
+ key: "clearChallengeLoadedTimer",
5755
+ value: function clearChallengeLoadedTimer() {
5756
+ if (this.challengeLoadedTimer !== null) {
5757
+ clearTimeout(this.challengeLoadedTimer);
5758
+ this.challengeLoadedTimer = null;
5759
+ }
5760
+ }
5761
+ /**
5762
+ * Challenge iframe lifecycle (`chargeAuthChallengeLoaded`).
5763
+ *
5764
+ * The challenge iframe is cross-origin, but its `load` event still fires when the ACS page finishes loading:
5765
+ * the first one means the page painted (merchants can drop their loader). A fallback timer
5766
+ * (`armChallengeLoadedFallback`) guarantees `chargeAuthChallengeLoaded` within `CHALLENGE_LOADED_FALLBACK_MS`
5767
+ * even if no `load` ever fires; a late paint after that is simply ignored. Later `load`s are NOT a usable
5768
+ * signal for "the shopper submitted": the post-submit hops are HTTP redirects and self-submitting pages whose
5769
+ * loads abort, and the final callback page posts its message before its own `load` — so the post-submit
5770
+ * moment is `chargeAuthChallengeCompleted`, driven by the `secondary_url` result monitor instead.
5771
+ *
5772
+ * Nothing is emitted once the flow reached a terminal outcome (`finishChallengeLifecycle`), and a
5773
+ * decoupled challenge (no challenge iframe) never emits `chargeAuthChallengeLoaded`.
5774
+ *
5775
+ * Resets the per-challenge state and returns the `load` handler to attach to the challenge iframe.
5776
+ */
5777
+ }, {
5778
+ key: "startChallengeLifecycle",
5779
+ value: function startChallengeLifecycle(charge3dsId) {
5780
+ var _this4 = this;
5781
+ this.clearChallengeLoadedTimer();
5782
+ this.challengeLoadedEmitted = false;
5783
+ this.challengeCompletedEmitted = false;
5784
+ this.challengeLifecycleClosed = false;
5785
+ return function () {
5786
+ var _a;
5787
+ if (_this4.challengeLifecycleClosed || _this4.challengeLoadedEmitted || !((_a = _this4.iFrameAuthorization) === null || _a === void 0 ? void 0 : _a.isExist())) {
5788
+ return;
5789
+ }
5790
+ _this4.clearChallengeLoadedTimer();
5791
+ _this4.emitChallengeLoaded(charge3dsId, 'load');
5792
+ };
5793
+ }
5794
+ /**
5795
+ * Arms the `chargeAuthChallengeLoaded` fallback — only when a challenge iframe actually exists (the
5796
+ * container may be gone, or `IFrame.load` may have no-op'd on an already-present iframe).
5797
+ */
5798
+ }, {
5799
+ key: "armChallengeLoadedFallback",
5800
+ value: function armChallengeLoadedFallback(charge3dsId) {
5801
+ var _this5 = this;
5802
+ var _a;
5803
+ if (!((_a = this.iFrameAuthorization) === null || _a === void 0 ? void 0 : _a.isExist())) {
5804
+ return;
5805
+ }
5806
+ this.challengeLoadedTimer = setTimeout(function () {
5807
+ _this5.challengeLoadedTimer = null;
5808
+ if (_this5.challengeLifecycleClosed || _this5.challengeLoadedEmitted) {
5809
+ return;
5810
+ }
5811
+ _this5.emitChallengeLoaded(charge3dsId, 'timeout');
5812
+ }, CHALLENGE_LOADED_FALLBACK_MS);
5813
+ }
5814
+ }, {
5815
+ key: "emitChallengeLoaded",
5816
+ value: function emitChallengeLoaded(charge3dsId, reason) {
5817
+ this.challengeLoadedEmitted = true;
5818
+ this.eventEmitter.emit(GPAYMENTS_EVENT.CHALLENGE_LOADED, {
5819
+ charge_3ds_id: charge3dsId,
5820
+ reason: reason
5821
+ });
5822
+ }
5823
+ /**
5824
+ * `chargeAuthChallengeCompleted` — once per authentication, never after a terminal event. Idempotent so the
5825
+ * poll and the callback message can both call it: the first transport to notice the issuer's result wins.
5826
+ */
5827
+ }, {
5828
+ key: "emitChallengeCompleted",
5829
+ value: function emitChallengeCompleted(charge3dsId, source) {
5830
+ if (this.challengeLifecycleClosed || this.challengeCompletedEmitted) {
5831
+ return;
5832
+ }
5833
+ this.challengeCompletedEmitted = true;
5834
+ this.eventEmitter.emit(GPAYMENTS_EVENT.CHALLENGE_COMPLETED, {
5835
+ charge_3ds_id: charge3dsId,
5836
+ source: source
5837
+ });
5838
+ }
5839
+ /** Call before emitting any terminal event: no lifecycle event may follow it, and the result poll stops. */
5840
+ }, {
5841
+ key: "finishChallengeLifecycle",
5842
+ value: function finishChallengeLifecycle() {
5843
+ this.clearChallengeLoadedTimer();
5844
+ this.stopResultPolling();
5845
+ this.challengeLifecycleClosed = true;
5846
+ }
5847
+ }, {
5848
+ key: "stopResultPolling",
5849
+ value: function stopResultPolling() {
5850
+ if (this.resultPollTimer !== null) {
5851
+ clearTimeout(this.resultPollTimer);
5852
+ this.resultPollTimer = null;
5853
+ }
5854
+ }
5484
5855
  }, {
5485
5856
  key: "parseResultData",
5486
5857
  value: function parseResultData(_ref2, charge3dsId) {
@@ -5514,24 +5885,49 @@ var GPaymentsService = /*#__PURE__*/function () {
5514
5885
  }, {
5515
5886
  key: "processResult",
5516
5887
  value: function processResult(charge3dsId) {
5517
- var _this2 = this;
5888
+ var _this6 = this;
5518
5889
  if (this.resultRead) {
5519
5890
  return;
5520
5891
  }
5521
5892
  this.resultRead = true;
5893
+ this.finishChallengeLifecycle();
5522
5894
  this.api.charge().standalone3dsHandle().then(function (result) {
5523
5895
  var _a;
5524
- _this2.iFrameAuthorization.remove();
5525
- (_a = _this2.iFrameSecondaryUrl) === null || _a === void 0 ? void 0 : _a.remove();
5896
+ _this6.iFrameAuthorization.remove();
5897
+ (_a = _this6.iFrameSecondaryUrl) === null || _a === void 0 ? void 0 : _a.remove();
5526
5898
  if (result.status === PROCESS_STANDALONE_3DS_STATUS.SUCCESS) {
5527
- _this2.eventEmitter.emit(GPAYMENTS_EVENT.AUTH_SUCCESS, _this2.parseResultData(result, charge3dsId));
5899
+ _this6.eventEmitter.emit(GPAYMENTS_EVENT.AUTH_SUCCESS, _this6.parseResultData(result, charge3dsId));
5528
5900
  } else {
5529
- _this2.eventEmitter.emit(GPAYMENTS_EVENT.AUTH_ERROR, _this2.parseResultData(result, charge3dsId));
5901
+ _this6.eventEmitter.emit(GPAYMENTS_EVENT.AUTH_ERROR, _this6.parseResultData(result, charge3dsId));
5530
5902
  }
5531
5903
  }, function (err) {
5532
- _this2.eventEmitter.emit(GPAYMENTS_EVENT.ERROR, _this2.parseError(err, charge3dsId));
5904
+ _this6.eventEmitter.emit(GPAYMENTS_EVENT.ERROR, _this6.parseError(err, charge3dsId));
5533
5905
  });
5534
5906
  }
5907
+ /** Final (post-challenge) outcome delivered by the API through the callback message. */
5908
+ }, {
5909
+ key: "handleResultOutcome",
5910
+ value: function handleResultOutcome(_ref4, charge3dsId) {
5911
+ var status = _ref4.status,
5912
+ error = _ref4.error;
5913
+ if (this.resultRead) {
5914
+ return;
5915
+ }
5916
+ this.resultRead = true;
5917
+ this.finishChallengeLifecycle();
5918
+ this.removeIframes();
5919
+ if (status === PROCESS_STANDALONE_3DS_STATUS.SUCCESS) {
5920
+ this.eventEmitter.emit(GPAYMENTS_EVENT.AUTH_SUCCESS, this.parseResultData({
5921
+ status: status
5922
+ }, charge3dsId));
5923
+ } else if (status === PROCESS_STANDALONE_3DS_STATUS.ERROR) {
5924
+ this.eventEmitter.emit(GPAYMENTS_EVENT.ERROR, this.parseError(error, charge3dsId));
5925
+ } else {
5926
+ this.eventEmitter.emit(GPAYMENTS_EVENT.AUTH_ERROR, this.parseResultData({
5927
+ status: status
5928
+ }, charge3dsId));
5929
+ }
5930
+ }
5535
5931
  }, {
5536
5932
  key: "externalAPI",
5537
5933
  value: function externalAPI(method, url) {
@@ -5545,60 +5941,106 @@ var GPaymentsService = /*#__PURE__*/function () {
5545
5941
  reject(error);
5546
5942
  }
5547
5943
  };
5944
+ // without these a failed request left the promise pending forever and the poll silently dead
5945
+ var fail = function fail() {
5946
+ return reject(new ResultMonitorTransportError());
5947
+ };
5948
+ request.onerror = fail;
5949
+ request.onabort = fail;
5950
+ request.ontimeout = fail;
5548
5951
  request.send();
5549
5952
  });
5550
5953
  }
5954
+ /**
5955
+ * Polls the GPayments result monitor (`secondary_url`) until `AuthResultReady`. It is the primary source of
5956
+ * `chargeAuthChallengeCompleted` (the monitor flips as soon as the issuer's RReq reaches the 3DS Server) and
5957
+ * the fallback transport for the outcome itself (`GET /handle`) when the callback message never arrives —
5958
+ * the only transport for decoupled challenges. Stops once an outcome was obtained or the lifecycle closed.
5959
+ * Transport failures are retried on the same cadence; an unparsable body or an unknown event is fatal.
5960
+ */
5551
5961
  }, {
5552
5962
  key: "doPolling",
5553
- value: function doPolling(url, charge3dsId) {
5554
- var _this3 = this;
5963
+ value: function doPolling(url, charge3dsId, intervalMs) {
5964
+ var _this7 = this;
5965
+ this.stopResultPolling();
5966
+ if (this.resultRead || this.challengeLifecycleClosed) {
5967
+ return;
5968
+ }
5969
+ var schedule = function schedule() {
5970
+ _this7.resultPollTimer = setTimeout(function () {
5971
+ _this7.resultPollTimer = null;
5972
+ _this7.doPolling(url, charge3dsId, intervalMs);
5973
+ }, intervalMs);
5974
+ };
5555
5975
  this.externalAPI('GET', url).then(function (data) {
5976
+ if (_this7.resultRead || _this7.challengeLifecycleClosed) {
5977
+ return;
5978
+ }
5556
5979
  if (!data.event || data.event === 'AuthResultNotReady') {
5557
- setTimeout(function () {
5558
- _this3.doPolling(url, charge3dsId);
5559
- }, 2000);
5980
+ schedule();
5560
5981
  } else if (data.event === 'AuthResultReady') {
5561
- _this3.processResult(charge3dsId);
5982
+ _this7.emitChallengeCompleted(charge3dsId, 'poll');
5983
+ _this7.processResult(charge3dsId);
5562
5984
  } else {
5563
5985
  throw new Error('Event not supported');
5564
5986
  }
5565
5987
  })["catch"](function (err) {
5566
- return _this3.eventEmitter.emit(GPAYMENTS_EVENT.ERROR, _this3.parseError(err, charge3dsId));
5988
+ if (err instanceof ResultMonitorTransportError) {
5989
+ if (!_this7.resultRead && !_this7.challengeLifecycleClosed) {
5990
+ schedule();
5991
+ }
5992
+ return;
5993
+ }
5994
+ _this7.finishChallengeLifecycle();
5995
+ _this7.eventEmitter.emit(GPAYMENTS_EVENT.ERROR, _this7.parseError(err, charge3dsId));
5567
5996
  });
5568
5997
  }
5998
+ /**
5999
+ * Branches on the authentication outcome — delivered by the API through the callback message
6000
+ * (`process_status` → `status`) or, in two-step mode, returned by `/process` (same shape).
6001
+ */
5569
6002
  }, {
5570
- key: "performAuthentication",
5571
- value: function performAuthentication(_ref4) {
5572
- var _this4 = this;
5573
- var charge_3ds_id = _ref4.charge_3ds_id;
5574
- var _a;
5575
- this.iFrameAuthorization.remove();
5576
- (_a = this.iFrameSecondaryUrl) === null || _a === void 0 ? void 0 : _a.remove();
5577
- this.api.charge().standalone3dsProcess({
5578
- charge_3ds_id: charge_3ds_id
5579
- }).then(function (authenticationResult) {
5580
- var _a, _b;
5581
- if (authenticationResult.status === AUTHENTICATION_STATUS.SUCCESS) {
5582
- _this4.eventEmitter.emit(GPAYMENTS_EVENT.AUTH_SUCCESS, _this4.parseHandleResponse(authenticationResult, charge_3ds_id));
5583
- } else if (authenticationResult.status === AUTHENTICATION_STATUS.PENDING) {
5584
- if ((_a = authenticationResult === null || authenticationResult === void 0 ? void 0 : authenticationResult.result) === null || _a === void 0 ? void 0 : _a.challenge) {
5585
- _this4.eventEmitter.emit(GPAYMENTS_EVENT.CHALLENGE, _this4.parseHandleResponse(authenticationResult, charge_3ds_id));
5586
- _this4.initializeIFrames(authenticationResult.result.challenge_url, undefined, 'Authentication Challenge', false);
5587
- if (authenticationResult.result.secondary_url) {
5588
- _this4.doPolling(authenticationResult.result.secondary_url, charge_3ds_id);
5589
- }
5590
- } else if ((_b = authenticationResult === null || authenticationResult === void 0 ? void 0 : authenticationResult.result) === null || _b === void 0 ? void 0 : _b.decoupled_challenge) {
5591
- _this4.eventEmitter.emit(GPAYMENTS_EVENT.DECOUPLED, _this4.parseHandleResponse(authenticationResult, charge_3ds_id));
5592
- if (authenticationResult.result.secondary_url) {
5593
- _this4.doPolling(authenticationResult.result.secondary_url, charge_3ds_id);
5594
- }
6003
+ key: "handleAuthenticationOutcome",
6004
+ value: function handleAuthenticationOutcome(authenticationResult, charge_3ds_id) {
6005
+ var _a, _b, _c;
6006
+ if (authenticationResult.status !== AUTHENTICATION_STATUS.PENDING) {
6007
+ // terminal outcome — a later `chargeAuthSuccess` message or poll must not emit a second result
6008
+ this.resultRead = true;
6009
+ }
6010
+ if (authenticationResult.status === AUTHENTICATION_STATUS.SUCCESS) {
6011
+ this.finishChallengeLifecycle();
6012
+ this.eventEmitter.emit(GPAYMENTS_EVENT.AUTH_SUCCESS, this.parseHandleResponse(authenticationResult, charge_3ds_id));
6013
+ } else if (authenticationResult.status === AUTHENTICATION_STATUS.PENDING) {
6014
+ if ((_a = authenticationResult === null || authenticationResult === void 0 ? void 0 : authenticationResult.result) === null || _a === void 0 ? void 0 : _a.challenge) {
6015
+ this.eventEmitter.emit(GPAYMENTS_EVENT.CHALLENGE, this.parseHandleResponse(authenticationResult, charge_3ds_id));
6016
+ var onChallengeLoad = this.startChallengeLifecycle(charge_3ds_id);
6017
+ this.initializeIFrames(authenticationResult.result.challenge_url, undefined, 'Authentication Challenge', false, onChallengeLoad);
6018
+ this.armChallengeLoadedFallback(charge_3ds_id);
6019
+ if (authenticationResult.result.secondary_url) {
6020
+ this.doPolling(authenticationResult.result.secondary_url, charge_3ds_id, CHALLENGE_RESULT_POLL_INTERVAL_MS);
6021
+ }
6022
+ } else if ((_b = authenticationResult === null || authenticationResult === void 0 ? void 0 : authenticationResult.result) === null || _b === void 0 ? void 0 : _b.decoupled_challenge) {
6023
+ this.challengeCompletedEmitted = false;
6024
+ this.challengeLifecycleClosed = false;
6025
+ this.eventEmitter.emit(GPAYMENTS_EVENT.DECOUPLED, this.parseHandleResponse(authenticationResult, charge_3ds_id));
6026
+ if (authenticationResult.result.secondary_url) {
6027
+ this.doPolling(authenticationResult.result.secondary_url, charge_3ds_id, DECOUPLED_RESULT_POLL_INTERVAL_MS);
5595
6028
  }
5596
6029
  } else {
5597
- return _this4.eventEmitter.emit(GPAYMENTS_EVENT.AUTH_ERROR, _this4.parseHandleResponse(authenticationResult, charge_3ds_id));
5598
- }
5599
- }, function (err) {
5600
- _this4.eventEmitter.emit(GPAYMENTS_EVENT.ERROR, _this4.parseError(err, charge_3ds_id));
5601
- });
6030
+ // pending but nothing to run (no challenge delivered) — fail rather than hang silently
6031
+ // with the iframes already removed and no further message ever coming
6032
+ this.resultRead = true;
6033
+ this.finishChallengeLifecycle();
6034
+ this.eventEmitter.emit(GPAYMENTS_EVENT.AUTH_ERROR, this.parseHandleResponse(authenticationResult, charge_3ds_id));
6035
+ }
6036
+ } else if (authenticationResult.status === AUTHENTICATION_STATUS.ERROR) {
6037
+ // the API rejected the authentication, or could not run it inside the callback
6038
+ this.finishChallengeLifecycle();
6039
+ this.eventEmitter.emit(GPAYMENTS_EVENT.ERROR, this.parseError((_c = authenticationResult.error) !== null && _c !== void 0 ? _c : authenticationResult, charge_3ds_id));
6040
+ } else {
6041
+ this.finishChallengeLifecycle();
6042
+ this.eventEmitter.emit(GPAYMENTS_EVENT.AUTH_ERROR, this.parseHandleResponse(authenticationResult, charge_3ds_id));
6043
+ }
5602
6044
  }
5603
6045
  }]);
5604
6046
  }();
@@ -5625,16 +6067,61 @@ var Standalone3dsService = /*#__PURE__*/function () {
5625
6067
  api.setEnv(this.env, this.alias);
5626
6068
  switch (tokenData.service_type) {
5627
6069
  case STANDALONE_3DS_GATEWAYS.GPAYMENTS:
5628
- new GPaymentsService(this.container, api, this.eventEmitter).load(tokenData, options.title);
6070
+ this.gpaymentsService = new GPaymentsService(this.container, api, this.eventEmitter, this.getTrustedMessageOrigins(api));
6071
+ this.gpaymentsService.load(tokenData, options.title);
5629
6072
  break;
5630
6073
  }
5631
6074
  }
6075
+ /**
6076
+ * Two-step mode: forwards `Canvas3ds.authenticate()` to the provider flow. Calling it before `load()` is an
6077
+ * integration bug (nothing to authenticate yet): it emits `error` and is not remembered.
6078
+ */
6079
+ }, {
6080
+ key: "authenticate",
6081
+ value: function authenticate(token) {
6082
+ if (!this.gpaymentsService) {
6083
+ this.eventEmitter.emit(GPAYMENTS_EVENT.ERROR, {
6084
+ charge_3ds_id: Standalone3dsService.readCharge3dsId(token),
6085
+ error: {
6086
+ message: 'Canvas3ds.authenticate() called before load()'
6087
+ }
6088
+ });
6089
+ return;
6090
+ }
6091
+ this.gpaymentsService.authenticate();
6092
+ }
6093
+ /** `charge_3ds_id` from the standalone 3DS charge token, `undefined` when it cannot be decoded. */
6094
+ }, {
6095
+ key: "getTrustedMessageOrigins",
6096
+ value:
6097
+ /**
6098
+ * Origins allowed to deliver a processed 3DS outcome to the page via postMessage: the Paydock API
6099
+ * (inline callback page) and the Paydock widget (remote-app landing page), for the current env/alias.
6100
+ */
6101
+ function getTrustedMessageOrigins(api) {
6102
+ var origins = [api.getApiOrigin()];
6103
+ try {
6104
+ var widgetEnv = new Env(WIDGET_URL, this.env);
6105
+ widgetEnv.setEnv(this.env, this.alias);
6106
+ origins.push(ApiBase.toOrigin(widgetEnv.getConf().url));
6107
+ } catch (_a) {
6108
+ // widget origin unknown for this env — the API origin is enough
6109
+ }
6110
+ return origins;
6111
+ }
5632
6112
  }, {
5633
6113
  key: "setEnv",
5634
6114
  value: function setEnv(env, alias) {
5635
6115
  this.env = env;
5636
6116
  this.alias = alias;
5637
6117
  }
6118
+ }], [{
6119
+ key: "readCharge3dsId",
6120
+ value: function readCharge3dsId(token) {
6121
+ var _a;
6122
+ var parsedToken = AccessToken.validateJWT(token);
6123
+ return parsedToken ? (_a = AccessToken.extractData(parsedToken.body)) === null || _a === void 0 ? void 0 : _a.charge_3ds_id : undefined;
6124
+ }
5638
6125
  }]);
5639
6126
  }();
5640
6127
 
@@ -5797,11 +6284,14 @@ var TOKEN_FORMAT$2;
5797
6284
  * @const STANDALONE_3DS_EVENT
5798
6285
  *
5799
6286
  * @type {object}
5800
- * @param {string} CHARGE_AUTH_SUCCESS=chargeAuthSuccess
5801
- * @param {string} CHARGE_AUTH_REJECT=chargeAuthReject
5802
- * @param {string} CHARGE_AUTH_DECOUPLED=chargeAuthDecoupled
5803
- * @param {string} CHARGE_AUTH_CHALLENGE=chargeAuthChallenge
5804
- * @param {string} CHARGE_AUTH_INFO=chargeAuthInfo
6287
+ * @param {string} AUTH_SUCCESS=chargeAuthSuccess
6288
+ * @param {string} AUTH_ERROR=chargeAuthReject
6289
+ * @param {string} DECOUPLED=chargeAuthDecoupled
6290
+ * @param {string} CHALLENGE=chargeAuthChallenge
6291
+ * @param {string} READY=chargeAuthReady - two-step mode only: fingerprint done, waiting for `authenticate()`
6292
+ * @param {string} CHALLENGE_LOADED=chargeAuthChallengeLoaded - challenge iframe painted (first `load`, or 2.5 s fallback); reveal your challenge UI here
6293
+ * @param {string} CHALLENGE_COMPLETED=chargeAuthChallengeCompleted - the shopper finished the challenge (issuer result delivered to the 3DS Server); cover the challenge iframe with your own loader until the terminal event
6294
+ * @param {string} INFO=chargeAuthInfo
5805
6295
  * @param {string} ERROR=error
5806
6296
  */
5807
6297
  /**
@@ -5928,6 +6418,39 @@ var Canvas3ds = /*#__PURE__*/function () {
5928
6418
  });
5929
6419
  }
5930
6420
  }
6421
+ /**
6422
+ * Two-step mode only (`_3ds.authentication_flow: 'two_step'`): runs the authentication the API deferred after the
6423
+ * fingerprint. Call it when the shopper confirms (e.g. presses "Pay"); the outcome arrives through the
6424
+ * usual events (`chargeAuthChallenge`, `chargeAuthSuccess`, `chargeAuthReject`, `error`).
6425
+ *
6426
+ * Never throws. After `chargeAuthReady` the authentication starts at once; before it, the call is
6427
+ * remembered and runs as soon as the fingerprint completes. One-step charges, repeated calls and calls after
6428
+ * a terminal outcome are no-ops. For inbuilt 3DS (`html` / `url`) tokens it is a silent no-op: no event is
6429
+ * emitted at all, so do not wait for `error` there. Calling it before `load()` is an integration error: it emits `error` and
6430
+ * is not remembered. If `/process` could not be reached (`error` with `error_code` `service_unreachable` /
6431
+ * `aborted`), `authenticate()` may be called once more; any other `error`, and any failure of that retry,
6432
+ * is final.
6433
+ * No client-side deadline: a stale 3DS Server session surfaces as `error` / `chargeAuthReject`.
6434
+ *
6435
+ * @example
6436
+ * const widget = new Canvas3ds('#widget', token); // charge created with _3ds.authentication_flow: 'two_step'
6437
+ * widget.on('chargeAuthReady', () => payButton.disabled = false);
6438
+ * widget.on('chargeAuthChallenge', () => showChallengeModal());
6439
+ * widget.on('chargeAuthSuccess', (data) => completePayment(data.charge_3ds_id));
6440
+ * widget.on('chargeAuthReject', () => showDeclined());
6441
+ * widget.on('error', () => showError());
6442
+ * widget.load(); // fingerprint runs while the shopper reviews the order
6443
+ *
6444
+ * payButton.addEventListener('click', () => widget.authenticate());
6445
+ */
6446
+ }, {
6447
+ key: "authenticate",
6448
+ value: function authenticate() {
6449
+ if (this.token.format !== TOKEN_FORMAT$2.STANDALONE_3DS) {
6450
+ return;
6451
+ }
6452
+ this.standalone3dsService.authenticate(this.token.content);
6453
+ }
5931
6454
  /**
5932
6455
  * Using this method you can hide widget after load
5933
6456
  * @param {boolean} [saveSize=false] - using this param you can save iframe's size
@@ -6183,6 +6706,24 @@ var SUPPORTED_CARD_TYPES = {
6183
6706
  EFTPOS_WHITE: 'eftpos_white',
6184
6707
  UNIONPAY: 'unionpay'
6185
6708
  };
6709
+ /**
6710
+ * Card schemes the widget is able to resolve from a card number, and therefore the only values
6711
+ * `setAllowedCardSchemes` accepts. This is a separate vocabulary from SUPPORTED_CARD_TYPES, which drives the
6712
+ * card icons: it omits the icon-only variants `visa_white` / `eftpos_white`, and the `laser` / `maestro`
6713
+ * identifiers the widget can never resolve — allowing those would be configuration that can never match.
6714
+ */
6715
+ var CARD_SCHEME = {
6716
+ AMEX: 'amex',
6717
+ AUSBC: 'ausbc',
6718
+ DINERS: 'diners',
6719
+ DISCOVER: 'discover',
6720
+ EFTPOS: 'eftpos',
6721
+ JAPCB: 'japcb',
6722
+ MASTERCARD: 'mastercard',
6723
+ SOLO: 'solo',
6724
+ UNIONPAY: 'unionpay',
6725
+ VISA: 'visa'
6726
+ };
6186
6727
  var SUPPORTED_CHECKOUT_META_COLLECTION = [].concat([
6187
6728
  // Paypal
6188
6729
  'brand_name', 'cart_border_color', 'reference', 'email', 'hdr_img', 'logo_img', 'pay_flow_color', 'first_name', 'last_name', 'address_line', 'address_line2', 'address_city', 'address_state', 'address_postcode', 'address_country', 'phone', 'hide_shipping_address'], [
@@ -8367,7 +8908,7 @@ var AfterPayWalletService = /*#__PURE__*/function (_WalletService) {
8367
8908
  }]);
8368
8909
  }(WalletService);
8369
8910
 
8370
- /*! @license DOMPurify 3.4.11 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.11/LICENSE */
8911
+ /*! @license DOMPurify 3.4.13 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.13/LICENSE */
8371
8912
 
8372
8913
  var purify_cjs;
8373
8914
  var hasRequiredPurify_cjs;
@@ -8687,7 +9228,7 @@ function requirePurify_cjs () {
8687
9228
  const text = freeze(['#text']);
8688
9229
 
8689
9230
  const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'command', 'commandfor', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns']);
8690
- const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'mask-type', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
9231
+ const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dominant-baseline', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'mask-type', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-orientation', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
8691
9232
  const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnalign', 'columnlines', 'columnspacing', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lquote', 'lspace', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);
8692
9233
  const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
8693
9234
 
@@ -8800,7 +9341,7 @@ function requirePurify_cjs () {
8800
9341
  function createDOMPurify() {
8801
9342
  let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
8802
9343
  const DOMPurify = root => createDOMPurify(root);
8803
- DOMPurify.version = '3.4.11';
9344
+ DOMPurify.version = '3.4.13';
8804
9345
  DOMPurify.removed = [];
8805
9346
  if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
8806
9347
  // Not running in a browser, provide a factory function
@@ -8831,6 +9372,7 @@ function requirePurify_cjs () {
8831
9372
  const getAttributes = lookupGetter(ElementPrototype, 'attributes');
8832
9373
  const getNodeType = Node && Node.prototype ? lookupGetter(Node.prototype, 'nodeType') : null;
8833
9374
  const getNodeName = Node && Node.prototype ? lookupGetter(Node.prototype, 'nodeName') : null;
9375
+ const getOwnerDocument = Node && Node.prototype ? lookupGetter(Node.prototype, 'ownerDocument') : null;
8834
9376
  // As per issue #47, the web-components registry is inherited by a
8835
9377
  // new document created via createHTMLDocument. As per the spec
8836
9378
  // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
@@ -9482,6 +10024,13 @@ function requirePurify_cjs () {
9482
10024
  * @param root the in-place root to empty
9483
10025
  */
9484
10026
  const _neutralizeRoot = function _neutralizeRoot(root) {
10027
+ /* Strip every disallowed attribute (on* handlers included) off the whole
10028
+ subtree BEFORE detaching anything. Detaching first would hand back
10029
+ handler-bearing originals (e.g. an already-loading `<img onerror>`)
10030
+ whose queued resource event still fires in page scope after we throw.
10031
+ Clobber-safe reads; a doomed clobbered node's own attributes are
10032
+ irrelevant while its non-clobbered descendants are reached and scrubbed. */
10033
+ _neutralizeSubtree(root);
9485
10034
  const childNodes = getChildNodes(root);
9486
10035
  if (childNodes) {
9487
10036
  const snapshot = [];
@@ -9609,6 +10158,82 @@ function requirePurify_cjs () {
9609
10158
  }
9610
10159
  }
9611
10160
  };
10161
+ /**
10162
+ * _neutralizePatchLinkage
10163
+ *
10164
+ * IN_PLACE entry pre-pass (declarative-partial-updates / streaming
10165
+ * hardening, https://github.com/WICG/declarative-partial-updates).
10166
+ *
10167
+ * The main walk strips patch linkage (`for`/`patchsrc`) and removes range
10168
+ * markers (PIs / markup comments) node-by-node, in document order, AS it
10169
+ * reaches each node. On a live in-place root that leaves a window: from the
10170
+ * moment the root is connected until the walk arrives at a given node, that
10171
+ * node's linkage is live. A patch applied on connection/stream can fire as
10172
+ * a microtask during the walk and inject or teleport an unsanitized DOM
10173
+ * range into a region the iterator has already passed and will not revisit,
10174
+ * so the post-return "tree is sanitized" contract is violated. Sweep the
10175
+ * whole tree once up front and sever every linkage before the walk begins,
10176
+ * closing that window.
10177
+ *
10178
+ * This CANNOT undo a patch that already fired before sanitize ran — that is
10179
+ * the irreducible "do not IN_PLACE a live-connected attacker tree" caveat —
10180
+ * but it closes everything from sanitize-start onward. Gated on SAFE_FOR_XML
10181
+ * to group with the rest of the declarative-partial-updates handling and
10182
+ * stay overridable, consistent with the codebase.
10183
+ *
10184
+ * Clobber-safe traversal (cached childNodes getter); per-node try/catch so a
10185
+ * clobbered root cannot defeat the sweep of its non-clobbered descendants.
10186
+ *
10187
+ * NOTE (pending real-Chrome confirmation, see test/declarative-patch-probe
10188
+ * .html Q1): this mirrors the existing policy of keeping `for` on
10189
+ * <label>/<output>. If the shipping feature can drive a patch through a
10190
+ * surviving `for`-on-label/output + `id` pair, this pre-pass and the
10191
+ * attribute check at _isBasicCustomElement's caller must additionally drop
10192
+ * that pair on the IN_PLACE path. Left as-is until the taxonomy is verified.
10193
+ *
10194
+ * @param root the in-place root to sweep
10195
+ */
10196
+ const _neutralizePatchLinkage = function _neutralizePatchLinkage(root) {
10197
+ if (!SAFE_FOR_XML) {
10198
+ return;
10199
+ }
10200
+ const stack = [root];
10201
+ while (stack.length > 0) {
10202
+ const node = stack.pop();
10203
+ const nodeType = getNodeType ? getNodeType(node) : node.nodeType;
10204
+ /* Remove range markers (the target side of a patch linkage): every
10205
+ processing instruction, and any markup-bearing comment. */
10206
+ if (nodeType === NODE_TYPE.processingInstruction || nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, node.data)) {
10207
+ try {
10208
+ remove(node);
10209
+ } catch (_) {
10210
+ /* Best-effort */
10211
+ }
10212
+ continue;
10213
+ }
10214
+ /* Strip patch-source attributes (the source side) off elements. */
10215
+ if (nodeType === NODE_TYPE.element) {
10216
+ const element = node;
10217
+ const lcTag = transformCaseFunc(getNodeName ? getNodeName(node) : node.nodeName);
10218
+ try {
10219
+ if (element.hasAttribute && element.hasAttribute('patchsrc')) {
10220
+ element.removeAttribute('patchsrc');
10221
+ }
10222
+ if (element.hasAttribute && element.hasAttribute('for') && lcTag !== 'label' && lcTag !== 'output') {
10223
+ element.removeAttribute('for');
10224
+ }
10225
+ } catch (_) {
10226
+ /* Clobbered removeAttribute/hasAttribute on a doomed node — ignore */
10227
+ }
10228
+ }
10229
+ const childNodes = getChildNodes(node);
10230
+ if (childNodes) {
10231
+ for (let i = childNodes.length - 1; i >= 0; --i) {
10232
+ stack.push(childNodes[i]);
10233
+ }
10234
+ }
10235
+ }
10236
+ };
9612
10237
  /**
9613
10238
  * _initDocument
9614
10239
  *
@@ -9666,7 +10291,17 @@ function requirePurify_cjs () {
9666
10291
  * @return The created NodeIterator
9667
10292
  */
9668
10293
  const _createNodeIterator = function _createNodeIterator(root) {
9669
- return createNodeIterator.call(root.ownerDocument || root, root,
10294
+ /* Read ownerDocument through the cached Node.prototype getter, never the
10295
+ direct property. HTMLFormElement has [LegacyOverrideBuiltIns], so a
10296
+ clobbering child (<input name="ownerDocument"> or a form-associated
10297
+ external input) shadows the prototype getter and makes a direct read
10298
+ return that <input>. createNodeIterator.call(<input>, ...) then throws
10299
+ "Illegal invocation", and on the IN_PLACE path that throw lands before
10300
+ the walk's fail-closed barrier - leaving the caller's live tree, with
10301
+ any already-armed handler in it, un-neutralized. The cached getter
10302
+ returns the real Document regardless of the clobber. */
10303
+ const doc = getOwnerDocument ? getOwnerDocument(root) : root.ownerDocument;
10304
+ return createNodeIterator.call(doc || root, root,
9670
10305
  // eslint-disable-next-line no-bitwise
9671
10306
  NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
9672
10307
  };
@@ -9706,7 +10341,11 @@ function requirePurify_cjs () {
9706
10341
  const _scrubTemplateExpressions2 = function _scrubTemplateExpressions(node) {
9707
10342
  var _node$querySelectorAl;
9708
10343
  node.normalize();
9709
- const walker = createNodeIterator.call(node.ownerDocument || node, node,
10344
+ /* Clobber-safe ownerDocument read, same reasoning as _createNodeIterator:
10345
+ under SAFE_FOR_TEMPLATES this runs on the live IN_PLACE root, which may
10346
+ carry a form-named-getter override of ownerDocument. */
10347
+ const doc = getOwnerDocument ? getOwnerDocument(node) : node.ownerDocument;
10348
+ const walker = createNodeIterator.call(doc || node, node,
9710
10349
  // eslint-disable-next-line no-bitwise
9711
10350
  NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_CDATA_SECTION | NodeFilter.SHOW_PROCESSING_INSTRUCTION, null);
9712
10351
  let currentNode = walker.nextNode();
@@ -9859,15 +10498,21 @@ function requirePurify_cjs () {
9859
10498
  /**
9860
10499
  * Handle a node whose tag is forbidden or not allowlisted: keep
9861
10500
  * allowed custom elements (false return exits _sanitizeElements
9862
- * early - namespace/fallback checks and the afterSanitizeElements
9863
- * hook are intentionally skipped for kept custom elements), else
9864
- * hoist content per KEEP_CONTENT and remove.
10501
+ * early - the namespace and fallback-tag removal checks are
10502
+ * intentionally skipped for kept custom elements), else hoist
10503
+ * content per KEEP_CONTENT and remove.
10504
+ *
10505
+ * A kept custom element is the ONLY case in which this function
10506
+ * returns false, so the caller uses that return value to run the
10507
+ * afterSanitizeElements hook on the kept element and keep the
10508
+ * element-hook lifecycle consistent with normal allowlisted
10509
+ * elements (GHSA-c2j3-45gr-mqc4).
9865
10510
  *
9866
10511
  * @param currentNode the disallowed node
9867
10512
  * @param tagName the node's transformCaseFunc'd tag name
9868
10513
  * @return true if the node was removed, false if kept
9869
10514
  */
9870
- const _sanitizeDisallowedNode = function _sanitizeDisallowedNode(currentNode, tagName) {
10515
+ const _sanitizeDisallowedNode = function _sanitizeDisallowedNode(currentNode, tagName, root) {
9871
10516
  /* Check if we have a custom element to handle */
9872
10517
  if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
9873
10518
  if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
@@ -9890,27 +10535,26 @@ function requirePurify_cjs () {
9890
10535
  const childNodes = getChildNodes(currentNode);
9891
10536
  if (childNodes && parentNode) {
9892
10537
  const childCount = childNodes.length;
9893
- /* In-place: hoist the *original* children so the iterator visits
9894
- and sanitises them through the same allowlist pass as every other
9895
- node. The caller built the tree in the live document, so the
9896
- originals carry already-queued resource events (`<img onerror>`,
9897
- `<video>`/`<audio>` error, lazy/`onload`, …); cloning would leave
9898
- those originals detached but still armed, firing in page scope
9899
- while the returned tree looked clean. Moving is safe in-place: the
9900
- root is pre-validated as an allowed tag and so is never the node
9901
- being removed, which keeps `parentNode` inside the iterator root
9902
- and the relocated child inside the serialised tree.
9903
- Otherwise (string / DOM-copy paths): clone. The iterator is rooted
9904
- at — and the result serialised from — `body`, so a restrictive
9905
- ALLOWED_TAGS that removes `body` itself must leave its content in
9906
- place, which only cloning does; and those paths parse into an
9907
- inert document, so their discarded originals never had a queued
9908
- event to neutralise.
10538
+ /* Hoist by moving each child up one level rather than deep-cloning
10539
+ it. Moving transfers every descendant exactly once, so a chain of
10540
+ nested disallowed elements costs O(n) instead of the O(n^2) that
10541
+ re-cloning the shrinking subtree at each level produced; it also
10542
+ empties the removed original, so `DOMPurify.removed` no longer
10543
+ pins whole subtrees. Moving preserves the in-place guarantee too:
10544
+ an original carrying already-queued resource events (`<img
10545
+ onerror>`, `<video>`/`<audio>` error, lazy/`onload`, …) is
10546
+ relocated and sanitised rather than left detached but still armed.
10547
+ The sole case that must clone is removing the walk root itself.
10548
+ The result is serialised from the root's subtree, so a restrictive
10549
+ ALLOWED_TAGS that strips the root (`body` on the string path) must
10550
+ leave the content inside it, which only cloning does. In IN_PLACE
10551
+ the root is pre-validated as an allowed tag and so is never removed
10552
+ here, so that path always takes the move branch.
9909
10553
  `childNodes` is live; a tail-to-head walk keeps `childNodes[i]`
9910
10554
  valid whether we move (drops the trailing entry) or clone (leaves
9911
10555
  the list intact). */
9912
10556
  for (let i = childCount - 1; i >= 0; --i) {
9913
- const hoisted = IN_PLACE ? childNodes[i] : cloneNode(childNodes[i], true);
10557
+ const hoisted = currentNode === root ? cloneNode(childNodes[i], true) : childNodes[i];
9914
10558
  parentNode.insertBefore(hoisted, getNextSibling(currentNode));
9915
10559
  }
9916
10560
  }
@@ -9918,6 +10562,26 @@ function requirePurify_cjs () {
9918
10562
  _forceRemove(currentNode);
9919
10563
  return true;
9920
10564
  };
10565
+ /**
10566
+ * Fork a hook-mutable allowlist off its shared binding the first time a
10567
+ * (possibly lazily-installed) uponSanitize* hook is about to see it, so the
10568
+ * hook cannot widen the per-instance default or the setConfig binding by
10569
+ * reference and leak past the call. Returns the set unchanged once it is
10570
+ * already call-local, so repeated calls across elements are idempotent.
10571
+ *
10572
+ * @param hookList the uponSanitize* hook array for this event
10573
+ * @param set the current ALLOWED_TAGS / ALLOWED_ATTR binding
10574
+ * @param defaultSet the per-instance DEFAULT_ALLOWED_* constant
10575
+ * @param setConfigSet the captured setConfig() binding, or null
10576
+ * @return a call-local clone if a hook is present and set is still shared,
10577
+ * else set unchanged
10578
+ */
10579
+ const _forkSharedAllowlist = function _forkSharedAllowlist(hookList, set, defaultSet, setConfigSet) {
10580
+ if (hookList.length === 0) {
10581
+ return set;
10582
+ }
10583
+ return set === defaultSet || set === setConfigSet ? clone(set) : set;
10584
+ };
9921
10585
  /**
9922
10586
  * _sanitizeElements
9923
10587
  *
@@ -9927,9 +10591,20 @@ function requirePurify_cjs () {
9927
10591
  * @param currentNode to check for permission to exist
9928
10592
  * @return true if node was killed, false if left alive
9929
10593
  */
9930
- const _sanitizeElements = function _sanitizeElements(currentNode) {
10594
+ // eslint-disable-next-line complexity
10595
+ const _sanitizeElements = function _sanitizeElements(currentNode, root) {
9931
10596
  /* Execute a hook if present */
9932
10597
  _executeHooks(hooks.beforeSanitizeElements, currentNode, null);
10598
+ /* A hook may have detached the node - treat it as removed (see the
10599
+ detached-node comment after the uponSanitizeElement hook below). On
10600
+ the IN_PLACE path, neutralize the detached subtree first so a queued
10601
+ resource handler on it cannot fire in page scope after we return. */
10602
+ if (currentNode !== root && getParentNode(currentNode) === null) {
10603
+ if (IN_PLACE) {
10604
+ _neutralizeSubtree(currentNode);
10605
+ }
10606
+ return true;
10607
+ }
9933
10608
  /* Check if element is clobbered or can clobber */
9934
10609
  if (_isClobbered(currentNode)) {
9935
10610
  _forceRemove(currentNode);
@@ -9937,11 +10612,47 @@ function requirePurify_cjs () {
9937
10612
  }
9938
10613
  /* Now let's check the element's type and name */
9939
10614
  const tagName = transformCaseFunc(getNodeName ? getNodeName(currentNode) : currentNode.nodeName);
10615
+ /* Close the pre-walk clone-guard's timing gap: an uponSanitizeElement
10616
+ hook may have been installed after that guard sampled the hook arrays
10617
+ (e.g. lazily from beforeSanitizeElements), leaving ALLOWED_TAGS still
10618
+ aliasing a shared binding that a widening hook would mutate by
10619
+ reference. Fork it before exposing it to the hook. */
10620
+ ALLOWED_TAGS = _forkSharedAllowlist(hooks.uponSanitizeElement, ALLOWED_TAGS, DEFAULT_ALLOWED_TAGS, SET_CONFIG_ALLOWED_TAGS);
9940
10621
  /* Execute a hook if present */
9941
10622
  _executeHooks(hooks.uponSanitizeElement, currentNode, {
9942
10623
  tagName,
9943
10624
  allowedTags: ALLOWED_TAGS
9944
10625
  });
10626
+ /* A hook may have detached the node from the tree — a long-standing
10627
+ user pattern (issue #469; draw.io-style foreignObject filtering).
10628
+ Per the cached, unclobberable parentNode getter the node is
10629
+ genuinely out of the tree, so it can reach neither the serialized
10630
+ output nor an IN_PLACE live tree; treat it as removed and stop
10631
+ processing it. Without this guard, the unsafe-node / namespace
10632
+ checks below would call _forceRemove on a parentless node and hit
10633
+ the REPORT-3 fail-closed throw — which exists for nodes DOMPurify
10634
+ wants gone but *cannot* detach (clobbered / parentless roots), the
10635
+ opposite of a node that is already safely gone. The walk root is
10636
+ exempt: a detached IN_PLACE root is legitimate input and must still
10637
+ be fully sanitized, and a kill-decision on it must keep hitting the
10638
+ REPORT-3 throw. Nodes detached by hooks stay the hook's
10639
+ responsibility for placement: they are not recorded in
10640
+ DOMPurify.removed, so the post-walk IN_PLACE pass (which iterates
10641
+ DOMPurify.removed) does not reach them. But a hook-detached subtree
10642
+ can still hold a queued resource-event handler - e.g. an <img onload>
10643
+ that began loading when the caller built the live tree - which fires
10644
+ in page scope after sanitize returns even though the handler never
10645
+ reached the returned tree. That is the audit-5 F1 hazard, and the
10646
+ documented node.remove() hook pattern walks straight into it. So on
10647
+ the IN_PLACE path we neutralize the detached subtree inline here,
10648
+ stripping its non-allow-listed attributes before returning, exactly
10649
+ as the post-walk pass does for _forceRemove'd subtrees. */
10650
+ if (currentNode !== root && getParentNode(currentNode) === null) {
10651
+ if (IN_PLACE) {
10652
+ _neutralizeSubtree(currentNode);
10653
+ }
10654
+ return true;
10655
+ }
9945
10656
  /* Remove mXSS vectors, processing instructions and risky comments */
9946
10657
  if (_isUnsafeNode(currentNode, tagName)) {
9947
10658
  _forceRemove(currentNode);
@@ -9949,7 +10660,22 @@ function requirePurify_cjs () {
9949
10660
  }
9950
10661
  /* Remove element if anything forbids its presence */
9951
10662
  if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) {
9952
- return _sanitizeDisallowedNode(currentNode, tagName);
10663
+ const removed = _sanitizeDisallowedNode(currentNode, tagName, root);
10664
+ /* A false return means the node is a custom element kept via
10665
+ CUSTOM_ELEMENT_HANDLING - the only keep path through
10666
+ _sanitizeDisallowedNode. Run afterSanitizeElements on it so the
10667
+ element-hook lifecycle matches normal allowlisted elements: a
10668
+ security policy applied in this hook (e.g. stripping an attribute
10669
+ from every surviving element) must not silently skip kept custom
10670
+ elements (GHSA-c2j3-45gr-mqc4). This mirrors the normal-element
10671
+ tail below - the hook runs, then the walker's subsequent
10672
+ _sanitizeAttributes pass sanitizes the element's attributes. The
10673
+ deliberately skipped namespace and fallback-tag removal checks stay
10674
+ skipped; they are removal decisions, not the hook contract. */
10675
+ if (removed === false) {
10676
+ _executeHooks(hooks.afterSanitizeElements, currentNode, null);
10677
+ }
10678
+ return removed;
9953
10679
  }
9954
10680
  /* Check whether element has a valid namespace.
9955
10681
  Realm-safe check (GHSA-hpcv-96wg-7vj8): use the cached Node.prototype
@@ -9996,6 +10722,33 @@ function requirePurify_cjs () {
9996
10722
  if (FORBID_ATTR[lcName]) {
9997
10723
  return false;
9998
10724
  }
10725
+ /* Reject declarative-partial-updates patch-linkage attributes
10726
+ (https://github.com/WICG/declarative-partial-updates).
10727
+ Empirical note (Chrome 150, verified — see
10728
+ test/declarative-patch-probe-v3.html): expansion is NOT applied after
10729
+ sanitization. For the string path it fires during sanitize()'s own
10730
+ parse, so the walk sees and sanitizes the fully materialized expanded
10731
+ tree — teleports into MathML/SVG integration points included; a
10732
+ weaponized `<template for>`->`<img onerror>` comes back with the handler
10733
+ stripped. For the IN_PLACE path it fires on connection, before the walk.
10734
+ Either way DOMPurify is NOT blind to the patch.
10735
+ This removal is therefore defense-in-depth rather than the sole barrier:
10736
+ it prevents live linkage from surviving into the OUTPUT and re-expanding
10737
+ in the caller's context, and keeps behaviour deterministic if a future
10738
+ engine defers expansion. `for` is legitimate only on <label>/<output>;
10739
+ anywhere else (notably <template for>) it links the element to a patch
10740
+ target and teleports or removes an arbitrary DOM range by id/marker name.
10741
+ `patchsrc` fetches remote markup and is treated as a script-loading
10742
+ mechanism (CSP). Gated on SAFE_FOR_XML so the removal groups with the
10743
+ other structural-threat checks and stays overridable, consistent with
10744
+ the rest of the codebase. PI range markers are already removed by
10745
+ _isUnsafeNode. */
10746
+ if (SAFE_FOR_XML && lcName === 'patchsrc') {
10747
+ return false;
10748
+ }
10749
+ if (SAFE_FOR_XML && lcName === 'for' && lcTag !== 'label' && lcTag !== 'output') {
10750
+ return false;
10751
+ }
9999
10752
  /* Make sure attribute cannot clobber */
10000
10753
  if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
10001
10754
  return false;
@@ -10112,6 +10865,9 @@ function requirePurify_cjs () {
10112
10865
  if (!attributes || _isClobbered(currentNode)) {
10113
10866
  return;
10114
10867
  }
10868
+ /* Same lazy-install guard as uponSanitizeElement (see there): fork the
10869
+ attribute allowlist off its shared binding before a hook can see it. */
10870
+ ALLOWED_ATTR = _forkSharedAllowlist(hooks.uponSanitizeAttribute, ALLOWED_ATTR, DEFAULT_ALLOWED_ATTR, SET_CONFIG_ALLOWED_ATTR);
10115
10871
  const hookEvent = {
10116
10872
  attrName: '',
10117
10873
  attrValue: '',
@@ -10205,7 +10961,7 @@ function requirePurify_cjs () {
10205
10961
  /* Execute a hook if present */
10206
10962
  _executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
10207
10963
  /* Sanitize tags and elements */
10208
- _sanitizeElements(shadowNode);
10964
+ _sanitizeElements(shadowNode, fragment);
10209
10965
  /* Check attributes next */
10210
10966
  _sanitizeAttributes(shadowNode);
10211
10967
  /* Deep shadow DOM detected.
@@ -10396,6 +11152,11 @@ function requirePurify_cjs () {
10396
11152
  keep using and whose return value they ignore — unsanitized. REPORT-2. */
10397
11153
  const inPlace = IN_PLACE && typeof dirty !== 'string' && _isNode(dirty);
10398
11154
  if (inPlace) {
11155
+ /* Declarative-partial-updates / streaming pre-pass: sever every patch
11156
+ linkage across the live tree BEFORE the walk, so no patch can fire
11157
+ mid-walk and inject into an already-processed region. Runs first, so
11158
+ it also covers the forbidden/clobbered roots that throw below. */
11159
+ _neutralizePatchLinkage(dirty);
10399
11160
  /* Do some early pre-sanitization to avoid unsafe root nodes.
10400
11161
  Read nodeName through the cached prototype getter — a clobbering
10401
11162
  child named "nodeName" on the form root would otherwise shadow
@@ -10405,6 +11166,9 @@ function requirePurify_cjs () {
10405
11166
  if (typeof nn === 'string') {
10406
11167
  const tagName = transformCaseFunc(nn);
10407
11168
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
11169
+ /* Fail closed on a live root: neutralize handlers/children before
11170
+ throwing, exactly as the mid-walk abort path does. */
11171
+ _neutralizeRoot(dirty);
10408
11172
  throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
10409
11173
  }
10410
11174
  }
@@ -10419,6 +11183,10 @@ function requirePurify_cjs () {
10419
11183
  the application unsanitized. Refuse to sanitize such a root
10420
11184
  the same way we refuse a forbidden tag. GHSA-r47g-fvhr-h676. */
10421
11185
  if (_isClobbered(dirty)) {
11186
+ /* Fail closed on a live clobbered root before throwing.
11187
+ _neutralizeRoot's reads are clobber-safe (cached getters); the
11188
+ form's non-clobbered descendants, e.g. an armed <img>, are scrubbed. */
11189
+ _neutralizeRoot(dirty);
10422
11190
  throw typeErrorCreate('root node is clobbered and cannot be sanitized in-place');
10423
11191
  }
10424
11192
  /* Sanitize attached shadow roots before the main iterator runs.
@@ -10471,20 +11239,24 @@ function requirePurify_cjs () {
10471
11239
  _forceRemove(body.firstChild);
10472
11240
  }
10473
11241
  /* Get node iterator */
10474
- const nodeIterator = _createNodeIterator(inPlace ? dirty : body);
11242
+ const walkRoot = inPlace ? dirty : body;
10475
11243
  /* Now start iterating over the created document.
10476
11244
  The walk runs inside an exception barrier (campaign-3 F2): a re-entrant
10477
11245
  engine/custom-element mutation can detach a node mid-walk so
10478
11246
  `_forceRemove`'s parentless guard throws, aborting the loop. Without the
10479
11247
  barrier the caller's in-place tree would be left half-sanitized with the
10480
- unvisited tail still armed. On any throw we fail closed — strip the
10481
- in-place root bare — then rethrow so the existing throw contract is
10482
- preserved. (String/DOM-copy paths never return the partial body, so the
10483
- propagating throw is already fail-closed there.) */
11248
+ unvisited tail still armed. _createNodeIterator itself is inside the
11249
+ barrier too: constructing the iterator dereferences the root's document,
11250
+ and any failure there (e.g. an exotic/clobbered root) must still fail
11251
+ closed rather than skip the neutralize. On any throw we fail closed -
11252
+ strip the in-place root bare - then rethrow so the existing throw
11253
+ contract is preserved. (String/DOM-copy paths never return the partial
11254
+ body, so the propagating throw is already fail-closed there.) */
10484
11255
  try {
11256
+ const nodeIterator = _createNodeIterator(walkRoot);
10485
11257
  while (currentNode = nodeIterator.nextNode()) {
10486
11258
  /* Sanitize tags and elements */
10487
- _sanitizeElements(currentNode);
11259
+ _sanitizeElements(currentNode, walkRoot);
10488
11260
  /* Check attributes next */
10489
11261
  _sanitizeAttributes(currentNode);
10490
11262
  /* Shadow DOM detected, sanitize it.
@@ -10498,6 +11270,14 @@ function requirePurify_cjs () {
10498
11270
  } catch (error) {
10499
11271
  if (inPlace) {
10500
11272
  _neutralizeRoot(dirty);
11273
+ /* Nodes _forceRemove'd earlier in the aborted walk are already
11274
+ detached from the root, so _neutralizeRoot's subtree pass does not
11275
+ reach them. Defuse them too, mirroring the success-path loop below. */
11276
+ arrayForEach(DOMPurify.removed, entry => {
11277
+ if (entry.element) {
11278
+ _neutralizeSubtree(entry.element);
11279
+ }
11280
+ });
10501
11281
  }
10502
11282
  throw error;
10503
11283
  }
@@ -14924,6 +15704,54 @@ var MultiWidget = /*#__PURE__*/function () {
14924
15704
  validate_card_types: validateCardNumberInput
14925
15705
  });
14926
15706
  }
15707
+ /**
15708
+ * Current method restricts which card schemes the card number input accepts, without changing which card
15709
+ * icons the widget displays. Scheme identifiers are matched case-insensitively and ignoring surrounding
15710
+ * whitespace; unsupported identifiers are ignored with a warning, so a list in which every identifier is
15711
+ * unsupported applies no restriction at all.
15712
+ *
15713
+ * Takes precedence over setSupportedCardIcons: when a non-empty list is supplied here, it is the only list
15714
+ * used for validation and the validateCardNumberInput flag of setSupportedCardIcons no longer affects it.
15715
+ * Calling this method more than once replaces the previous list rather than merging with it.
15716
+ *
15717
+ * Must be called before load().
15718
+ *
15719
+ * @example
15720
+ *
15721
+ * widget.setAllowedCardSchemes(['visa', 'mastercard']);
15722
+ *
15723
+ * @param {string[]} schemes - [CARD_SCHEME]{@link CARD_SCHEME}
15724
+ */
15725
+ }, {
15726
+ key: "setAllowedCardSchemes",
15727
+ value: function setAllowedCardSchemes(schemes) {
15728
+ var vocabulary = ObjectHelper.values(CARD_SCHEME);
15729
+ var allowedSchemes = [];
15730
+ var _iterator = _createForOfIteratorHelper(Array.isArray(schemes) ? schemes : []),
15731
+ _step;
15732
+ try {
15733
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
15734
+ var entry = _step.value;
15735
+ var scheme = typeof entry === 'string' ? entry.trim().toLowerCase() : '';
15736
+ if (vocabulary.indexOf(scheme) === -1) {
15737
+ console.warn("Widget::setAllowedCardSchemes: unsupported card scheme ".concat(entry));
15738
+ continue;
15739
+ }
15740
+ if (allowedSchemes.indexOf(scheme) === -1) {
15741
+ allowedSchemes.push(scheme);
15742
+ }
15743
+ }
15744
+ } catch (err) {
15745
+ _iterator.e(err);
15746
+ } finally {
15747
+ _iterator.f();
15748
+ }
15749
+ if (allowedSchemes.length) {
15750
+ this.link.setParams({
15751
+ allowed_card_schemes: allowedSchemes.join(',')
15752
+ });
15753
+ }
15754
+ }
14927
15755
  /**
14928
15756
  * Current method can hide prevent the widget from showing the error messages
14929
15757
  *
@@ -45284,14 +46112,17 @@ var dbf = function (l) { return l == 1 ? 3 : l < 6 ? 2 : l == 9 ? 1 : 0; };
45284
46112
  var slzh = function (d, b) { return b + 30 + b2(d, b + 26) + b2(d, b + 28); };
45285
46113
  // read zip header
45286
46114
  var zh = function (d, b, z) {
45287
- var fnl = b2(d, b + 28), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl, bs = b4(d, b + 20);
45288
- var _a = z && bs == 4294967295 ? z64e(d, es) : [bs, b4(d, b + 24), b4(d, b + 42)], sc = _a[0], su = _a[1], off = _a[2];
45289
- return [b2(d, b + 10), sc, su, fn, es + b2(d, b + 30) + b2(d, b + 32), off];
46115
+ var fnl = b2(d, b + 28), efl = b2(d, b + 30), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl, bs = b4(d, b + 20);
46116
+ var _a = z && bs == 4294967295 ? z64e(d, es, efl) : [bs, b4(d, b + 24), b4(d, b + 42)], sc = _a[0], su = _a[1], off = _a[2];
46117
+ return [b2(d, b + 10), sc, su, fn, es + efl + b2(d, b + 32), off];
45290
46118
  };
45291
46119
  // read zip64 extra field
45292
- var z64e = function (d, b) {
45293
- for (; b2(d, b) != 1; b += 4 + b2(d, b + 2))
45294
- ;
46120
+ var z64e = function (d, b, l) {
46121
+ var e = b + l;
46122
+ for (; b2(d, b) != 1; b += 4 + b2(d, b + 2)) {
46123
+ if (b + 4 > e)
46124
+ err(13);
46125
+ }
45295
46126
  return [b8(d, b + 12), b8(d, b + 4), b8(d, b + 20)];
45296
46127
  };
45297
46128
  // extra field length
@@ -45894,7 +46725,7 @@ var Unzip = /*#__PURE__*/ (function () {
45894
46725
  var sc_1 = b4(buf, i + 18), su_1 = b4(buf, i + 22);
45895
46726
  var fn_1 = strFromU8(buf.subarray(i + 30, i += 30 + fnl), !u);
45896
46727
  if (sc_1 == 4294967295) {
45897
- _a = dd ? [-2] : z64e(buf, i), sc_1 = _a[0], su_1 = _a[1];
46728
+ _a = dd ? [-2] : z64e(buf, i, es), sc_1 = _a[0], su_1 = _a[1];
45898
46729
  }
45899
46730
  else if (dd)
45900
46731
  sc_1 = -1;
@@ -46175,6 +47006,7 @@ exports.Api = Api;
46175
47006
  exports.ApplePayOpenWalletButton = ApplePayOpenWalletButton;
46176
47007
  exports.ApplePayWalletButtonExpress = ApplePayWalletButtonExpress;
46177
47008
  exports.CANVAS_3DS_EVENT = CANVAS_3DS_EVENT;
47009
+ exports.CARD_SCHEME = CARD_SCHEME;
46178
47010
  exports.CHECKOUT_BUTTON_EVENT = CHECKOUT_BUTTON_EVENT;
46179
47011
  exports.CHECKOUT_EVENT = EVENT$2;
46180
47012
  exports.Canvas3ds = Canvas3ds;