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