@paydock/client-sdk 1.144.1-beta → 1.147.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +551 -32
- package/bundles/index.cjs +2683 -599
- package/bundles/index.cjs.d.ts +322 -21
- package/bundles/index.mjs +2683 -600
- package/bundles/index.mjs.d.ts +322 -21
- 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 +1 -5
- package/bundles/types/api/api-charge-internal.d.ts.map +1 -1
- package/bundles/types/canvas-3ds/canvas-3ds-event-map.d.ts +50 -0
- package/bundles/types/canvas-3ds/canvas-3ds-event-map.d.ts.map +1 -1
- package/bundles/types/canvas-3ds/canvas-3ds.d.ts +7 -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 +117 -2
- package/bundles/types/canvas-3ds/services/gpayments-service.d.ts.map +1 -1
- package/bundles/types/canvas-3ds/services/standalone3ds-service.d.ts +6 -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/helper/browser.d.ts.map +1 -1
- package/bundles/types/helper/element-id.d.ts +3 -1
- package/bundles/types/helper/element-id.d.ts.map +1 -1
- package/bundles/types/helper/uuid.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/open-wallets/apple-pay-open-wallet-button.d.ts +41 -0
- package/bundles/types/open-wallets/apple-pay-open-wallet-button.d.ts.map +1 -1
- package/bundles/types/open-wallets/base/open-wallet-buttons.d.ts +10 -0
- package/bundles/types/open-wallets/base/open-wallet-buttons.d.ts.map +1 -1
- package/bundles/types/open-wallets/base/service-config-cache.d.ts +23 -0
- package/bundles/types/open-wallets/base/service-config-cache.d.ts.map +1 -0
- package/bundles/types/open-wallets/google-pay-open-wallet-button.d.ts +33 -1
- package/bundles/types/open-wallets/google-pay-open-wallet-button.d.ts.map +1 -1
- package/bundles/types/open-wallets/index.d.ts +82 -1
- package/bundles/types/open-wallets/index.d.ts.map +1 -1
- package/bundles/types/open-wallets/interfaces/apple-pay/apple-pay-availability.interface.d.ts +41 -0
- package/bundles/types/open-wallets/interfaces/apple-pay/apple-pay-availability.interface.d.ts.map +1 -0
- package/bundles/types/open-wallets/interfaces/apple-pay/index.d.ts +1 -0
- package/bundles/types/open-wallets/interfaces/apple-pay/index.d.ts.map +1 -1
- package/bundles/types/open-wallets/interfaces/google-pay/google-pay-availability.interface.d.ts +25 -0
- package/bundles/types/open-wallets/interfaces/google-pay/google-pay-availability.interface.d.ts.map +1 -0
- package/bundles/types/open-wallets/interfaces/google-pay/index.d.ts +1 -0
- package/bundles/types/open-wallets/interfaces/google-pay/index.d.ts.map +1 -1
- package/bundles/types/open-wallets/interfaces/index.d.ts +3 -0
- package/bundles/types/open-wallets/interfaces/index.d.ts.map +1 -1
- package/bundles/types/open-wallets/interfaces/open-wallet-availability.interface.d.ts +31 -0
- package/bundles/types/open-wallets/interfaces/open-wallet-availability.interface.d.ts.map +1 -0
- package/bundles/types/open-wallets/services/apple-pay/apple-pay-availability.d.ts +58 -0
- package/bundles/types/open-wallets/services/apple-pay/apple-pay-availability.d.ts.map +1 -0
- package/bundles/types/open-wallets/services/apple-pay/apple-pay.open-wallet.service.d.ts +8 -10
- package/bundles/types/open-wallets/services/apple-pay/apple-pay.open-wallet.service.d.ts.map +1 -1
- package/bundles/types/open-wallets/services/apple-pay/index.d.ts +1 -0
- package/bundles/types/open-wallets/services/apple-pay/index.d.ts.map +1 -1
- package/bundles/types/open-wallets/services/google-pay/google-pay-availability.d.ts +53 -0
- package/bundles/types/open-wallets/services/google-pay/google-pay-availability.d.ts.map +1 -0
- package/bundles/types/open-wallets/services/google-pay/google-pay.open-wallet.service.d.ts.map +1 -1
- package/bundles/types/open-wallets/services/google-pay/index.d.ts +1 -0
- package/bundles/types/open-wallets/services/google-pay/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 +2683 -599
- package/bundles/widget.umd.js.d.ts +322 -21
- package/bundles/widget.umd.js.min.d.ts +322 -21
- package/bundles/widget.umd.min.js +1 -1
- package/docs/api-canvas3ds.md +10 -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/open-wallet-buttons-examples.md +170 -2
- package/docs/open-wallet-buttons.md +252 -29
- package/docs/widget-examples.md +41 -0
- package/package.json +20 -11
- package/slate.md +254 -3
package/bundles/index.cjs
CHANGED
|
@@ -830,16 +830,28 @@ var Browser = /*#__PURE__*/function () {
|
|
|
830
830
|
value: function getBrowserName() {
|
|
831
831
|
var sUsrAg = navigator.userAgent;
|
|
832
832
|
// "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0"
|
|
833
|
-
if (sUsrAg.indexOf('Firefox') > -1)
|
|
834
|
-
|
|
833
|
+
if (sUsrAg.indexOf('Firefox') > -1) {
|
|
834
|
+
return 'Mozilla Firefox';
|
|
835
|
+
}
|
|
836
|
+
if (sUsrAg.indexOf('Opera') > -1) {
|
|
837
|
+
return 'Opera';
|
|
838
|
+
}
|
|
835
839
|
// "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; Zoom 3.6.0; wbx 1.0.0; rv:11.0) like Gecko"
|
|
836
|
-
if (sUsrAg.indexOf('Trident') > -1)
|
|
840
|
+
if (sUsrAg.indexOf('Trident') > -1) {
|
|
841
|
+
return 'Microsoft Internet Explorer';
|
|
842
|
+
}
|
|
837
843
|
// "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299"
|
|
838
|
-
if (sUsrAg.indexOf('Edge') > -1)
|
|
844
|
+
if (sUsrAg.indexOf('Edge') > -1) {
|
|
845
|
+
return 'Microsoft Edge';
|
|
846
|
+
}
|
|
839
847
|
// "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/66.0.3359.181 Chrome/66.0.3359.181 Safari/537.36"
|
|
840
|
-
if (sUsrAg.indexOf('Chrome') > -1)
|
|
848
|
+
if (sUsrAg.indexOf('Chrome') > -1) {
|
|
849
|
+
return 'Google Chrome or Chromium';
|
|
850
|
+
}
|
|
841
851
|
// "Mozilla/5.0 (iPhone; CPU iPhone OS 11_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.0 Mobile/15E148 Safari/604.1 980x1306"
|
|
842
|
-
if (sUsrAg.indexOf('Safari') > -1)
|
|
852
|
+
if (sUsrAg.indexOf('Safari') > -1) {
|
|
853
|
+
return 'Apple Safari';
|
|
854
|
+
}
|
|
843
855
|
return 'unknown';
|
|
844
856
|
}
|
|
845
857
|
}, {
|
|
@@ -870,11 +882,13 @@ var Browser = /*#__PURE__*/function () {
|
|
|
870
882
|
}, {
|
|
871
883
|
key: "getBrowserInfo",
|
|
872
884
|
value: function getBrowserInfo() {
|
|
873
|
-
|
|
885
|
+
// Bound the user-agent length before running any regex to avoid
|
|
886
|
+
// pathological backtracking (ReDoS) on maliciously long inputs.
|
|
887
|
+
var ua = (navigator.userAgent || '').slice(0, 256);
|
|
874
888
|
var M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
|
|
875
|
-
var tem;
|
|
876
|
-
if (/trident/i.test(M[1])) {
|
|
877
|
-
tem = /\brv[ :](\d+)
|
|
889
|
+
var tem = null;
|
|
890
|
+
if (/trident/i.test(M[1] || '')) {
|
|
891
|
+
tem = ua.match(/\brv[ :](\d+)/) || [];
|
|
878
892
|
return {
|
|
879
893
|
name: 'IE',
|
|
880
894
|
version: tem[1] || ''
|
|
@@ -890,8 +904,8 @@ var Browser = /*#__PURE__*/function () {
|
|
|
890
904
|
}
|
|
891
905
|
}
|
|
892
906
|
M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?'];
|
|
893
|
-
|
|
894
|
-
if (
|
|
907
|
+
tem = ua.match(/version\/(\d+)/i);
|
|
908
|
+
if (tem != null) {
|
|
895
909
|
M.splice(1, 1, tem[1]);
|
|
896
910
|
}
|
|
897
911
|
return {
|
|
@@ -1851,7 +1865,7 @@ SDK.headerKeys = Object.freeze({
|
|
|
1851
1865
|
version: 'x-sdk-version',
|
|
1852
1866
|
type: 'x-sdk-type'
|
|
1853
1867
|
});
|
|
1854
|
-
SDK._version = 'v1.
|
|
1868
|
+
SDK._version = 'v1.147.0';
|
|
1855
1869
|
|
|
1856
1870
|
var ACCESS_HEADER_NAME = {
|
|
1857
1871
|
accessToken: 'x-access-token',
|
|
@@ -4707,6 +4721,15 @@ var ApiBase = /*#__PURE__*/function () {
|
|
|
4707
4721
|
this.env.setEnv(env, alias);
|
|
4708
4722
|
return this;
|
|
4709
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
|
+
}
|
|
4710
4733
|
}, {
|
|
4711
4734
|
key: "requestObservable",
|
|
4712
4735
|
value: function requestObservable(method, url, requestBody) {
|
|
@@ -4919,6 +4942,18 @@ var ApiBase = /*#__PURE__*/function () {
|
|
|
4919
4942
|
environment: this.env.getEnv()
|
|
4920
4943
|
};
|
|
4921
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
|
+
}
|
|
4922
4957
|
}]);
|
|
4923
4958
|
}();
|
|
4924
4959
|
|
|
@@ -4979,7 +5014,6 @@ var Api = /*#__PURE__*/function (_ApiBase) {
|
|
|
4979
5014
|
|
|
4980
5015
|
var WALLET_CAPTURE_LINK = '/v1/charges/wallet/capture';
|
|
4981
5016
|
var WALLET_CALLBACK_LINK = '/v1/charges/wallet/callback';
|
|
4982
|
-
var STANDALONE_3DS_PROCESS_LINK = '/v1/charges/standalone-3ds/process';
|
|
4983
5017
|
var STANDALONE_3DS_HANDLE_LINK = '/v1/charges/standalone-3ds/handle';
|
|
4984
5018
|
var ApiChargeInternal = /*#__PURE__*/function () {
|
|
4985
5019
|
function ApiChargeInternal(api) {
|
|
@@ -4996,11 +5030,6 @@ var ApiChargeInternal = /*#__PURE__*/function () {
|
|
|
4996
5030
|
value: function walletCallback(payload) {
|
|
4997
5031
|
return this.api.getClientPromise('POST', WALLET_CALLBACK_LINK).send(payload);
|
|
4998
5032
|
}
|
|
4999
|
-
}, {
|
|
5000
|
-
key: "standalone3dsProcess",
|
|
5001
|
-
value: function standalone3dsProcess(payload) {
|
|
5002
|
-
return this.api.getClientPromise('POST', STANDALONE_3DS_PROCESS_LINK).send(payload);
|
|
5003
|
-
}
|
|
5004
5033
|
}, {
|
|
5005
5034
|
key: "standalone3dsHandle",
|
|
5006
5035
|
value: function standalone3dsHandle() {
|
|
@@ -5139,6 +5168,10 @@ var IFrame = /*#__PURE__*/function () {
|
|
|
5139
5168
|
_classCallCheck(this, IFrame);
|
|
5140
5169
|
this.container = container;
|
|
5141
5170
|
}
|
|
5171
|
+
/**
|
|
5172
|
+
* @param options.onLoad Called on every `load` of the iframe (initial document and each
|
|
5173
|
+
* subsequent navigation). Attached before `src` is set so a fast load is never missed.
|
|
5174
|
+
*/
|
|
5142
5175
|
return _createClass(IFrame, [{
|
|
5143
5176
|
key: "load",
|
|
5144
5177
|
value: function load(link) {
|
|
@@ -5147,6 +5180,9 @@ var IFrame = /*#__PURE__*/function () {
|
|
|
5147
5180
|
if (!this.container.isExist() || this.isExist()) return;
|
|
5148
5181
|
var iFrame = document.createElement('iframe');
|
|
5149
5182
|
if (options.title) iFrame.title = options.title;
|
|
5183
|
+
if (options.onLoad) {
|
|
5184
|
+
iFrame.addEventListener('load', options.onLoad);
|
|
5185
|
+
}
|
|
5150
5186
|
if (iframeClass) iFrame.classList.add(iframeClass);
|
|
5151
5187
|
// Set additional attributes before src and DOM insertion so that
|
|
5152
5188
|
// Permissions-Policy (e.g. allow="payment") is in place when the
|
|
@@ -5287,14 +5323,14 @@ var IFrameEvent = /*#__PURE__*/function () {
|
|
|
5287
5323
|
data = JSON.parse(event.data);
|
|
5288
5324
|
} catch (error) {}
|
|
5289
5325
|
if (!data) return;
|
|
5290
|
-
_this.emit(data);
|
|
5326
|
+
_this.emit(data, event.origin);
|
|
5291
5327
|
});
|
|
5292
5328
|
}
|
|
5293
5329
|
return _createClass(IFrameEvent, [{
|
|
5294
5330
|
key: "emit",
|
|
5295
|
-
value: function emit(data) {
|
|
5331
|
+
value: function emit(data, origin) {
|
|
5296
5332
|
for (var key in this.listeners) {
|
|
5297
|
-
if (this.listeners[key].event === data.event && data.widget_id === this.listeners[key].widget_id) this.listeners[key].listener.apply(this, [data]);
|
|
5333
|
+
if (this.listeners[key].event === data.event && data.widget_id === this.listeners[key].widget_id) this.listeners[key].listener.apply(this, [data, origin]);
|
|
5298
5334
|
}
|
|
5299
5335
|
}
|
|
5300
5336
|
}, {
|
|
@@ -5367,6 +5403,20 @@ var GPAYMENTS_EVENT = {
|
|
|
5367
5403
|
DECOUPLED: 'chargeAuthDecoupled',
|
|
5368
5404
|
/** Challenge required — issuer demands user interaction. Payload: `Canvas3dsAuthPayload`. */
|
|
5369
5405
|
CHALLENGE: 'chargeAuthChallenge',
|
|
5406
|
+
/**
|
|
5407
|
+
* The challenge iframe fired its FIRST `load` after `chargeAuthChallenge` (the ACS page has painted),
|
|
5408
|
+
* or the `CHALLENGE_LOADED_FALLBACK_MS` safety fallback elapsed without one. Emitted exactly once per
|
|
5409
|
+
* challenge; never emitted for a decoupled challenge (no challenge iframe). Payload: `Canvas3dsChallengeLoadedPayload`.
|
|
5410
|
+
*/
|
|
5411
|
+
CHALLENGE_LOADED: 'chargeAuthChallengeLoaded',
|
|
5412
|
+
/**
|
|
5413
|
+
* The issuer delivered the final authentication result to the 3DS Server (the shopper finished the
|
|
5414
|
+
* challenge, or approved a decoupled authentication) and the SDK is now obtaining the outcome. Emitted at
|
|
5415
|
+
* most once per authentication, always before the terminal event. Detected through the `secondary_url`
|
|
5416
|
+
* result monitor (`source: 'poll'`) or, when the callback message arrives first, right before the outcome
|
|
5417
|
+
* it carries (`source: 'callback'`). Payload: `Canvas3dsChallengeCompletedPayload`.
|
|
5418
|
+
*/
|
|
5419
|
+
CHALLENGE_COMPLETED: 'chargeAuthChallengeCompleted',
|
|
5370
5420
|
/** Informational signal emitted by the GPayments flow. Internal — not part of the public typed surface. */
|
|
5371
5421
|
INFO: 'chargeAuthInfo',
|
|
5372
5422
|
/** Error during the flow (network, parsing, etc.). Payload: `Canvas3dsErrorPayload`. */
|
|
@@ -5378,13 +5428,56 @@ var GPAYMENTS_EVENT = {
|
|
|
5378
5428
|
* backwards-compatible internal usage.
|
|
5379
5429
|
*/
|
|
5380
5430
|
var CANVAS_3DS_EVENT = GPAYMENTS_EVENT;
|
|
5431
|
+
/**
|
|
5432
|
+
* Upper bound on how long `chargeAuthChallengeLoaded` waits for the challenge iframe's first `load`.
|
|
5433
|
+
* When it elapses the event is emitted with `reason: 'timeout'` so a merchant loader is never held
|
|
5434
|
+
* forever; the real `load` arriving afterwards is the paint and is simply ignored.
|
|
5435
|
+
*/
|
|
5436
|
+
var CHALLENGE_LOADED_FALLBACK_MS = 2500;
|
|
5437
|
+
/**
|
|
5438
|
+
* Interval of the `secondary_url` (GPayments `resultMonUrl`) poll while an ACS challenge is on screen.
|
|
5439
|
+
* The monitor flips to `AuthResultReady` when the issuer's RReq reaches the 3DS Server — before the ACS has
|
|
5440
|
+
* even returned its final page to the browser — so this poll is what makes `chargeAuthChallengeCompleted`
|
|
5441
|
+
* land before the shopper sees any post-submit page. GPayments recommends 2 s and leaves the interval to the
|
|
5442
|
+
* requestor; 1 s matches the cadence of GPayments' own monitoring frame.
|
|
5443
|
+
*/
|
|
5444
|
+
var CHALLENGE_RESULT_POLL_INTERVAL_MS = 1000;
|
|
5445
|
+
/**
|
|
5446
|
+
* Interval of the `secondary_url` poll for a decoupled challenge (shopper approves out-of-band, possibly
|
|
5447
|
+
* minutes later; nothing to cover on screen) — the GPayments-recommended 2 s.
|
|
5448
|
+
*/
|
|
5449
|
+
var DECOUPLED_RESULT_POLL_INTERVAL_MS = 2000;
|
|
5450
|
+
/** The result-monitor request failed at transport level (network, abort, timeout) — retried, not fatal. */
|
|
5451
|
+
var ResultMonitorTransportError = /*#__PURE__*/function (_Error) {
|
|
5452
|
+
function ResultMonitorTransportError() {
|
|
5453
|
+
var _this;
|
|
5454
|
+
_classCallCheck(this, ResultMonitorTransportError);
|
|
5455
|
+
_this = _callSuper(this, ResultMonitorTransportError, ['Standalone 3DS result monitor request failed']);
|
|
5456
|
+
_this.name = 'ResultMonitorTransportError';
|
|
5457
|
+
return _this;
|
|
5458
|
+
}
|
|
5459
|
+
_inherits(ResultMonitorTransportError, _Error);
|
|
5460
|
+
return _createClass(ResultMonitorTransportError);
|
|
5461
|
+
}(/*#__PURE__*/_wrapNativeSuper(Error));
|
|
5381
5462
|
var GPaymentsService = /*#__PURE__*/function () {
|
|
5382
5463
|
function GPaymentsService(container, api, eventEmitter) {
|
|
5464
|
+
var trustedOrigins = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
|
|
5383
5465
|
_classCallCheck(this, GPaymentsService);
|
|
5384
5466
|
this.container = container;
|
|
5385
5467
|
this.api = api;
|
|
5386
5468
|
this.eventEmitter = eventEmitter;
|
|
5469
|
+
this.trustedOrigins = trustedOrigins;
|
|
5387
5470
|
this.resultRead = false;
|
|
5471
|
+
/** `chargeAuthChallengeLoaded` already emitted for the current challenge (by `load` or by the fallback). */
|
|
5472
|
+
this.challengeLoadedEmitted = false;
|
|
5473
|
+
/** `chargeAuthChallengeCompleted` already emitted for the current authentication (poll or callback). */
|
|
5474
|
+
this.challengeCompletedEmitted = false;
|
|
5475
|
+
/** Set once a terminal event was emitted: no lifecycle event may follow (reset when a new challenge starts). */
|
|
5476
|
+
this.challengeLifecycleClosed = false;
|
|
5477
|
+
this.challengeLoadedTimer = null;
|
|
5478
|
+
this.resultPollTimer = null;
|
|
5479
|
+
/** One-shot guard for the `chargeAuth` leg — the callback message may be delivered more than once. */
|
|
5480
|
+
this.authHandled = false;
|
|
5388
5481
|
this.iFrameEvent = new IFrameEvent(window);
|
|
5389
5482
|
}
|
|
5390
5483
|
return _createClass(GPaymentsService, [{
|
|
@@ -5404,6 +5497,7 @@ var GPaymentsService = /*#__PURE__*/function () {
|
|
|
5404
5497
|
key: "initializeIFrames",
|
|
5405
5498
|
value: function initializeIFrames(initializationUrl, secondaryUrl, title) {
|
|
5406
5499
|
var hideAuthorization = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
|
|
5500
|
+
var onLoad = arguments.length > 4 ? arguments[4] : undefined;
|
|
5407
5501
|
var container = this.container.getElement();
|
|
5408
5502
|
if (!container) {
|
|
5409
5503
|
return;
|
|
@@ -5414,7 +5508,8 @@ var GPaymentsService = /*#__PURE__*/function () {
|
|
|
5414
5508
|
this.browserAndChallengeContainer = new Container('#paydock_authorization_iframe');
|
|
5415
5509
|
this.iFrameAuthorization = new IFrame(this.browserAndChallengeContainer);
|
|
5416
5510
|
this.iFrameAuthorization.load(initializationUrl, {
|
|
5417
|
-
title: title
|
|
5511
|
+
title: title,
|
|
5512
|
+
onLoad: onLoad
|
|
5418
5513
|
});
|
|
5419
5514
|
if (secondaryUrl) {
|
|
5420
5515
|
var divSecondaryURL = document.createElement('div');
|
|
@@ -5448,25 +5543,192 @@ var GPaymentsService = /*#__PURE__*/function () {
|
|
|
5448
5543
|
}, {
|
|
5449
5544
|
key: "setupIFrameEvents",
|
|
5450
5545
|
value: function setupIFrameEvents(widgetId) {
|
|
5451
|
-
var
|
|
5452
|
-
this.iFrameEvent.on(EVENT$4.CHARGE_AUTH, widgetId, function (data) {
|
|
5546
|
+
var _this2 = this;
|
|
5547
|
+
this.iFrameEvent.on(EVENT$4.CHARGE_AUTH, widgetId, function (data, origin) {
|
|
5453
5548
|
if (data.status === IFRAME_STATUS.SKIPPED || data.status === IFRAME_STATUS.FINISHED) {
|
|
5549
|
+
// this channel is spoofable — any frame that knows the charge_3ds_id can post to it.
|
|
5550
|
+
// A message from an unknown sender must never terminate a live authentication: ignore it.
|
|
5551
|
+
if (!_this2.isTrustedOrigin(origin)) {
|
|
5552
|
+
return;
|
|
5553
|
+
}
|
|
5554
|
+
// one-shot: the callback message can be delivered more than once (it is posted to both
|
|
5555
|
+
// `parent` and `top`) — a duplicate must not re-emit events or duplicate challenge iframes
|
|
5556
|
+
if (_this2.authHandled) {
|
|
5557
|
+
return;
|
|
5558
|
+
}
|
|
5559
|
+
_this2.authHandled = true;
|
|
5454
5560
|
if (data.info) {
|
|
5455
|
-
|
|
5561
|
+
_this2.eventEmitter.emit(GPAYMENTS_EVENT.INFO, {
|
|
5456
5562
|
info: data.info
|
|
5457
5563
|
});
|
|
5458
5564
|
}
|
|
5459
|
-
|
|
5565
|
+
_this2.removeIframes();
|
|
5566
|
+
if (typeof data.process_status === 'string') {
|
|
5567
|
+
// the API already ran the authentication inside the callback: no /process call needed
|
|
5568
|
+
_this2.handleAuthenticationOutcome({
|
|
5569
|
+
status: data.process_status,
|
|
5570
|
+
result: data.result,
|
|
5571
|
+
error: data.error
|
|
5572
|
+
}, data.charge_3ds_id);
|
|
5573
|
+
} else {
|
|
5574
|
+
// The API always runs the authentication inside the callback (PAYRAC-2480). A trusted
|
|
5575
|
+
// message without the outcome means an outdated API — fail, never verify via /process.
|
|
5576
|
+
_this2.finishChallengeLifecycle();
|
|
5577
|
+
_this2.eventEmitter.emit(GPAYMENTS_EVENT.ERROR, _this2.parseError({
|
|
5578
|
+
message: 'Standalone 3DS authentication outcome missing from the callback message'
|
|
5579
|
+
}, data.charge_3ds_id));
|
|
5580
|
+
}
|
|
5460
5581
|
} else if (data.status === IFRAME_STATUS.TIMEOUT || data.status === IFRAME_STATUS.INVALID) {
|
|
5461
|
-
|
|
5582
|
+
_this2.finishChallengeLifecycle();
|
|
5583
|
+
_this2.eventEmitter.emit(GPAYMENTS_EVENT.AUTH_ERROR, _this2.parseHandleResponse({
|
|
5462
5584
|
status: data.status
|
|
5463
5585
|
}, data.charge_3ds_id));
|
|
5464
5586
|
}
|
|
5465
5587
|
});
|
|
5466
|
-
this.iFrameEvent.on(EVENT$4.CHARGE_AUTH_SUCCESS, widgetId, function (data) {
|
|
5467
|
-
|
|
5588
|
+
this.iFrameEvent.on(EVENT$4.CHARGE_AUTH_SUCCESS, widgetId, function (data, origin) {
|
|
5589
|
+
// the callback iframe reached Paydock with `AuthResultReady`: the issuer has answered. Announce it
|
|
5590
|
+
// before the outcome so merchants always see completed → terminal, whichever transport won.
|
|
5591
|
+
_this2.emitChallengeCompleted(data.charge_3ds_id, 'callback');
|
|
5592
|
+
if (_this2.hasChainedOutcome(data, origin)) {
|
|
5593
|
+
// the API already fetched the challenge result inside the callback: no GET /handle needed
|
|
5594
|
+
_this2.handleResultOutcome({
|
|
5595
|
+
status: data.process_status,
|
|
5596
|
+
result: data.result,
|
|
5597
|
+
error: data.error
|
|
5598
|
+
}, data.charge_3ds_id);
|
|
5599
|
+
} else {
|
|
5600
|
+
_this2.processResult(data.charge_3ds_id);
|
|
5601
|
+
}
|
|
5602
|
+
});
|
|
5603
|
+
}
|
|
5604
|
+
/**
|
|
5605
|
+
* A message carries a processed outcome only when it has `process_status` AND comes from a trusted
|
|
5606
|
+
* origin. Used by the result leg: an untrusted `process_status` is never acted upon — the SDK falls
|
|
5607
|
+
* back to `GET /handle` (still needed for decoupled challenges / polling). The authentication leg
|
|
5608
|
+
* ignores untrusted senders outright, before this check.
|
|
5609
|
+
*/
|
|
5610
|
+
}, {
|
|
5611
|
+
key: "hasChainedOutcome",
|
|
5612
|
+
value: function hasChainedOutcome(data, origin) {
|
|
5613
|
+
return typeof data.process_status === 'string' && this.isTrustedOrigin(origin);
|
|
5614
|
+
}
|
|
5615
|
+
}, {
|
|
5616
|
+
key: "isTrustedOrigin",
|
|
5617
|
+
value: function isTrustedOrigin(origin) {
|
|
5618
|
+
// 'null' is the serialized opaque origin (e.g. a sandboxed iframe) — never trust it, even if a
|
|
5619
|
+
// misconfigured env URL were ever to put the literal string 'null' into the trusted list
|
|
5620
|
+
return typeof origin === 'string' && origin !== 'null' && this.trustedOrigins.includes(origin);
|
|
5621
|
+
}
|
|
5622
|
+
}, {
|
|
5623
|
+
key: "removeIframes",
|
|
5624
|
+
value: function removeIframes() {
|
|
5625
|
+
var _a, _b;
|
|
5626
|
+
this.clearChallengeLoadedTimer();
|
|
5627
|
+
(_a = this.iFrameAuthorization) === null || _a === void 0 ? void 0 : _a.remove();
|
|
5628
|
+
(_b = this.iFrameSecondaryUrl) === null || _b === void 0 ? void 0 : _b.remove();
|
|
5629
|
+
}
|
|
5630
|
+
}, {
|
|
5631
|
+
key: "clearChallengeLoadedTimer",
|
|
5632
|
+
value: function clearChallengeLoadedTimer() {
|
|
5633
|
+
if (this.challengeLoadedTimer !== null) {
|
|
5634
|
+
clearTimeout(this.challengeLoadedTimer);
|
|
5635
|
+
this.challengeLoadedTimer = null;
|
|
5636
|
+
}
|
|
5637
|
+
}
|
|
5638
|
+
/**
|
|
5639
|
+
* Challenge iframe lifecycle (`chargeAuthChallengeLoaded`).
|
|
5640
|
+
*
|
|
5641
|
+
* The challenge iframe is cross-origin, but its `load` event still fires when the ACS page finishes loading:
|
|
5642
|
+
* the first one means the page painted (merchants can drop their loader). A fallback timer
|
|
5643
|
+
* (`armChallengeLoadedFallback`) guarantees `chargeAuthChallengeLoaded` within `CHALLENGE_LOADED_FALLBACK_MS`
|
|
5644
|
+
* even if no `load` ever fires; a late paint after that is simply ignored. Later `load`s are NOT a usable
|
|
5645
|
+
* signal for "the shopper submitted": the post-submit hops are HTTP redirects and self-submitting pages whose
|
|
5646
|
+
* loads abort, and the final callback page posts its message before its own `load` — so the post-submit
|
|
5647
|
+
* moment is `chargeAuthChallengeCompleted`, driven by the `secondary_url` result monitor instead.
|
|
5648
|
+
*
|
|
5649
|
+
* Nothing is emitted once the flow reached a terminal outcome (`finishChallengeLifecycle`), and a
|
|
5650
|
+
* decoupled challenge (no challenge iframe) never emits `chargeAuthChallengeLoaded`.
|
|
5651
|
+
*
|
|
5652
|
+
* Resets the per-challenge state and returns the `load` handler to attach to the challenge iframe.
|
|
5653
|
+
*/
|
|
5654
|
+
}, {
|
|
5655
|
+
key: "startChallengeLifecycle",
|
|
5656
|
+
value: function startChallengeLifecycle(charge3dsId) {
|
|
5657
|
+
var _this3 = this;
|
|
5658
|
+
this.clearChallengeLoadedTimer();
|
|
5659
|
+
this.challengeLoadedEmitted = false;
|
|
5660
|
+
this.challengeCompletedEmitted = false;
|
|
5661
|
+
this.challengeLifecycleClosed = false;
|
|
5662
|
+
return function () {
|
|
5663
|
+
var _a;
|
|
5664
|
+
if (_this3.challengeLifecycleClosed || _this3.challengeLoadedEmitted || !((_a = _this3.iFrameAuthorization) === null || _a === void 0 ? void 0 : _a.isExist())) {
|
|
5665
|
+
return;
|
|
5666
|
+
}
|
|
5667
|
+
_this3.clearChallengeLoadedTimer();
|
|
5668
|
+
_this3.emitChallengeLoaded(charge3dsId, 'load');
|
|
5669
|
+
};
|
|
5670
|
+
}
|
|
5671
|
+
/**
|
|
5672
|
+
* Arms the `chargeAuthChallengeLoaded` fallback — only when a challenge iframe actually exists (the
|
|
5673
|
+
* container may be gone, or `IFrame.load` may have no-op'd on an already-present iframe).
|
|
5674
|
+
*/
|
|
5675
|
+
}, {
|
|
5676
|
+
key: "armChallengeLoadedFallback",
|
|
5677
|
+
value: function armChallengeLoadedFallback(charge3dsId) {
|
|
5678
|
+
var _this4 = this;
|
|
5679
|
+
var _a;
|
|
5680
|
+
if (!((_a = this.iFrameAuthorization) === null || _a === void 0 ? void 0 : _a.isExist())) {
|
|
5681
|
+
return;
|
|
5682
|
+
}
|
|
5683
|
+
this.challengeLoadedTimer = setTimeout(function () {
|
|
5684
|
+
_this4.challengeLoadedTimer = null;
|
|
5685
|
+
if (_this4.challengeLifecycleClosed || _this4.challengeLoadedEmitted) {
|
|
5686
|
+
return;
|
|
5687
|
+
}
|
|
5688
|
+
_this4.emitChallengeLoaded(charge3dsId, 'timeout');
|
|
5689
|
+
}, CHALLENGE_LOADED_FALLBACK_MS);
|
|
5690
|
+
}
|
|
5691
|
+
}, {
|
|
5692
|
+
key: "emitChallengeLoaded",
|
|
5693
|
+
value: function emitChallengeLoaded(charge3dsId, reason) {
|
|
5694
|
+
this.challengeLoadedEmitted = true;
|
|
5695
|
+
this.eventEmitter.emit(GPAYMENTS_EVENT.CHALLENGE_LOADED, {
|
|
5696
|
+
charge_3ds_id: charge3dsId,
|
|
5697
|
+
reason: reason
|
|
5698
|
+
});
|
|
5699
|
+
}
|
|
5700
|
+
/**
|
|
5701
|
+
* `chargeAuthChallengeCompleted` — once per authentication, never after a terminal event. Idempotent so the
|
|
5702
|
+
* poll and the callback message can both call it: the first transport to notice the issuer's result wins.
|
|
5703
|
+
*/
|
|
5704
|
+
}, {
|
|
5705
|
+
key: "emitChallengeCompleted",
|
|
5706
|
+
value: function emitChallengeCompleted(charge3dsId, source) {
|
|
5707
|
+
if (this.challengeLifecycleClosed || this.challengeCompletedEmitted) {
|
|
5708
|
+
return;
|
|
5709
|
+
}
|
|
5710
|
+
this.challengeCompletedEmitted = true;
|
|
5711
|
+
this.eventEmitter.emit(GPAYMENTS_EVENT.CHALLENGE_COMPLETED, {
|
|
5712
|
+
charge_3ds_id: charge3dsId,
|
|
5713
|
+
source: source
|
|
5468
5714
|
});
|
|
5469
5715
|
}
|
|
5716
|
+
/** Call before emitting any terminal event: no lifecycle event may follow it, and the result poll stops. */
|
|
5717
|
+
}, {
|
|
5718
|
+
key: "finishChallengeLifecycle",
|
|
5719
|
+
value: function finishChallengeLifecycle() {
|
|
5720
|
+
this.clearChallengeLoadedTimer();
|
|
5721
|
+
this.stopResultPolling();
|
|
5722
|
+
this.challengeLifecycleClosed = true;
|
|
5723
|
+
}
|
|
5724
|
+
}, {
|
|
5725
|
+
key: "stopResultPolling",
|
|
5726
|
+
value: function stopResultPolling() {
|
|
5727
|
+
if (this.resultPollTimer !== null) {
|
|
5728
|
+
clearTimeout(this.resultPollTimer);
|
|
5729
|
+
this.resultPollTimer = null;
|
|
5730
|
+
}
|
|
5731
|
+
}
|
|
5470
5732
|
}, {
|
|
5471
5733
|
key: "parseResultData",
|
|
5472
5734
|
value: function parseResultData(_ref2, charge3dsId) {
|
|
@@ -5500,24 +5762,49 @@ var GPaymentsService = /*#__PURE__*/function () {
|
|
|
5500
5762
|
}, {
|
|
5501
5763
|
key: "processResult",
|
|
5502
5764
|
value: function processResult(charge3dsId) {
|
|
5503
|
-
var
|
|
5765
|
+
var _this5 = this;
|
|
5504
5766
|
if (this.resultRead) {
|
|
5505
5767
|
return;
|
|
5506
5768
|
}
|
|
5507
5769
|
this.resultRead = true;
|
|
5770
|
+
this.finishChallengeLifecycle();
|
|
5508
5771
|
this.api.charge().standalone3dsHandle().then(function (result) {
|
|
5509
5772
|
var _a;
|
|
5510
|
-
|
|
5511
|
-
(_a =
|
|
5773
|
+
_this5.iFrameAuthorization.remove();
|
|
5774
|
+
(_a = _this5.iFrameSecondaryUrl) === null || _a === void 0 ? void 0 : _a.remove();
|
|
5512
5775
|
if (result.status === PROCESS_STANDALONE_3DS_STATUS.SUCCESS) {
|
|
5513
|
-
|
|
5776
|
+
_this5.eventEmitter.emit(GPAYMENTS_EVENT.AUTH_SUCCESS, _this5.parseResultData(result, charge3dsId));
|
|
5514
5777
|
} else {
|
|
5515
|
-
|
|
5778
|
+
_this5.eventEmitter.emit(GPAYMENTS_EVENT.AUTH_ERROR, _this5.parseResultData(result, charge3dsId));
|
|
5516
5779
|
}
|
|
5517
5780
|
}, function (err) {
|
|
5518
|
-
|
|
5781
|
+
_this5.eventEmitter.emit(GPAYMENTS_EVENT.ERROR, _this5.parseError(err, charge3dsId));
|
|
5519
5782
|
});
|
|
5520
5783
|
}
|
|
5784
|
+
/** Final (post-challenge) outcome delivered by the API through the callback message. */
|
|
5785
|
+
}, {
|
|
5786
|
+
key: "handleResultOutcome",
|
|
5787
|
+
value: function handleResultOutcome(_ref4, charge3dsId) {
|
|
5788
|
+
var status = _ref4.status,
|
|
5789
|
+
error = _ref4.error;
|
|
5790
|
+
if (this.resultRead) {
|
|
5791
|
+
return;
|
|
5792
|
+
}
|
|
5793
|
+
this.resultRead = true;
|
|
5794
|
+
this.finishChallengeLifecycle();
|
|
5795
|
+
this.removeIframes();
|
|
5796
|
+
if (status === PROCESS_STANDALONE_3DS_STATUS.SUCCESS) {
|
|
5797
|
+
this.eventEmitter.emit(GPAYMENTS_EVENT.AUTH_SUCCESS, this.parseResultData({
|
|
5798
|
+
status: status
|
|
5799
|
+
}, charge3dsId));
|
|
5800
|
+
} else if (status === PROCESS_STANDALONE_3DS_STATUS.ERROR) {
|
|
5801
|
+
this.eventEmitter.emit(GPAYMENTS_EVENT.ERROR, this.parseError(error, charge3dsId));
|
|
5802
|
+
} else {
|
|
5803
|
+
this.eventEmitter.emit(GPAYMENTS_EVENT.AUTH_ERROR, this.parseResultData({
|
|
5804
|
+
status: status
|
|
5805
|
+
}, charge3dsId));
|
|
5806
|
+
}
|
|
5807
|
+
}
|
|
5521
5808
|
}, {
|
|
5522
5809
|
key: "externalAPI",
|
|
5523
5810
|
value: function externalAPI(method, url) {
|
|
@@ -5531,60 +5818,106 @@ var GPaymentsService = /*#__PURE__*/function () {
|
|
|
5531
5818
|
reject(error);
|
|
5532
5819
|
}
|
|
5533
5820
|
};
|
|
5821
|
+
// without these a failed request left the promise pending forever and the poll silently dead
|
|
5822
|
+
var fail = function fail() {
|
|
5823
|
+
return reject(new ResultMonitorTransportError());
|
|
5824
|
+
};
|
|
5825
|
+
request.onerror = fail;
|
|
5826
|
+
request.onabort = fail;
|
|
5827
|
+
request.ontimeout = fail;
|
|
5534
5828
|
request.send();
|
|
5535
5829
|
});
|
|
5536
5830
|
}
|
|
5831
|
+
/**
|
|
5832
|
+
* Polls the GPayments result monitor (`secondary_url`) until `AuthResultReady`. It is the primary source of
|
|
5833
|
+
* `chargeAuthChallengeCompleted` (the monitor flips as soon as the issuer's RReq reaches the 3DS Server) and
|
|
5834
|
+
* the fallback transport for the outcome itself (`GET /handle`) when the callback message never arrives —
|
|
5835
|
+
* the only transport for decoupled challenges. Stops once an outcome was obtained or the lifecycle closed.
|
|
5836
|
+
* Transport failures are retried on the same cadence; an unparsable body or an unknown event is fatal.
|
|
5837
|
+
*/
|
|
5537
5838
|
}, {
|
|
5538
5839
|
key: "doPolling",
|
|
5539
|
-
value: function doPolling(url, charge3dsId) {
|
|
5540
|
-
var
|
|
5840
|
+
value: function doPolling(url, charge3dsId, intervalMs) {
|
|
5841
|
+
var _this6 = this;
|
|
5842
|
+
this.stopResultPolling();
|
|
5843
|
+
if (this.resultRead || this.challengeLifecycleClosed) {
|
|
5844
|
+
return;
|
|
5845
|
+
}
|
|
5846
|
+
var schedule = function schedule() {
|
|
5847
|
+
_this6.resultPollTimer = setTimeout(function () {
|
|
5848
|
+
_this6.resultPollTimer = null;
|
|
5849
|
+
_this6.doPolling(url, charge3dsId, intervalMs);
|
|
5850
|
+
}, intervalMs);
|
|
5851
|
+
};
|
|
5541
5852
|
this.externalAPI('GET', url).then(function (data) {
|
|
5853
|
+
if (_this6.resultRead || _this6.challengeLifecycleClosed) {
|
|
5854
|
+
return;
|
|
5855
|
+
}
|
|
5542
5856
|
if (!data.event || data.event === 'AuthResultNotReady') {
|
|
5543
|
-
|
|
5544
|
-
_this3.doPolling(url, charge3dsId);
|
|
5545
|
-
}, 2000);
|
|
5857
|
+
schedule();
|
|
5546
5858
|
} else if (data.event === 'AuthResultReady') {
|
|
5547
|
-
|
|
5859
|
+
_this6.emitChallengeCompleted(charge3dsId, 'poll');
|
|
5860
|
+
_this6.processResult(charge3dsId);
|
|
5548
5861
|
} else {
|
|
5549
5862
|
throw new Error('Event not supported');
|
|
5550
5863
|
}
|
|
5551
5864
|
})["catch"](function (err) {
|
|
5552
|
-
|
|
5865
|
+
if (err instanceof ResultMonitorTransportError) {
|
|
5866
|
+
if (!_this6.resultRead && !_this6.challengeLifecycleClosed) {
|
|
5867
|
+
schedule();
|
|
5868
|
+
}
|
|
5869
|
+
return;
|
|
5870
|
+
}
|
|
5871
|
+
_this6.finishChallengeLifecycle();
|
|
5872
|
+
_this6.eventEmitter.emit(GPAYMENTS_EVENT.ERROR, _this6.parseError(err, charge3dsId));
|
|
5553
5873
|
});
|
|
5554
5874
|
}
|
|
5875
|
+
/**
|
|
5876
|
+
* Branches on the authentication outcome delivered by the API through the callback message
|
|
5877
|
+
* (`process_status` → `status`; same shape as the `/process` response body).
|
|
5878
|
+
*/
|
|
5555
5879
|
}, {
|
|
5556
|
-
key: "
|
|
5557
|
-
value: function
|
|
5558
|
-
var
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
if (authenticationResult.
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5880
|
+
key: "handleAuthenticationOutcome",
|
|
5881
|
+
value: function handleAuthenticationOutcome(authenticationResult, charge_3ds_id) {
|
|
5882
|
+
var _a, _b, _c;
|
|
5883
|
+
if (authenticationResult.status !== AUTHENTICATION_STATUS.PENDING) {
|
|
5884
|
+
// terminal outcome — a later `chargeAuthSuccess` message or poll must not emit a second result
|
|
5885
|
+
this.resultRead = true;
|
|
5886
|
+
}
|
|
5887
|
+
if (authenticationResult.status === AUTHENTICATION_STATUS.SUCCESS) {
|
|
5888
|
+
this.finishChallengeLifecycle();
|
|
5889
|
+
this.eventEmitter.emit(GPAYMENTS_EVENT.AUTH_SUCCESS, this.parseHandleResponse(authenticationResult, charge_3ds_id));
|
|
5890
|
+
} else if (authenticationResult.status === AUTHENTICATION_STATUS.PENDING) {
|
|
5891
|
+
if ((_a = authenticationResult === null || authenticationResult === void 0 ? void 0 : authenticationResult.result) === null || _a === void 0 ? void 0 : _a.challenge) {
|
|
5892
|
+
this.eventEmitter.emit(GPAYMENTS_EVENT.CHALLENGE, this.parseHandleResponse(authenticationResult, charge_3ds_id));
|
|
5893
|
+
var onChallengeLoad = this.startChallengeLifecycle(charge_3ds_id);
|
|
5894
|
+
this.initializeIFrames(authenticationResult.result.challenge_url, undefined, 'Authentication Challenge', false, onChallengeLoad);
|
|
5895
|
+
this.armChallengeLoadedFallback(charge_3ds_id);
|
|
5896
|
+
if (authenticationResult.result.secondary_url) {
|
|
5897
|
+
this.doPolling(authenticationResult.result.secondary_url, charge_3ds_id, CHALLENGE_RESULT_POLL_INTERVAL_MS);
|
|
5898
|
+
}
|
|
5899
|
+
} else if ((_b = authenticationResult === null || authenticationResult === void 0 ? void 0 : authenticationResult.result) === null || _b === void 0 ? void 0 : _b.decoupled_challenge) {
|
|
5900
|
+
this.challengeCompletedEmitted = false;
|
|
5901
|
+
this.challengeLifecycleClosed = false;
|
|
5902
|
+
this.eventEmitter.emit(GPAYMENTS_EVENT.DECOUPLED, this.parseHandleResponse(authenticationResult, charge_3ds_id));
|
|
5903
|
+
if (authenticationResult.result.secondary_url) {
|
|
5904
|
+
this.doPolling(authenticationResult.result.secondary_url, charge_3ds_id, DECOUPLED_RESULT_POLL_INTERVAL_MS);
|
|
5581
5905
|
}
|
|
5582
5906
|
} else {
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5907
|
+
// pending but nothing to run (no challenge delivered) — fail rather than hang silently
|
|
5908
|
+
// with the iframes already removed and no further message ever coming
|
|
5909
|
+
this.resultRead = true;
|
|
5910
|
+
this.finishChallengeLifecycle();
|
|
5911
|
+
this.eventEmitter.emit(GPAYMENTS_EVENT.AUTH_ERROR, this.parseHandleResponse(authenticationResult, charge_3ds_id));
|
|
5912
|
+
}
|
|
5913
|
+
} else if (authenticationResult.status === AUTHENTICATION_STATUS.ERROR) {
|
|
5914
|
+
// the API rejected the authentication, or could not run it inside the callback
|
|
5915
|
+
this.finishChallengeLifecycle();
|
|
5916
|
+
this.eventEmitter.emit(GPAYMENTS_EVENT.ERROR, this.parseError((_c = authenticationResult.error) !== null && _c !== void 0 ? _c : authenticationResult, charge_3ds_id));
|
|
5917
|
+
} else {
|
|
5918
|
+
this.finishChallengeLifecycle();
|
|
5919
|
+
this.eventEmitter.emit(GPAYMENTS_EVENT.AUTH_ERROR, this.parseHandleResponse(authenticationResult, charge_3ds_id));
|
|
5920
|
+
}
|
|
5588
5921
|
}
|
|
5589
5922
|
}]);
|
|
5590
5923
|
}();
|
|
@@ -5611,10 +5944,27 @@ var Standalone3dsService = /*#__PURE__*/function () {
|
|
|
5611
5944
|
api.setEnv(this.env, this.alias);
|
|
5612
5945
|
switch (tokenData.service_type) {
|
|
5613
5946
|
case STANDALONE_3DS_GATEWAYS.GPAYMENTS:
|
|
5614
|
-
new GPaymentsService(this.container, api, this.eventEmitter).load(tokenData, options.title);
|
|
5947
|
+
new GPaymentsService(this.container, api, this.eventEmitter, this.getTrustedMessageOrigins(api)).load(tokenData, options.title);
|
|
5615
5948
|
break;
|
|
5616
5949
|
}
|
|
5617
5950
|
}
|
|
5951
|
+
/**
|
|
5952
|
+
* Origins allowed to deliver a processed 3DS outcome to the page via postMessage: the Paydock API
|
|
5953
|
+
* (inline callback page) and the Paydock widget (remote-app landing page), for the current env/alias.
|
|
5954
|
+
*/
|
|
5955
|
+
}, {
|
|
5956
|
+
key: "getTrustedMessageOrigins",
|
|
5957
|
+
value: function getTrustedMessageOrigins(api) {
|
|
5958
|
+
var origins = [api.getApiOrigin()];
|
|
5959
|
+
try {
|
|
5960
|
+
var widgetEnv = new Env(WIDGET_URL, this.env);
|
|
5961
|
+
widgetEnv.setEnv(this.env, this.alias);
|
|
5962
|
+
origins.push(ApiBase.toOrigin(widgetEnv.getConf().url));
|
|
5963
|
+
} catch (_a) {
|
|
5964
|
+
// widget origin unknown for this env — the API origin is enough
|
|
5965
|
+
}
|
|
5966
|
+
return origins;
|
|
5967
|
+
}
|
|
5618
5968
|
}, {
|
|
5619
5969
|
key: "setEnv",
|
|
5620
5970
|
value: function setEnv(env, alias) {
|
|
@@ -5650,12 +6000,12 @@ var Uuid = /*#__PURE__*/function () {
|
|
|
5650
6000
|
return _createClass(Uuid, null, [{
|
|
5651
6001
|
key: "generate",
|
|
5652
6002
|
value: function generate() {
|
|
5653
|
-
if (typeof window !==
|
|
6003
|
+
if (typeof window !== 'undefined' && typeof window.crypto !== 'undefined' && typeof window.crypto.getRandomValues !== 'undefined') {
|
|
5654
6004
|
var buf = new Uint16Array(8);
|
|
5655
6005
|
window.crypto.getRandomValues(buf);
|
|
5656
|
-
return
|
|
6006
|
+
return Uuid.hash(buf[0]) + Uuid.hash(buf[1]) + '-' + Uuid.hash(buf[2]) + '-' + Uuid.hash(buf[3]) + '-' + Uuid.hash(buf[4]) + '-' + Uuid.hash(buf[5]) + Uuid.hash(buf[6]) + Uuid.hash(buf[7]);
|
|
5657
6007
|
} else {
|
|
5658
|
-
return
|
|
6008
|
+
return Uuid.random() + Uuid.random() + '-' + Uuid.random() + '-' + Uuid.random() + '-' + Uuid.random() + '-' + Uuid.random() + Uuid.random() + Uuid.random();
|
|
5659
6009
|
}
|
|
5660
6010
|
}
|
|
5661
6011
|
}, {
|
|
@@ -5663,14 +6013,22 @@ var Uuid = /*#__PURE__*/function () {
|
|
|
5663
6013
|
value: function hash(number) {
|
|
5664
6014
|
var hash = number.toString(16);
|
|
5665
6015
|
while (hash.length < 4) {
|
|
5666
|
-
hash =
|
|
6016
|
+
hash = '0' + hash;
|
|
5667
6017
|
}
|
|
5668
6018
|
return hash;
|
|
5669
6019
|
}
|
|
5670
6020
|
}, {
|
|
5671
6021
|
key: "random",
|
|
5672
6022
|
value: function random() {
|
|
5673
|
-
|
|
6023
|
+
var value;
|
|
6024
|
+
if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {
|
|
6025
|
+
var buf = new Uint16Array(1);
|
|
6026
|
+
crypto.getRandomValues(buf);
|
|
6027
|
+
value = buf[0] / 0x10000;
|
|
6028
|
+
} else {
|
|
6029
|
+
value = Math.random();
|
|
6030
|
+
}
|
|
6031
|
+
return Math.floor((1 + value) * 0x10000).toString(16).substring(1);
|
|
5674
6032
|
}
|
|
5675
6033
|
}]);
|
|
5676
6034
|
}();
|
|
@@ -5775,11 +6133,13 @@ var TOKEN_FORMAT$2;
|
|
|
5775
6133
|
* @const STANDALONE_3DS_EVENT
|
|
5776
6134
|
*
|
|
5777
6135
|
* @type {object}
|
|
5778
|
-
* @param {string}
|
|
5779
|
-
* @param {string}
|
|
5780
|
-
* @param {string}
|
|
5781
|
-
* @param {string}
|
|
5782
|
-
* @param {string}
|
|
6136
|
+
* @param {string} AUTH_SUCCESS=chargeAuthSuccess
|
|
6137
|
+
* @param {string} AUTH_ERROR=chargeAuthReject
|
|
6138
|
+
* @param {string} DECOUPLED=chargeAuthDecoupled
|
|
6139
|
+
* @param {string} CHALLENGE=chargeAuthChallenge
|
|
6140
|
+
* @param {string} CHALLENGE_LOADED=chargeAuthChallengeLoaded - challenge iframe painted (first `load`, or 2.5 s fallback); reveal your challenge UI here
|
|
6141
|
+
* @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
|
|
6142
|
+
* @param {string} INFO=chargeAuthInfo
|
|
5783
6143
|
* @param {string} ERROR=error
|
|
5784
6144
|
*/
|
|
5785
6145
|
/**
|
|
@@ -6161,6 +6521,24 @@ var SUPPORTED_CARD_TYPES = {
|
|
|
6161
6521
|
EFTPOS_WHITE: 'eftpos_white',
|
|
6162
6522
|
UNIONPAY: 'unionpay'
|
|
6163
6523
|
};
|
|
6524
|
+
/**
|
|
6525
|
+
* Card schemes the widget is able to resolve from a card number, and therefore the only values
|
|
6526
|
+
* `setAllowedCardSchemes` accepts. This is a separate vocabulary from SUPPORTED_CARD_TYPES, which drives the
|
|
6527
|
+
* card icons: it omits the icon-only variants `visa_white` / `eftpos_white`, and the `laser` / `maestro`
|
|
6528
|
+
* identifiers the widget can never resolve — allowing those would be configuration that can never match.
|
|
6529
|
+
*/
|
|
6530
|
+
var CARD_SCHEME = {
|
|
6531
|
+
AMEX: 'amex',
|
|
6532
|
+
AUSBC: 'ausbc',
|
|
6533
|
+
DINERS: 'diners',
|
|
6534
|
+
DISCOVER: 'discover',
|
|
6535
|
+
EFTPOS: 'eftpos',
|
|
6536
|
+
JAPCB: 'japcb',
|
|
6537
|
+
MASTERCARD: 'mastercard',
|
|
6538
|
+
SOLO: 'solo',
|
|
6539
|
+
UNIONPAY: 'unionpay',
|
|
6540
|
+
VISA: 'visa'
|
|
6541
|
+
};
|
|
6164
6542
|
var SUPPORTED_CHECKOUT_META_COLLECTION = [].concat([
|
|
6165
6543
|
// Paypal
|
|
6166
6544
|
'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'], [
|
|
@@ -8345,7 +8723,7 @@ var AfterPayWalletService = /*#__PURE__*/function (_WalletService) {
|
|
|
8345
8723
|
}]);
|
|
8346
8724
|
}(WalletService);
|
|
8347
8725
|
|
|
8348
|
-
/*! @license DOMPurify 3.4.
|
|
8726
|
+
/*! @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 */
|
|
8349
8727
|
|
|
8350
8728
|
var purify_cjs;
|
|
8351
8729
|
var hasRequiredPurify_cjs;
|
|
@@ -8354,22 +8732,63 @@ function requirePurify_cjs () {
|
|
|
8354
8732
|
if (hasRequiredPurify_cjs) return purify_cjs;
|
|
8355
8733
|
hasRequiredPurify_cjs = 1;
|
|
8356
8734
|
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
|
|
8735
|
+
function _arrayLikeToArray(r, a) {
|
|
8736
|
+
(null == a || a > r.length) && (a = r.length);
|
|
8737
|
+
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
|
|
8738
|
+
return n;
|
|
8739
|
+
}
|
|
8740
|
+
function _arrayWithHoles(r) {
|
|
8741
|
+
if (Array.isArray(r)) return r;
|
|
8742
|
+
}
|
|
8743
|
+
function _iterableToArrayLimit(r, l) {
|
|
8744
|
+
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
|
|
8745
|
+
if (null != t) {
|
|
8746
|
+
var e,
|
|
8747
|
+
n,
|
|
8748
|
+
i,
|
|
8749
|
+
u,
|
|
8750
|
+
a = [],
|
|
8751
|
+
f = true,
|
|
8752
|
+
o = false;
|
|
8753
|
+
try {
|
|
8754
|
+
if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
|
|
8755
|
+
} catch (r) {
|
|
8756
|
+
o = true, n = r;
|
|
8757
|
+
} finally {
|
|
8758
|
+
try {
|
|
8759
|
+
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
|
|
8760
|
+
} finally {
|
|
8761
|
+
if (o) throw n;
|
|
8762
|
+
}
|
|
8763
|
+
}
|
|
8764
|
+
return a;
|
|
8765
|
+
}
|
|
8766
|
+
}
|
|
8767
|
+
function _nonIterableRest() {
|
|
8768
|
+
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
8769
|
+
}
|
|
8770
|
+
function _slicedToArray(r, e) {
|
|
8771
|
+
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
|
|
8772
|
+
}
|
|
8773
|
+
function _unsupportedIterableToArray(r, a) {
|
|
8774
|
+
if (r) {
|
|
8775
|
+
if ("string" == typeof r) return _arrayLikeToArray(r, a);
|
|
8776
|
+
var t = {}.toString.call(r).slice(8, -1);
|
|
8777
|
+
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
|
|
8778
|
+
}
|
|
8779
|
+
}
|
|
8780
|
+
|
|
8781
|
+
const entries = Object.entries,
|
|
8782
|
+
setPrototypeOf = Object.setPrototypeOf,
|
|
8783
|
+
isFrozen = Object.isFrozen,
|
|
8784
|
+
getPrototypeOf = Object.getPrototypeOf,
|
|
8785
|
+
getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
8786
|
+
let freeze = Object.freeze,
|
|
8787
|
+
seal = Object.seal,
|
|
8788
|
+
create = Object.create; // eslint-disable-line import/no-mutable-exports
|
|
8789
|
+
let _ref = typeof Reflect !== 'undefined' && Reflect,
|
|
8790
|
+
apply = _ref.apply,
|
|
8791
|
+
construct = _ref.construct;
|
|
8373
8792
|
if (!freeze) {
|
|
8374
8793
|
freeze = function freeze(x) {
|
|
8375
8794
|
return x;
|
|
@@ -8401,13 +8820,19 @@ function requirePurify_cjs () {
|
|
|
8401
8820
|
const arrayPop = unapply(Array.prototype.pop);
|
|
8402
8821
|
const arrayPush = unapply(Array.prototype.push);
|
|
8403
8822
|
const arraySplice = unapply(Array.prototype.splice);
|
|
8823
|
+
const arrayIsArray = Array.isArray;
|
|
8404
8824
|
const stringToLowerCase = unapply(String.prototype.toLowerCase);
|
|
8405
8825
|
const stringToString = unapply(String.prototype.toString);
|
|
8406
8826
|
const stringMatch = unapply(String.prototype.match);
|
|
8407
8827
|
const stringReplace = unapply(String.prototype.replace);
|
|
8408
8828
|
const stringIndexOf = unapply(String.prototype.indexOf);
|
|
8409
8829
|
const stringTrim = unapply(String.prototype.trim);
|
|
8830
|
+
const numberToString = unapply(Number.prototype.toString);
|
|
8831
|
+
const booleanToString = unapply(Boolean.prototype.toString);
|
|
8832
|
+
const bigintToString = typeof BigInt === 'undefined' ? null : unapply(BigInt.prototype.toString);
|
|
8833
|
+
const symbolToString = typeof Symbol === 'undefined' ? null : unapply(Symbol.prototype.toString);
|
|
8410
8834
|
const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
|
|
8835
|
+
const objectToString = unapply(Object.prototype.toString);
|
|
8411
8836
|
const regExpTest = unapply(RegExp.prototype.test);
|
|
8412
8837
|
const typeErrorCreate = unconstruct(TypeError);
|
|
8413
8838
|
/**
|
|
@@ -8457,6 +8882,9 @@ function requirePurify_cjs () {
|
|
|
8457
8882
|
// Prevent prototype setters from intercepting set as a this value.
|
|
8458
8883
|
setPrototypeOf(set, null);
|
|
8459
8884
|
}
|
|
8885
|
+
if (!arrayIsArray(array)) {
|
|
8886
|
+
return set;
|
|
8887
|
+
}
|
|
8460
8888
|
let l = array.length;
|
|
8461
8889
|
while (l--) {
|
|
8462
8890
|
let element = array[l];
|
|
@@ -8497,10 +8925,13 @@ function requirePurify_cjs () {
|
|
|
8497
8925
|
*/
|
|
8498
8926
|
function clone(object) {
|
|
8499
8927
|
const newObject = create(null);
|
|
8500
|
-
for (const
|
|
8928
|
+
for (const _ref2 of entries(object)) {
|
|
8929
|
+
var _ref3 = _slicedToArray(_ref2, 2);
|
|
8930
|
+
const property = _ref3[0];
|
|
8931
|
+
const value = _ref3[1];
|
|
8501
8932
|
const isPropertyExist = objectHasOwnProperty(object, property);
|
|
8502
8933
|
if (isPropertyExist) {
|
|
8503
|
-
if (
|
|
8934
|
+
if (arrayIsArray(value)) {
|
|
8504
8935
|
newObject[property] = cleanArray(value);
|
|
8505
8936
|
} else if (value && typeof value === 'object' && value.constructor === Object) {
|
|
8506
8937
|
newObject[property] = clone(value);
|
|
@@ -8511,6 +8942,58 @@ function requirePurify_cjs () {
|
|
|
8511
8942
|
}
|
|
8512
8943
|
return newObject;
|
|
8513
8944
|
}
|
|
8945
|
+
/**
|
|
8946
|
+
* Convert non-node values into strings without depending on direct property access.
|
|
8947
|
+
*
|
|
8948
|
+
* @param value - The value to stringify.
|
|
8949
|
+
* @returns A string representation of the provided value.
|
|
8950
|
+
*/
|
|
8951
|
+
function stringifyValue(value) {
|
|
8952
|
+
switch (typeof value) {
|
|
8953
|
+
case 'string':
|
|
8954
|
+
{
|
|
8955
|
+
return value;
|
|
8956
|
+
}
|
|
8957
|
+
case 'number':
|
|
8958
|
+
{
|
|
8959
|
+
return numberToString(value);
|
|
8960
|
+
}
|
|
8961
|
+
case 'boolean':
|
|
8962
|
+
{
|
|
8963
|
+
return booleanToString(value);
|
|
8964
|
+
}
|
|
8965
|
+
case 'bigint':
|
|
8966
|
+
{
|
|
8967
|
+
return bigintToString ? bigintToString(value) : '0';
|
|
8968
|
+
}
|
|
8969
|
+
case 'symbol':
|
|
8970
|
+
{
|
|
8971
|
+
return symbolToString ? symbolToString(value) : 'Symbol()';
|
|
8972
|
+
}
|
|
8973
|
+
case 'undefined':
|
|
8974
|
+
{
|
|
8975
|
+
return objectToString(value);
|
|
8976
|
+
}
|
|
8977
|
+
case 'function':
|
|
8978
|
+
case 'object':
|
|
8979
|
+
{
|
|
8980
|
+
if (value === null) {
|
|
8981
|
+
return objectToString(value);
|
|
8982
|
+
}
|
|
8983
|
+
const valueAsRecord = value;
|
|
8984
|
+
const valueToString = lookupGetter(valueAsRecord, 'toString');
|
|
8985
|
+
if (typeof valueToString === 'function') {
|
|
8986
|
+
const stringified = valueToString(valueAsRecord);
|
|
8987
|
+
return typeof stringified === 'string' ? stringified : objectToString(stringified);
|
|
8988
|
+
}
|
|
8989
|
+
return objectToString(value);
|
|
8990
|
+
}
|
|
8991
|
+
default:
|
|
8992
|
+
{
|
|
8993
|
+
return objectToString(value);
|
|
8994
|
+
}
|
|
8995
|
+
}
|
|
8996
|
+
}
|
|
8514
8997
|
/**
|
|
8515
8998
|
* This method automatically checks if the prop is function or getter and behaves accordingly.
|
|
8516
8999
|
*
|
|
@@ -8536,6 +9019,14 @@ function requirePurify_cjs () {
|
|
|
8536
9019
|
}
|
|
8537
9020
|
return fallbackValue;
|
|
8538
9021
|
}
|
|
9022
|
+
function isRegex(value) {
|
|
9023
|
+
try {
|
|
9024
|
+
regExpTest(value, '');
|
|
9025
|
+
return true;
|
|
9026
|
+
} catch (_unused) {
|
|
9027
|
+
return false;
|
|
9028
|
+
}
|
|
9029
|
+
}
|
|
8539
9030
|
|
|
8540
9031
|
const html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'search', 'section', 'select', 'shadow', 'slot', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);
|
|
8541
9032
|
const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'enterkeyhint', 'exportparts', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'inputmode', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'part', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);
|
|
@@ -8551,15 +9042,14 @@ function requirePurify_cjs () {
|
|
|
8551
9042
|
const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
|
|
8552
9043
|
const text = freeze(['#text']);
|
|
8553
9044
|
|
|
8554
|
-
const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', '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'
|
|
8555
|
-
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']);
|
|
9045
|
+
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']);
|
|
9046
|
+
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']);
|
|
8556
9047
|
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']);
|
|
8557
9048
|
const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
|
|
8558
9049
|
|
|
8559
|
-
|
|
8560
|
-
const
|
|
8561
|
-
const
|
|
8562
|
-
const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex
|
|
9050
|
+
const MUSTACHE_EXPR = seal(/{{[\w\W]*|^[\w\W]*}}/g);
|
|
9051
|
+
const ERB_EXPR = seal(/<%[\w\W]*|^[\w\W]*%>/g);
|
|
9052
|
+
const TMPLIT_EXPR = seal(/\${[\w\W]*/g);
|
|
8563
9053
|
const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
|
|
8564
9054
|
const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
|
|
8565
9055
|
const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
|
|
@@ -8569,30 +9059,31 @@ function requirePurify_cjs () {
|
|
|
8569
9059
|
);
|
|
8570
9060
|
const DOCTYPE_NAME = seal(/^html$/i);
|
|
8571
9061
|
const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
|
|
9062
|
+
// Markup-significant character probes used by _sanitizeElements.
|
|
9063
|
+
// Shared module-level instances are safe despite the sticky /g flags:
|
|
9064
|
+
// unapply() resets lastIndex for RegExp receivers before every call.
|
|
9065
|
+
const ELEMENT_MARKUP_PROBE = seal(/<[/\w!]/g);
|
|
9066
|
+
const COMMENT_MARKUP_PROBE = seal(/<[/\w]/g);
|
|
9067
|
+
const FALLBACK_TAG_CLOSE = seal(/<\/no(script|embed|frames)/i);
|
|
9068
|
+
const SELF_CLOSING_TAG = seal(/\/>/i);
|
|
8572
9069
|
|
|
8573
|
-
var EXPRESSIONS = /*#__PURE__*/Object.freeze({
|
|
8574
|
-
__proto__: null,
|
|
8575
|
-
ARIA_ATTR: ARIA_ATTR,
|
|
8576
|
-
ATTR_WHITESPACE: ATTR_WHITESPACE,
|
|
8577
|
-
CUSTOM_ELEMENT: CUSTOM_ELEMENT,
|
|
8578
|
-
DATA_ATTR: DATA_ATTR,
|
|
8579
|
-
DOCTYPE_NAME: DOCTYPE_NAME,
|
|
8580
|
-
ERB_EXPR: ERB_EXPR,
|
|
8581
|
-
IS_ALLOWED_URI: IS_ALLOWED_URI,
|
|
8582
|
-
IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
|
|
8583
|
-
MUSTACHE_EXPR: MUSTACHE_EXPR,
|
|
8584
|
-
TMPLIT_EXPR: TMPLIT_EXPR
|
|
8585
|
-
});
|
|
8586
|
-
|
|
8587
|
-
/* eslint-disable @typescript-eslint/indent */
|
|
8588
9070
|
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
|
|
8589
9071
|
const NODE_TYPE = {
|
|
8590
9072
|
element: 1,
|
|
9073
|
+
attribute: 2,
|
|
8591
9074
|
text: 3,
|
|
9075
|
+
cdataSection: 4,
|
|
9076
|
+
entityReference: 5,
|
|
8592
9077
|
// Deprecated
|
|
8593
|
-
|
|
9078
|
+
entityNode: 6,
|
|
9079
|
+
// Deprecated
|
|
9080
|
+
processingInstruction: 7,
|
|
8594
9081
|
comment: 8,
|
|
8595
|
-
document: 9
|
|
9082
|
+
document: 9,
|
|
9083
|
+
documentType: 10,
|
|
9084
|
+
documentFragment: 11,
|
|
9085
|
+
notation: 12 // Deprecated
|
|
9086
|
+
};
|
|
8596
9087
|
const getGlobal = function getGlobal() {
|
|
8597
9088
|
return typeof window === 'undefined' ? null : window;
|
|
8598
9089
|
};
|
|
@@ -8647,10 +9138,25 @@ function requirePurify_cjs () {
|
|
|
8647
9138
|
uponSanitizeShadowNode: []
|
|
8648
9139
|
};
|
|
8649
9140
|
};
|
|
9141
|
+
/**
|
|
9142
|
+
* Resolve a set-valued configuration option: a fresh set built from
|
|
9143
|
+
* cfg[key] when it is an own array property (seeded with a clone of
|
|
9144
|
+
* options.base when given, case-normalized via options.transform),
|
|
9145
|
+
* the fallback set otherwise.
|
|
9146
|
+
*
|
|
9147
|
+
* @param cfg the cloned, prototype-free configuration object
|
|
9148
|
+
* @param key the configuration property to read
|
|
9149
|
+
* @param fallback the set to use when the option is absent or not an array
|
|
9150
|
+
* @param options transform and optional base set to merge into
|
|
9151
|
+
* @returns the resolved set
|
|
9152
|
+
*/
|
|
9153
|
+
const _resolveSetOption = function _resolveSetOption(cfg, key, fallback, options) {
|
|
9154
|
+
return objectHasOwnProperty(cfg, key) && arrayIsArray(cfg[key]) ? addToSet(options.base ? clone(options.base) : {}, cfg[key], options.transform) : fallback;
|
|
9155
|
+
};
|
|
8650
9156
|
function createDOMPurify() {
|
|
8651
9157
|
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
|
|
8652
9158
|
const DOMPurify = root => createDOMPurify(root);
|
|
8653
|
-
DOMPurify.version = '3.4.
|
|
9159
|
+
DOMPurify.version = '3.4.13';
|
|
8654
9160
|
DOMPurify.removed = [];
|
|
8655
9161
|
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
8656
9162
|
// Not running in a browser, provide a factory function
|
|
@@ -8658,28 +9164,30 @@ function requirePurify_cjs () {
|
|
|
8658
9164
|
DOMPurify.isSupported = false;
|
|
8659
9165
|
return DOMPurify;
|
|
8660
9166
|
}
|
|
8661
|
-
let
|
|
8662
|
-
document
|
|
8663
|
-
} = window;
|
|
9167
|
+
let document = window.document;
|
|
8664
9168
|
const originalDocument = document;
|
|
8665
9169
|
const currentScript = originalDocument.currentScript;
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
NamedNodeMap
|
|
8673
|
-
HTMLFormElement
|
|
8674
|
-
DOMParser,
|
|
8675
|
-
trustedTypes
|
|
8676
|
-
} = window;
|
|
9170
|
+
window.DocumentFragment;
|
|
9171
|
+
const HTMLTemplateElement = window.HTMLTemplateElement,
|
|
9172
|
+
Node = window.Node,
|
|
9173
|
+
Element = window.Element,
|
|
9174
|
+
NodeFilter = window.NodeFilter,
|
|
9175
|
+
_window$NamedNodeMap = window.NamedNodeMap;
|
|
9176
|
+
_window$NamedNodeMap === void 0 ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap;
|
|
9177
|
+
window.HTMLFormElement;
|
|
9178
|
+
const DOMParser = window.DOMParser,
|
|
9179
|
+
trustedTypes = window.trustedTypes;
|
|
8677
9180
|
const ElementPrototype = Element.prototype;
|
|
8678
9181
|
const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
|
|
8679
9182
|
const remove = lookupGetter(ElementPrototype, 'remove');
|
|
8680
9183
|
const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
|
|
8681
9184
|
const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
|
|
8682
9185
|
const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
|
|
9186
|
+
const getShadowRoot = lookupGetter(ElementPrototype, 'shadowRoot');
|
|
9187
|
+
const getAttributes = lookupGetter(ElementPrototype, 'attributes');
|
|
9188
|
+
const getNodeType = Node && Node.prototype ? lookupGetter(Node.prototype, 'nodeType') : null;
|
|
9189
|
+
const getNodeName = Node && Node.prototype ? lookupGetter(Node.prototype, 'nodeName') : null;
|
|
9190
|
+
const getOwnerDocument = Node && Node.prototype ? lookupGetter(Node.prototype, 'ownerDocument') : null;
|
|
8683
9191
|
// As per issue #47, the web-components registry is inherited by a
|
|
8684
9192
|
// new document created via createHTMLDocument. As per the spec
|
|
8685
9193
|
// (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
|
|
@@ -8694,33 +9202,74 @@ function requirePurify_cjs () {
|
|
|
8694
9202
|
}
|
|
8695
9203
|
let trustedTypesPolicy;
|
|
8696
9204
|
let emptyHTML = '';
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
9205
|
+
// The instance's own internal Trusted Types policy. Unlike a caller-supplied
|
|
9206
|
+
// `TRUSTED_TYPES_POLICY`, this is created at most once — Trusted Types throws
|
|
9207
|
+
// on duplicate policy names — and is the only policy allowed to persist
|
|
9208
|
+
// across configurations and survive `clearConfig()`.
|
|
9209
|
+
let defaultTrustedTypesPolicy;
|
|
9210
|
+
let defaultTrustedTypesPolicyResolved = false;
|
|
9211
|
+
// Tracks whether we are already inside a call to the configured Trusted Types
|
|
9212
|
+
// policy (`createHTML` or `createScriptURL`). If a supplied policy callback
|
|
9213
|
+
// itself calls `DOMPurify.sanitize` (the cause of #1422), `sanitize` would
|
|
9214
|
+
// re-enter the policy and recurse until the stack overflows. We detect that
|
|
9215
|
+
// re-entry and throw a clear, actionable error instead. The guard is shared
|
|
9216
|
+
// across both callbacks, because either one re-entering `sanitize` triggers
|
|
9217
|
+
// the same unbounded recursion.
|
|
9218
|
+
let IN_TRUSTED_TYPES_POLICY = 0;
|
|
9219
|
+
const _assertNotInTrustedTypesPolicy = function _assertNotInTrustedTypesPolicy() {
|
|
9220
|
+
if (IN_TRUSTED_TYPES_POLICY > 0) {
|
|
9221
|
+
throw typeErrorCreate('A configured TRUSTED_TYPES_POLICY callback (createHTML or ' + 'createScriptURL) must not call DOMPurify.sanitize, as that causes ' + 'infinite recursion. Do not pass a policy whose callbacks wrap ' + 'DOMPurify as TRUSTED_TYPES_POLICY; see the "DOMPurify and Trusted ' + 'Types" section of the README.');
|
|
9222
|
+
}
|
|
9223
|
+
};
|
|
9224
|
+
const _createTrustedHTML = function _createTrustedHTML(html) {
|
|
9225
|
+
_assertNotInTrustedTypesPolicy();
|
|
9226
|
+
IN_TRUSTED_TYPES_POLICY++;
|
|
9227
|
+
try {
|
|
9228
|
+
return trustedTypesPolicy.createHTML(html);
|
|
9229
|
+
} finally {
|
|
9230
|
+
IN_TRUSTED_TYPES_POLICY--;
|
|
9231
|
+
}
|
|
9232
|
+
};
|
|
9233
|
+
const _createTrustedScriptURL = function _createTrustedScriptURL(scriptUrl) {
|
|
9234
|
+
_assertNotInTrustedTypesPolicy();
|
|
9235
|
+
IN_TRUSTED_TYPES_POLICY++;
|
|
9236
|
+
try {
|
|
9237
|
+
return trustedTypesPolicy.createScriptURL(scriptUrl);
|
|
9238
|
+
} finally {
|
|
9239
|
+
IN_TRUSTED_TYPES_POLICY--;
|
|
9240
|
+
}
|
|
9241
|
+
};
|
|
9242
|
+
// Lazily resolve (and cache) the instance's internal default policy.
|
|
9243
|
+
// Resolution is attempted at most once: a successful `createPolicy` cannot be
|
|
9244
|
+
// repeated (Trusted Types throws on duplicate names), and a failed or
|
|
9245
|
+
// unsupported attempt must not be retried on every parse.
|
|
9246
|
+
const _getDefaultTrustedTypesPolicy = function _getDefaultTrustedTypesPolicy() {
|
|
9247
|
+
if (!defaultTrustedTypesPolicyResolved) {
|
|
9248
|
+
defaultTrustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
|
|
9249
|
+
defaultTrustedTypesPolicyResolved = true;
|
|
9250
|
+
}
|
|
9251
|
+
return defaultTrustedTypesPolicy;
|
|
9252
|
+
};
|
|
9253
|
+
const _document = document,
|
|
9254
|
+
implementation = _document.implementation,
|
|
9255
|
+
createNodeIterator = _document.createNodeIterator,
|
|
9256
|
+
createDocumentFragment = _document.createDocumentFragment,
|
|
9257
|
+
getElementsByTagName = _document.getElementsByTagName;
|
|
9258
|
+
const importNode = originalDocument.importNode;
|
|
8706
9259
|
let hooks = _createHooksMap();
|
|
8707
9260
|
/**
|
|
8708
9261
|
* Expose whether this browser supports running the full DOMPurify.
|
|
8709
9262
|
*/
|
|
8710
9263
|
DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
|
|
8711
|
-
const
|
|
8712
|
-
|
|
8713
|
-
|
|
8714
|
-
|
|
8715
|
-
|
|
8716
|
-
|
|
8717
|
-
|
|
8718
|
-
|
|
8719
|
-
|
|
8720
|
-
} = EXPRESSIONS;
|
|
8721
|
-
let {
|
|
8722
|
-
IS_ALLOWED_URI: IS_ALLOWED_URI$1
|
|
8723
|
-
} = EXPRESSIONS;
|
|
9264
|
+
const MUSTACHE_EXPR$1 = MUSTACHE_EXPR,
|
|
9265
|
+
ERB_EXPR$1 = ERB_EXPR,
|
|
9266
|
+
TMPLIT_EXPR$1 = TMPLIT_EXPR,
|
|
9267
|
+
DATA_ATTR$1 = DATA_ATTR,
|
|
9268
|
+
ARIA_ATTR$1 = ARIA_ATTR,
|
|
9269
|
+
IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA,
|
|
9270
|
+
ATTR_WHITESPACE$1 = ATTR_WHITESPACE,
|
|
9271
|
+
CUSTOM_ELEMENT$1 = CUSTOM_ELEMENT;
|
|
9272
|
+
let IS_ALLOWED_URI$1 = IS_ALLOWED_URI;
|
|
8724
9273
|
/**
|
|
8725
9274
|
* We consider the elements and attributes below to be safe. Ideally
|
|
8726
9275
|
* don't add any new ones but feel free to remove unwanted ones.
|
|
@@ -8797,6 +9346,13 @@ function requirePurify_cjs () {
|
|
|
8797
9346
|
let WHOLE_DOCUMENT = false;
|
|
8798
9347
|
/* Track whether config is already set on this instance of DOMPurify. */
|
|
8799
9348
|
let SET_CONFIG = false;
|
|
9349
|
+
/* Pristine allowlist bindings captured at setConfig() time. On the
|
|
9350
|
+
* persistent-config path sanitize() restores the sets from these before
|
|
9351
|
+
* the per-walk hook clone-guard, so a hook's in-call widening cannot
|
|
9352
|
+
* carry across calls. Null until setConfig() is called; reset by
|
|
9353
|
+
* clearConfig(). */
|
|
9354
|
+
let SET_CONFIG_ALLOWED_TAGS = null;
|
|
9355
|
+
let SET_CONFIG_ALLOWED_ATTR = null;
|
|
8800
9356
|
/* Decide if all elements (e.g. style, script) must be children of
|
|
8801
9357
|
* document.body. By default, browsers might move them to document.head */
|
|
8802
9358
|
let FORCE_BODY = false;
|
|
@@ -8839,7 +9395,17 @@ function requirePurify_cjs () {
|
|
|
8839
9395
|
let USE_PROFILES = {};
|
|
8840
9396
|
/* Tags to ignore content of when KEEP_CONTENT is true */
|
|
8841
9397
|
let FORBID_CONTENTS = null;
|
|
8842
|
-
const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script',
|
|
9398
|
+
const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script',
|
|
9399
|
+
// <selectedcontent> mirrors the selected <option>'s subtree, cloned by
|
|
9400
|
+
// the UA (customizable <select>) — including any on* handlers — and the
|
|
9401
|
+
// engine re-mirrors synchronously whenever a removal changes which
|
|
9402
|
+
// option/selectedcontent is current, even inside DOMPurify's inert
|
|
9403
|
+
// DOMParser document. Hoisting its children on removal re-inserts a fresh
|
|
9404
|
+
// mirror target ahead of the walk, which the engine refills, looping
|
|
9405
|
+
// forever (DoS) and amplifying output. Dropping its content on removal
|
|
9406
|
+
// (rather than hoisting) breaks that cascade; the content is a duplicate
|
|
9407
|
+
// of the option, which is sanitized on its own. See campaign-3 F1/F6.
|
|
9408
|
+
'selectedcontent', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
|
|
8843
9409
|
/* Tags that are safe for data: URIs */
|
|
8844
9410
|
let DATA_URI_TAGS = null;
|
|
8845
9411
|
const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
|
|
@@ -8855,8 +9421,10 @@ function requirePurify_cjs () {
|
|
|
8855
9421
|
/* Allowed XHTML+XML namespaces */
|
|
8856
9422
|
let ALLOWED_NAMESPACES = null;
|
|
8857
9423
|
const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
|
|
8858
|
-
|
|
8859
|
-
let
|
|
9424
|
+
const DEFAULT_MATHML_TEXT_INTEGRATION_POINTS = freeze(['mi', 'mo', 'mn', 'ms', 'mtext']);
|
|
9425
|
+
let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS);
|
|
9426
|
+
const DEFAULT_HTML_INTEGRATION_POINTS = freeze(['annotation-xml']);
|
|
9427
|
+
let HTML_INTEGRATION_POINTS = addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS);
|
|
8860
9428
|
// Certain elements are allowed in both SVG and HTML
|
|
8861
9429
|
// namespace. We need to specify them explicitly
|
|
8862
9430
|
// so that they don't get erroneously deleted from
|
|
@@ -8898,15 +9466,33 @@ function requirePurify_cjs () {
|
|
|
8898
9466
|
// HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
|
|
8899
9467
|
transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
|
|
8900
9468
|
/* Set configuration parameters */
|
|
8901
|
-
ALLOWED_TAGS =
|
|
8902
|
-
|
|
8903
|
-
|
|
8904
|
-
|
|
8905
|
-
|
|
8906
|
-
|
|
8907
|
-
|
|
8908
|
-
|
|
8909
|
-
|
|
9469
|
+
ALLOWED_TAGS = _resolveSetOption(cfg, 'ALLOWED_TAGS', DEFAULT_ALLOWED_TAGS, {
|
|
9470
|
+
transform: transformCaseFunc
|
|
9471
|
+
});
|
|
9472
|
+
ALLOWED_ATTR = _resolveSetOption(cfg, 'ALLOWED_ATTR', DEFAULT_ALLOWED_ATTR, {
|
|
9473
|
+
transform: transformCaseFunc
|
|
9474
|
+
});
|
|
9475
|
+
ALLOWED_NAMESPACES = _resolveSetOption(cfg, 'ALLOWED_NAMESPACES', DEFAULT_ALLOWED_NAMESPACES, {
|
|
9476
|
+
transform: stringToString
|
|
9477
|
+
});
|
|
9478
|
+
URI_SAFE_ATTRIBUTES = _resolveSetOption(cfg, 'ADD_URI_SAFE_ATTR', DEFAULT_URI_SAFE_ATTRIBUTES, {
|
|
9479
|
+
transform: transformCaseFunc,
|
|
9480
|
+
base: DEFAULT_URI_SAFE_ATTRIBUTES
|
|
9481
|
+
});
|
|
9482
|
+
DATA_URI_TAGS = _resolveSetOption(cfg, 'ADD_DATA_URI_TAGS', DEFAULT_DATA_URI_TAGS, {
|
|
9483
|
+
transform: transformCaseFunc,
|
|
9484
|
+
base: DEFAULT_DATA_URI_TAGS
|
|
9485
|
+
});
|
|
9486
|
+
FORBID_CONTENTS = _resolveSetOption(cfg, 'FORBID_CONTENTS', DEFAULT_FORBID_CONTENTS, {
|
|
9487
|
+
transform: transformCaseFunc
|
|
9488
|
+
});
|
|
9489
|
+
FORBID_TAGS = _resolveSetOption(cfg, 'FORBID_TAGS', clone({}), {
|
|
9490
|
+
transform: transformCaseFunc
|
|
9491
|
+
});
|
|
9492
|
+
FORBID_ATTR = _resolveSetOption(cfg, 'FORBID_ATTR', clone({}), {
|
|
9493
|
+
transform: transformCaseFunc
|
|
9494
|
+
});
|
|
9495
|
+
USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES && typeof cfg.USE_PROFILES === 'object' ? clone(cfg.USE_PROFILES) : cfg.USE_PROFILES : false;
|
|
8910
9496
|
ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
|
|
8911
9497
|
ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
|
|
8912
9498
|
ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
|
|
@@ -8922,20 +9508,22 @@ function requirePurify_cjs () {
|
|
|
8922
9508
|
SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
|
|
8923
9509
|
KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
|
|
8924
9510
|
IN_PLACE = cfg.IN_PLACE || false; // Default false
|
|
8925
|
-
IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP
|
|
8926
|
-
NAMESPACE = cfg.NAMESPACE
|
|
8927
|
-
MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS
|
|
8928
|
-
HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
|
|
9511
|
+
IS_ALLOWED_URI$1 = isRegex(cfg.ALLOWED_URI_REGEXP) ? cfg.ALLOWED_URI_REGEXP : IS_ALLOWED_URI; // Default regexp
|
|
9512
|
+
NAMESPACE = typeof cfg.NAMESPACE === 'string' ? cfg.NAMESPACE : HTML_NAMESPACE; // Default HTML namespace
|
|
9513
|
+
MATHML_TEXT_INTEGRATION_POINTS = objectHasOwnProperty(cfg, 'MATHML_TEXT_INTEGRATION_POINTS') && cfg.MATHML_TEXT_INTEGRATION_POINTS && typeof cfg.MATHML_TEXT_INTEGRATION_POINTS === 'object' ? clone(cfg.MATHML_TEXT_INTEGRATION_POINTS) : addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS); // Default built-in map
|
|
9514
|
+
HTML_INTEGRATION_POINTS = objectHasOwnProperty(cfg, 'HTML_INTEGRATION_POINTS') && cfg.HTML_INTEGRATION_POINTS && typeof cfg.HTML_INTEGRATION_POINTS === 'object' ? clone(cfg.HTML_INTEGRATION_POINTS) : addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS); // Default built-in map
|
|
9515
|
+
const customElementHandling = objectHasOwnProperty(cfg, 'CUSTOM_ELEMENT_HANDLING') && cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING === 'object' ? clone(cfg.CUSTOM_ELEMENT_HANDLING) : create(null);
|
|
9516
|
+
CUSTOM_ELEMENT_HANDLING = create(null);
|
|
9517
|
+
if (objectHasOwnProperty(customElementHandling, 'tagNameCheck') && isRegexOrFunction(customElementHandling.tagNameCheck)) {
|
|
9518
|
+
CUSTOM_ELEMENT_HANDLING.tagNameCheck = customElementHandling.tagNameCheck; // Default undefined
|
|
8932
9519
|
}
|
|
8933
|
-
if (
|
|
8934
|
-
CUSTOM_ELEMENT_HANDLING.attributeNameCheck =
|
|
9520
|
+
if (objectHasOwnProperty(customElementHandling, 'attributeNameCheck') && isRegexOrFunction(customElementHandling.attributeNameCheck)) {
|
|
9521
|
+
CUSTOM_ELEMENT_HANDLING.attributeNameCheck = customElementHandling.attributeNameCheck; // Default undefined
|
|
8935
9522
|
}
|
|
8936
|
-
if (
|
|
8937
|
-
CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements =
|
|
9523
|
+
if (objectHasOwnProperty(customElementHandling, 'allowCustomizedBuiltInElements') && typeof customElementHandling.allowCustomizedBuiltInElements === 'boolean') {
|
|
9524
|
+
CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = customElementHandling.allowCustomizedBuiltInElements; // Default undefined
|
|
8938
9525
|
}
|
|
9526
|
+
seal(CUSTOM_ELEMENT_HANDLING);
|
|
8939
9527
|
if (SAFE_FOR_TEMPLATES) {
|
|
8940
9528
|
ALLOW_DATA_ATTR = false;
|
|
8941
9529
|
}
|
|
@@ -8971,36 +9559,36 @@ function requirePurify_cjs () {
|
|
|
8971
9559
|
EXTRA_ELEMENT_HANDLING.tagCheck = null;
|
|
8972
9560
|
EXTRA_ELEMENT_HANDLING.attributeCheck = null;
|
|
8973
9561
|
/* Merge configuration parameters */
|
|
8974
|
-
if (cfg
|
|
9562
|
+
if (objectHasOwnProperty(cfg, 'ADD_TAGS')) {
|
|
8975
9563
|
if (typeof cfg.ADD_TAGS === 'function') {
|
|
8976
9564
|
EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;
|
|
8977
|
-
} else {
|
|
9565
|
+
} else if (arrayIsArray(cfg.ADD_TAGS)) {
|
|
8978
9566
|
if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
|
|
8979
9567
|
ALLOWED_TAGS = clone(ALLOWED_TAGS);
|
|
8980
9568
|
}
|
|
8981
9569
|
addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
|
|
8982
9570
|
}
|
|
8983
9571
|
}
|
|
8984
|
-
if (cfg
|
|
9572
|
+
if (objectHasOwnProperty(cfg, 'ADD_ATTR')) {
|
|
8985
9573
|
if (typeof cfg.ADD_ATTR === 'function') {
|
|
8986
9574
|
EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;
|
|
8987
|
-
} else {
|
|
9575
|
+
} else if (arrayIsArray(cfg.ADD_ATTR)) {
|
|
8988
9576
|
if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
|
|
8989
9577
|
ALLOWED_ATTR = clone(ALLOWED_ATTR);
|
|
8990
9578
|
}
|
|
8991
9579
|
addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
|
|
8992
9580
|
}
|
|
8993
9581
|
}
|
|
8994
|
-
if (cfg.ADD_URI_SAFE_ATTR) {
|
|
9582
|
+
if (objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') && arrayIsArray(cfg.ADD_URI_SAFE_ATTR)) {
|
|
8995
9583
|
addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
|
|
8996
9584
|
}
|
|
8997
|
-
if (cfg.FORBID_CONTENTS) {
|
|
9585
|
+
if (objectHasOwnProperty(cfg, 'FORBID_CONTENTS') && arrayIsArray(cfg.FORBID_CONTENTS)) {
|
|
8998
9586
|
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
|
|
8999
9587
|
FORBID_CONTENTS = clone(FORBID_CONTENTS);
|
|
9000
9588
|
}
|
|
9001
9589
|
addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
|
|
9002
9590
|
}
|
|
9003
|
-
if (cfg.ADD_FORBID_CONTENTS) {
|
|
9591
|
+
if (objectHasOwnProperty(cfg, 'ADD_FORBID_CONTENTS') && arrayIsArray(cfg.ADD_FORBID_CONTENTS)) {
|
|
9004
9592
|
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
|
|
9005
9593
|
FORBID_CONTENTS = clone(FORBID_CONTENTS);
|
|
9006
9594
|
}
|
|
@@ -9019,6 +9607,13 @@ function requirePurify_cjs () {
|
|
|
9019
9607
|
addToSet(ALLOWED_TAGS, ['tbody']);
|
|
9020
9608
|
delete FORBID_TAGS.tbody;
|
|
9021
9609
|
}
|
|
9610
|
+
// Re-derive the active Trusted Types policy from this configuration on
|
|
9611
|
+
// every parse. The active policy must never be sticky closure state that
|
|
9612
|
+
// outlives the config that set it: a caller-supplied policy left in place
|
|
9613
|
+
// after `clearConfig()` — or after a later call that supplied none, or
|
|
9614
|
+
// `TRUSTED_TYPES_POLICY: null` — could sign a subsequent "default"
|
|
9615
|
+
// `RETURN_TRUSTED_TYPE` result with a foreign, possibly unsafe policy.
|
|
9616
|
+
// See GHSA-vxr8-fq34-vvx9.
|
|
9022
9617
|
if (cfg.TRUSTED_TYPES_POLICY) {
|
|
9023
9618
|
if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {
|
|
9024
9619
|
throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');
|
|
@@ -9026,18 +9621,45 @@ function requirePurify_cjs () {
|
|
|
9026
9621
|
if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {
|
|
9027
9622
|
throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
|
|
9028
9623
|
}
|
|
9029
|
-
//
|
|
9624
|
+
// A caller-supplied policy applies to this configuration only.
|
|
9625
|
+
const previousTrustedTypesPolicy = trustedTypesPolicy;
|
|
9030
9626
|
trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
|
|
9031
|
-
// Sign local variables required by `sanitize`.
|
|
9032
|
-
|
|
9627
|
+
// Sign local variables required by `sanitize`. If the supplied policy's
|
|
9628
|
+
// `createHTML` is circular (i.e. it calls `DOMPurify.sanitize`), this
|
|
9629
|
+
// throws via the re-entrancy guard. Restore the previous policy first so
|
|
9630
|
+
// the instance is not left in a poisoned state. See #1422.
|
|
9631
|
+
try {
|
|
9632
|
+
emptyHTML = _createTrustedHTML('');
|
|
9633
|
+
} catch (error) {
|
|
9634
|
+
trustedTypesPolicy = previousTrustedTypesPolicy;
|
|
9635
|
+
throw error;
|
|
9636
|
+
}
|
|
9637
|
+
} else if (cfg.TRUSTED_TYPES_POLICY === null) {
|
|
9638
|
+
// Explicit opt-out for this call: perform no Trusted Types signing and
|
|
9639
|
+
// create nothing (so a strict `trusted-types` CSP that disallows a
|
|
9640
|
+
// `dompurify` policy can still call `sanitize` from inside its own
|
|
9641
|
+
// policy — see #1422). Resetting to `undefined` rather than a sticky
|
|
9642
|
+
// `null` also drops any previously retained caller policy, so it cannot
|
|
9643
|
+
// resurface on a later call, while still allowing the next config-less
|
|
9644
|
+
// call to restore the internal default policy. See GHSA-vxr8-fq34-vvx9.
|
|
9645
|
+
trustedTypesPolicy = undefined;
|
|
9646
|
+
emptyHTML = '';
|
|
9033
9647
|
} else {
|
|
9034
|
-
//
|
|
9648
|
+
// No policy supplied: keep the currently active policy if one is set — a
|
|
9649
|
+
// previously supplied policy is intentionally sticky across config-less
|
|
9650
|
+
// calls — otherwise fall back to the instance's own internal policy,
|
|
9651
|
+
// created at most once. (A policy supplied for a *single* call still
|
|
9652
|
+
// lingers by design; what must not linger is a policy whose configuration
|
|
9653
|
+
// has been torn down via `clearConfig()`, which restores the default.)
|
|
9035
9654
|
if (trustedTypesPolicy === undefined) {
|
|
9036
|
-
trustedTypesPolicy =
|
|
9655
|
+
trustedTypesPolicy = _getDefaultTrustedTypesPolicy();
|
|
9037
9656
|
}
|
|
9038
|
-
//
|
|
9039
|
-
|
|
9040
|
-
|
|
9657
|
+
// Sign internal variables only when a policy is active. A falsy policy
|
|
9658
|
+
// (Trusted Types unsupported, creation failed, or an explicit opt-out)
|
|
9659
|
+
// leaves `emptyHTML` as a plain string, so we never call `.createHTML` on
|
|
9660
|
+
// a non-policy and throw. See #1422.
|
|
9661
|
+
if (trustedTypesPolicy && typeof emptyHTML === 'string') {
|
|
9662
|
+
emptyHTML = _createTrustedHTML('');
|
|
9041
9663
|
}
|
|
9042
9664
|
}
|
|
9043
9665
|
// Prevent further manipulation of configuration.
|
|
@@ -9052,6 +9674,77 @@ function requirePurify_cjs () {
|
|
|
9052
9674
|
* correctly. */
|
|
9053
9675
|
const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
|
|
9054
9676
|
const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
|
|
9677
|
+
/**
|
|
9678
|
+
* Namespace rules for an element in the SVG namespace.
|
|
9679
|
+
*
|
|
9680
|
+
* @param tagName the element's lowercase tag name
|
|
9681
|
+
* @param parent the (possibly simulated) parent node
|
|
9682
|
+
* @param parentTagName the parent's lowercase tag name
|
|
9683
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
9684
|
+
*/
|
|
9685
|
+
const _checkSvgNamespace = function _checkSvgNamespace(tagName, parent, parentTagName) {
|
|
9686
|
+
// The only way to switch from HTML namespace to SVG
|
|
9687
|
+
// is via <svg>. If it happens via any other tag, then
|
|
9688
|
+
// it should be killed.
|
|
9689
|
+
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
9690
|
+
return tagName === 'svg';
|
|
9691
|
+
}
|
|
9692
|
+
// The only way to switch from MathML to SVG is via <svg>
|
|
9693
|
+
// if the parent is either <annotation-xml> or a MathML
|
|
9694
|
+
// text integration point.
|
|
9695
|
+
if (parent.namespaceURI === MATHML_NAMESPACE) {
|
|
9696
|
+
return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
|
|
9697
|
+
}
|
|
9698
|
+
// We only allow elements that are defined in SVG
|
|
9699
|
+
// spec. All others are disallowed in SVG namespace.
|
|
9700
|
+
return Boolean(ALL_SVG_TAGS[tagName]);
|
|
9701
|
+
};
|
|
9702
|
+
/**
|
|
9703
|
+
* Namespace rules for an element in the MathML namespace.
|
|
9704
|
+
*
|
|
9705
|
+
* @param tagName the element's lowercase tag name
|
|
9706
|
+
* @param parent the (possibly simulated) parent node
|
|
9707
|
+
* @param parentTagName the parent's lowercase tag name
|
|
9708
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
9709
|
+
*/
|
|
9710
|
+
const _checkMathMlNamespace = function _checkMathMlNamespace(tagName, parent, parentTagName) {
|
|
9711
|
+
// The only way to switch from HTML namespace to MathML
|
|
9712
|
+
// is via <math>. If it happens via any other tag, then
|
|
9713
|
+
// it should be killed.
|
|
9714
|
+
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
9715
|
+
return tagName === 'math';
|
|
9716
|
+
}
|
|
9717
|
+
// The only way to switch from SVG to MathML is via
|
|
9718
|
+
// <math> and HTML integration points
|
|
9719
|
+
if (parent.namespaceURI === SVG_NAMESPACE) {
|
|
9720
|
+
return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
|
|
9721
|
+
}
|
|
9722
|
+
// We only allow elements that are defined in MathML
|
|
9723
|
+
// spec. All others are disallowed in MathML namespace.
|
|
9724
|
+
return Boolean(ALL_MATHML_TAGS[tagName]);
|
|
9725
|
+
};
|
|
9726
|
+
/**
|
|
9727
|
+
* Namespace rules for an element in the HTML namespace.
|
|
9728
|
+
*
|
|
9729
|
+
* @param tagName the element's lowercase tag name
|
|
9730
|
+
* @param parent the (possibly simulated) parent node
|
|
9731
|
+
* @param parentTagName the parent's lowercase tag name
|
|
9732
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
9733
|
+
*/
|
|
9734
|
+
const _checkHtmlNamespace = function _checkHtmlNamespace(tagName, parent, parentTagName) {
|
|
9735
|
+
// The only way to switch from SVG to HTML is via
|
|
9736
|
+
// HTML integration points, and from MathML to HTML
|
|
9737
|
+
// is via MathML text integration points
|
|
9738
|
+
if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
|
|
9739
|
+
return false;
|
|
9740
|
+
}
|
|
9741
|
+
if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
|
|
9742
|
+
return false;
|
|
9743
|
+
}
|
|
9744
|
+
// We disallow tags that are specific for MathML
|
|
9745
|
+
// or SVG and should never appear in HTML namespace
|
|
9746
|
+
return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
|
|
9747
|
+
};
|
|
9055
9748
|
/**
|
|
9056
9749
|
* @param element a DOM element whose namespace is being checked
|
|
9057
9750
|
* @returns Return false if the element has a
|
|
@@ -9074,51 +9767,13 @@ function requirePurify_cjs () {
|
|
|
9074
9767
|
return false;
|
|
9075
9768
|
}
|
|
9076
9769
|
if (element.namespaceURI === SVG_NAMESPACE) {
|
|
9077
|
-
|
|
9078
|
-
// is via <svg>. If it happens via any other tag, then
|
|
9079
|
-
// it should be killed.
|
|
9080
|
-
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
9081
|
-
return tagName === 'svg';
|
|
9082
|
-
}
|
|
9083
|
-
// The only way to switch from MathML to SVG is via`
|
|
9084
|
-
// svg if parent is either <annotation-xml> or MathML
|
|
9085
|
-
// text integration points.
|
|
9086
|
-
if (parent.namespaceURI === MATHML_NAMESPACE) {
|
|
9087
|
-
return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
|
|
9088
|
-
}
|
|
9089
|
-
// We only allow elements that are defined in SVG
|
|
9090
|
-
// spec. All others are disallowed in SVG namespace.
|
|
9091
|
-
return Boolean(ALL_SVG_TAGS[tagName]);
|
|
9770
|
+
return _checkSvgNamespace(tagName, parent, parentTagName);
|
|
9092
9771
|
}
|
|
9093
9772
|
if (element.namespaceURI === MATHML_NAMESPACE) {
|
|
9094
|
-
|
|
9095
|
-
// is via <math>. If it happens via any other tag, then
|
|
9096
|
-
// it should be killed.
|
|
9097
|
-
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
9098
|
-
return tagName === 'math';
|
|
9099
|
-
}
|
|
9100
|
-
// The only way to switch from SVG to MathML is via
|
|
9101
|
-
// <math> and HTML integration points
|
|
9102
|
-
if (parent.namespaceURI === SVG_NAMESPACE) {
|
|
9103
|
-
return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
|
|
9104
|
-
}
|
|
9105
|
-
// We only allow elements that are defined in MathML
|
|
9106
|
-
// spec. All others are disallowed in MathML namespace.
|
|
9107
|
-
return Boolean(ALL_MATHML_TAGS[tagName]);
|
|
9773
|
+
return _checkMathMlNamespace(tagName, parent, parentTagName);
|
|
9108
9774
|
}
|
|
9109
9775
|
if (element.namespaceURI === HTML_NAMESPACE) {
|
|
9110
|
-
|
|
9111
|
-
// HTML integration points, and from MathML to HTML
|
|
9112
|
-
// is via MathML text integration points
|
|
9113
|
-
if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
|
|
9114
|
-
return false;
|
|
9115
|
-
}
|
|
9116
|
-
if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
|
|
9117
|
-
return false;
|
|
9118
|
-
}
|
|
9119
|
-
// We disallow tags that are specific for MathML
|
|
9120
|
-
// or SVG and should never appear in HTML namespace
|
|
9121
|
-
return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
|
|
9776
|
+
return _checkHtmlNamespace(tagName, parent, parentTagName);
|
|
9122
9777
|
}
|
|
9123
9778
|
// For XHTML and XML documents that support custom namespaces
|
|
9124
9779
|
if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
|
|
@@ -9143,7 +9798,81 @@ function requirePurify_cjs () {
|
|
|
9143
9798
|
// eslint-disable-next-line unicorn/prefer-dom-node-remove
|
|
9144
9799
|
getParentNode(node).removeChild(node);
|
|
9145
9800
|
} catch (_) {
|
|
9801
|
+
/* The normal detach failed — this is reached for a parentless node
|
|
9802
|
+
(getParentNode() is null, so .removeChild throws). Element.prototype
|
|
9803
|
+
.remove() is itself a spec no-op on a parentless node, so a recorded
|
|
9804
|
+
"removal" would otherwise hand the caller back an intact,
|
|
9805
|
+
payload-bearing node (e.g. a detached IN_PLACE root the mXSS canary or
|
|
9806
|
+
the style-with-element-child rule decided to kill). Fail closed by
|
|
9807
|
+
throwing — exactly as a clobbered root does at the IN_PLACE entry —
|
|
9808
|
+
rather than trying to "neutralize" the node via its own methods.
|
|
9809
|
+
Neutralizing would mean calling getAttributeNames()/removeAttribute()
|
|
9810
|
+
on the node, both of which a <form> root can clobber via a named child
|
|
9811
|
+
(and _isClobbered does not even probe getAttributeNames), so the
|
|
9812
|
+
neutralize step could itself be silently defeated, leaving the payload
|
|
9813
|
+
intact. A throw touches only the cached, clobber-safe remove() and
|
|
9814
|
+
getParentNode(). Generalizes GHSA-r47g-fvhr-h676 (clobbered-form root)
|
|
9815
|
+
to every root-kill reason. REPORT-3.
|
|
9816
|
+
This lives inside the catch, so it never fires for a normally-removed
|
|
9817
|
+
in-tree node: those have a parent, removeChild() succeeds, and the
|
|
9818
|
+
catch is not entered. Only a kept (parentless) root reaches here. */
|
|
9146
9819
|
remove(node);
|
|
9820
|
+
if (!getParentNode(node)) {
|
|
9821
|
+
throw typeErrorCreate('a node selected for removal could not be detached from its tree ' + 'and cannot be safely returned; refusing to sanitize in place');
|
|
9822
|
+
}
|
|
9823
|
+
}
|
|
9824
|
+
};
|
|
9825
|
+
/**
|
|
9826
|
+
* _neutralizeRoot
|
|
9827
|
+
*
|
|
9828
|
+
* Fail-closed teardown of an in-place root after the sanitize walk aborts
|
|
9829
|
+
* (campaign-3 F2). An internal throw mid-walk — e.g. a page-registered
|
|
9830
|
+
* custom element's reaction detaches a node so `_forceRemove`'s deliberate
|
|
9831
|
+
* parentless guard throws, or any other re-entrant engine mutation — would
|
|
9832
|
+
* otherwise leave the caller's *live* tree half-sanitized, with everything
|
|
9833
|
+
* after the abort point still carrying its handlers. There is no safe way
|
|
9834
|
+
* to resume the walk (the tree mutated under us), so we strip the root bare:
|
|
9835
|
+
* remove every child and every attribute, then let the caller's catch see
|
|
9836
|
+
* the original error. Clobber-safe (cached `remove`/`childNodes`/`attributes`
|
|
9837
|
+
* getters; the root was already clobber-pre-flighted at the IN_PLACE entry).
|
|
9838
|
+
*
|
|
9839
|
+
* @param root the in-place root to empty
|
|
9840
|
+
*/
|
|
9841
|
+
const _neutralizeRoot = function _neutralizeRoot(root) {
|
|
9842
|
+
/* Strip every disallowed attribute (on* handlers included) off the whole
|
|
9843
|
+
subtree BEFORE detaching anything. Detaching first would hand back
|
|
9844
|
+
handler-bearing originals (e.g. an already-loading `<img onerror>`)
|
|
9845
|
+
whose queued resource event still fires in page scope after we throw.
|
|
9846
|
+
Clobber-safe reads; a doomed clobbered node's own attributes are
|
|
9847
|
+
irrelevant while its non-clobbered descendants are reached and scrubbed. */
|
|
9848
|
+
_neutralizeSubtree(root);
|
|
9849
|
+
const childNodes = getChildNodes(root);
|
|
9850
|
+
if (childNodes) {
|
|
9851
|
+
const snapshot = [];
|
|
9852
|
+
arrayForEach(childNodes, child => {
|
|
9853
|
+
arrayPush(snapshot, child);
|
|
9854
|
+
});
|
|
9855
|
+
arrayForEach(snapshot, child => {
|
|
9856
|
+
try {
|
|
9857
|
+
remove(child);
|
|
9858
|
+
} catch (_) {
|
|
9859
|
+
/* Best-effort teardown; a still-attached child is handled below */
|
|
9860
|
+
}
|
|
9861
|
+
});
|
|
9862
|
+
}
|
|
9863
|
+
const attributes = getAttributes(root);
|
|
9864
|
+
if (attributes) {
|
|
9865
|
+
for (let i = attributes.length - 1; i >= 0; --i) {
|
|
9866
|
+
const attribute = attributes[i];
|
|
9867
|
+
const name = attribute && attribute.name;
|
|
9868
|
+
if (typeof name === 'string') {
|
|
9869
|
+
try {
|
|
9870
|
+
root.removeAttribute(name);
|
|
9871
|
+
} catch (_) {
|
|
9872
|
+
/* Clobbered removeAttribute — ignore (fail-closed best effort) */
|
|
9873
|
+
}
|
|
9874
|
+
}
|
|
9875
|
+
}
|
|
9147
9876
|
}
|
|
9148
9877
|
};
|
|
9149
9878
|
/**
|
|
@@ -9178,6 +9907,148 @@ function requirePurify_cjs () {
|
|
|
9178
9907
|
}
|
|
9179
9908
|
}
|
|
9180
9909
|
};
|
|
9910
|
+
/**
|
|
9911
|
+
* _stripDisallowedAttributes
|
|
9912
|
+
*
|
|
9913
|
+
* Removes every attribute the active configuration does not allow from a
|
|
9914
|
+
* single element, using the same allowlist as the main attribute pass (so
|
|
9915
|
+
* `on*` handlers go, but no `/^on/` blocklist is introduced). Used only to
|
|
9916
|
+
* neutralise nodes that are being discarded from an in-place tree.
|
|
9917
|
+
*
|
|
9918
|
+
* @param element the element to strip
|
|
9919
|
+
*/
|
|
9920
|
+
const _stripDisallowedAttributes = function _stripDisallowedAttributes(element) {
|
|
9921
|
+
const attributes = getAttributes(element);
|
|
9922
|
+
if (!attributes) {
|
|
9923
|
+
return;
|
|
9924
|
+
}
|
|
9925
|
+
for (let i = attributes.length - 1; i >= 0; --i) {
|
|
9926
|
+
const attribute = attributes[i];
|
|
9927
|
+
const name = attribute && attribute.name;
|
|
9928
|
+
if (typeof name !== 'string' || ALLOWED_ATTR[transformCaseFunc(name)]) {
|
|
9929
|
+
continue;
|
|
9930
|
+
}
|
|
9931
|
+
try {
|
|
9932
|
+
element.removeAttribute(name);
|
|
9933
|
+
} catch (_) {
|
|
9934
|
+
/* Clobbered removeAttribute on a doomed node — ignore */
|
|
9935
|
+
}
|
|
9936
|
+
}
|
|
9937
|
+
};
|
|
9938
|
+
/**
|
|
9939
|
+
* _neutralizeSubtree
|
|
9940
|
+
*
|
|
9941
|
+
* Completes the audit-5 F1 fix across every removal path. The KEEP_CONTENT
|
|
9942
|
+
* move-hoist neutralises only disallowed-tag removals; clobber, mXSS-canary,
|
|
9943
|
+
* namespace, comment, processing-instruction and KEEP_CONTENT:false removals
|
|
9944
|
+
* all drop their subtree wholesale via `_forceRemove`. On the IN_PLACE path
|
|
9945
|
+
* those dropped nodes are detached from the caller's LIVE tree but a
|
|
9946
|
+
* handler-bearing original among them (an `<img onerror>`/`<video>` that was
|
|
9947
|
+
* loading) keeps its queued resource event, which fires in page scope after
|
|
9948
|
+
* sanitize returns. This walks a removed subtree and strips every attribute
|
|
9949
|
+
* the active configuration does not allow — so `on*` handlers are cancelled
|
|
9950
|
+
* through the SAME allowlist that governs kept nodes, not a separate `/^on/`
|
|
9951
|
+
* blocklist. Run synchronously before sanitize returns, i.e. before any
|
|
9952
|
+
* queued event can fire. Hook-free by design: these nodes leave the output,
|
|
9953
|
+
* so firing attribute hooks for them would be surprising. Clobber-safe reads;
|
|
9954
|
+
* a doomed clobbered node may shadow `removeAttribute` (its own attributes are
|
|
9955
|
+
* irrelevant — it is discarded — while its non-clobbered descendants, e.g.
|
|
9956
|
+
* the `<img>`, are reached and scrubbed).
|
|
9957
|
+
*
|
|
9958
|
+
* @param root the root of a removed subtree to neutralise
|
|
9959
|
+
*/
|
|
9960
|
+
const _neutralizeSubtree = function _neutralizeSubtree(root) {
|
|
9961
|
+
const stack = [root];
|
|
9962
|
+
while (stack.length > 0) {
|
|
9963
|
+
const node = stack.pop();
|
|
9964
|
+
const nodeType = getNodeType ? getNodeType(node) : node.nodeType;
|
|
9965
|
+
if (nodeType === NODE_TYPE.element) {
|
|
9966
|
+
_stripDisallowedAttributes(node);
|
|
9967
|
+
}
|
|
9968
|
+
const childNodes = getChildNodes(node);
|
|
9969
|
+
if (childNodes) {
|
|
9970
|
+
for (let i = childNodes.length - 1; i >= 0; --i) {
|
|
9971
|
+
stack.push(childNodes[i]);
|
|
9972
|
+
}
|
|
9973
|
+
}
|
|
9974
|
+
}
|
|
9975
|
+
};
|
|
9976
|
+
/**
|
|
9977
|
+
* _neutralizePatchLinkage
|
|
9978
|
+
*
|
|
9979
|
+
* IN_PLACE entry pre-pass (declarative-partial-updates / streaming
|
|
9980
|
+
* hardening, https://github.com/WICG/declarative-partial-updates).
|
|
9981
|
+
*
|
|
9982
|
+
* The main walk strips patch linkage (`for`/`patchsrc`) and removes range
|
|
9983
|
+
* markers (PIs / markup comments) node-by-node, in document order, AS it
|
|
9984
|
+
* reaches each node. On a live in-place root that leaves a window: from the
|
|
9985
|
+
* moment the root is connected until the walk arrives at a given node, that
|
|
9986
|
+
* node's linkage is live. A patch applied on connection/stream can fire as
|
|
9987
|
+
* a microtask during the walk and inject or teleport an unsanitized DOM
|
|
9988
|
+
* range into a region the iterator has already passed and will not revisit,
|
|
9989
|
+
* so the post-return "tree is sanitized" contract is violated. Sweep the
|
|
9990
|
+
* whole tree once up front and sever every linkage before the walk begins,
|
|
9991
|
+
* closing that window.
|
|
9992
|
+
*
|
|
9993
|
+
* This CANNOT undo a patch that already fired before sanitize ran — that is
|
|
9994
|
+
* the irreducible "do not IN_PLACE a live-connected attacker tree" caveat —
|
|
9995
|
+
* but it closes everything from sanitize-start onward. Gated on SAFE_FOR_XML
|
|
9996
|
+
* to group with the rest of the declarative-partial-updates handling and
|
|
9997
|
+
* stay overridable, consistent with the codebase.
|
|
9998
|
+
*
|
|
9999
|
+
* Clobber-safe traversal (cached childNodes getter); per-node try/catch so a
|
|
10000
|
+
* clobbered root cannot defeat the sweep of its non-clobbered descendants.
|
|
10001
|
+
*
|
|
10002
|
+
* NOTE (pending real-Chrome confirmation, see test/declarative-patch-probe
|
|
10003
|
+
* .html Q1): this mirrors the existing policy of keeping `for` on
|
|
10004
|
+
* <label>/<output>. If the shipping feature can drive a patch through a
|
|
10005
|
+
* surviving `for`-on-label/output + `id` pair, this pre-pass and the
|
|
10006
|
+
* attribute check at _isBasicCustomElement's caller must additionally drop
|
|
10007
|
+
* that pair on the IN_PLACE path. Left as-is until the taxonomy is verified.
|
|
10008
|
+
*
|
|
10009
|
+
* @param root the in-place root to sweep
|
|
10010
|
+
*/
|
|
10011
|
+
const _neutralizePatchLinkage = function _neutralizePatchLinkage(root) {
|
|
10012
|
+
if (!SAFE_FOR_XML) {
|
|
10013
|
+
return;
|
|
10014
|
+
}
|
|
10015
|
+
const stack = [root];
|
|
10016
|
+
while (stack.length > 0) {
|
|
10017
|
+
const node = stack.pop();
|
|
10018
|
+
const nodeType = getNodeType ? getNodeType(node) : node.nodeType;
|
|
10019
|
+
/* Remove range markers (the target side of a patch linkage): every
|
|
10020
|
+
processing instruction, and any markup-bearing comment. */
|
|
10021
|
+
if (nodeType === NODE_TYPE.processingInstruction || nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, node.data)) {
|
|
10022
|
+
try {
|
|
10023
|
+
remove(node);
|
|
10024
|
+
} catch (_) {
|
|
10025
|
+
/* Best-effort */
|
|
10026
|
+
}
|
|
10027
|
+
continue;
|
|
10028
|
+
}
|
|
10029
|
+
/* Strip patch-source attributes (the source side) off elements. */
|
|
10030
|
+
if (nodeType === NODE_TYPE.element) {
|
|
10031
|
+
const element = node;
|
|
10032
|
+
const lcTag = transformCaseFunc(getNodeName ? getNodeName(node) : node.nodeName);
|
|
10033
|
+
try {
|
|
10034
|
+
if (element.hasAttribute && element.hasAttribute('patchsrc')) {
|
|
10035
|
+
element.removeAttribute('patchsrc');
|
|
10036
|
+
}
|
|
10037
|
+
if (element.hasAttribute && element.hasAttribute('for') && lcTag !== 'label' && lcTag !== 'output') {
|
|
10038
|
+
element.removeAttribute('for');
|
|
10039
|
+
}
|
|
10040
|
+
} catch (_) {
|
|
10041
|
+
/* Clobbered removeAttribute/hasAttribute on a doomed node — ignore */
|
|
10042
|
+
}
|
|
10043
|
+
}
|
|
10044
|
+
const childNodes = getChildNodes(node);
|
|
10045
|
+
if (childNodes) {
|
|
10046
|
+
for (let i = childNodes.length - 1; i >= 0; --i) {
|
|
10047
|
+
stack.push(childNodes[i]);
|
|
10048
|
+
}
|
|
10049
|
+
}
|
|
10050
|
+
}
|
|
10051
|
+
};
|
|
9181
10052
|
/**
|
|
9182
10053
|
* _initDocument
|
|
9183
10054
|
*
|
|
@@ -9199,7 +10070,7 @@ function requirePurify_cjs () {
|
|
|
9199
10070
|
// Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
|
|
9200
10071
|
dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
|
|
9201
10072
|
}
|
|
9202
|
-
const dirtyPayload = trustedTypesPolicy ?
|
|
10073
|
+
const dirtyPayload = trustedTypesPolicy ? _createTrustedHTML(dirty) : dirty;
|
|
9203
10074
|
/*
|
|
9204
10075
|
* Use the DOMParser API by default, fallback later if needs be
|
|
9205
10076
|
* DOMParser not work for svg when has multiple root element.
|
|
@@ -9235,33 +10106,297 @@ function requirePurify_cjs () {
|
|
|
9235
10106
|
* @return The created NodeIterator
|
|
9236
10107
|
*/
|
|
9237
10108
|
const _createNodeIterator = function _createNodeIterator(root) {
|
|
9238
|
-
|
|
10109
|
+
/* Read ownerDocument through the cached Node.prototype getter, never the
|
|
10110
|
+
direct property. HTMLFormElement has [LegacyOverrideBuiltIns], so a
|
|
10111
|
+
clobbering child (<input name="ownerDocument"> or a form-associated
|
|
10112
|
+
external input) shadows the prototype getter and makes a direct read
|
|
10113
|
+
return that <input>. createNodeIterator.call(<input>, ...) then throws
|
|
10114
|
+
"Illegal invocation", and on the IN_PLACE path that throw lands before
|
|
10115
|
+
the walk's fail-closed barrier - leaving the caller's live tree, with
|
|
10116
|
+
any already-armed handler in it, un-neutralized. The cached getter
|
|
10117
|
+
returns the real Document regardless of the clobber. */
|
|
10118
|
+
const doc = getOwnerDocument ? getOwnerDocument(root) : root.ownerDocument;
|
|
10119
|
+
return createNodeIterator.call(doc || root, root,
|
|
9239
10120
|
// eslint-disable-next-line no-bitwise
|
|
9240
10121
|
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
|
|
9241
10122
|
};
|
|
10123
|
+
/**
|
|
10124
|
+
* Replace template expression syntax (mustache, ERB, template
|
|
10125
|
+
* literal) with a space; shared by all SAFE_FOR_TEMPLATES scrub
|
|
10126
|
+
* sites. Order matters: mustache, then ERB, then template literal.
|
|
10127
|
+
*
|
|
10128
|
+
* @param value the string to scrub
|
|
10129
|
+
* @returns the scrubbed string
|
|
10130
|
+
*/
|
|
10131
|
+
const _stripTemplateExpressions = function _stripTemplateExpressions(value) {
|
|
10132
|
+
value = stringReplace(value, MUSTACHE_EXPR$1, ' ');
|
|
10133
|
+
value = stringReplace(value, ERB_EXPR$1, ' ');
|
|
10134
|
+
value = stringReplace(value, TMPLIT_EXPR$1, ' ');
|
|
10135
|
+
return value;
|
|
10136
|
+
};
|
|
10137
|
+
/**
|
|
10138
|
+
* Strip template-engine expressions ({{...}}, ${...}, <%...%>) from the
|
|
10139
|
+
* character data of an element subtree. Used as the final safety net for
|
|
10140
|
+
* SAFE_FOR_TEMPLATES on every DOM-returning code path so that expressions
|
|
10141
|
+
* which only form after text-node normalization (e.g. fragments split across
|
|
10142
|
+
* stripped elements) cannot survive into a template-evaluating framework.
|
|
10143
|
+
*
|
|
10144
|
+
* Walks text/comment/CDATA/processing-instruction nodes and mutates `.data`
|
|
10145
|
+
* in place rather than round-tripping through innerHTML. This preserves
|
|
10146
|
+
* descendant node references (important for IN_PLACE callers), avoids a
|
|
10147
|
+
* serialize/reparse cycle, and reads literal character data — which means
|
|
10148
|
+
* `<%...%>` in text content matches the ERB regex against its real bytes
|
|
10149
|
+
* instead of the HTML-entity-escaped form innerHTML would produce.
|
|
10150
|
+
*
|
|
10151
|
+
* Attribute values are not visited here; SAFE_FOR_TEMPLATES handling for
|
|
10152
|
+
* attributes is performed during the per-node `_sanitizeAttributes` pass.
|
|
10153
|
+
*
|
|
10154
|
+
* @param node The root element whose character data should be scrubbed.
|
|
10155
|
+
*/
|
|
10156
|
+
const _scrubTemplateExpressions2 = function _scrubTemplateExpressions(node) {
|
|
10157
|
+
var _node$querySelectorAl;
|
|
10158
|
+
node.normalize();
|
|
10159
|
+
/* Clobber-safe ownerDocument read, same reasoning as _createNodeIterator:
|
|
10160
|
+
under SAFE_FOR_TEMPLATES this runs on the live IN_PLACE root, which may
|
|
10161
|
+
carry a form-named-getter override of ownerDocument. */
|
|
10162
|
+
const doc = getOwnerDocument ? getOwnerDocument(node) : node.ownerDocument;
|
|
10163
|
+
const walker = createNodeIterator.call(doc || node, node,
|
|
10164
|
+
// eslint-disable-next-line no-bitwise
|
|
10165
|
+
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_CDATA_SECTION | NodeFilter.SHOW_PROCESSING_INSTRUCTION, null);
|
|
10166
|
+
let currentNode = walker.nextNode();
|
|
10167
|
+
while (currentNode) {
|
|
10168
|
+
currentNode.data = _stripTemplateExpressions(currentNode.data);
|
|
10169
|
+
currentNode = walker.nextNode();
|
|
10170
|
+
}
|
|
10171
|
+
// NodeIterator does not descend into <template>.content per the DOM spec,
|
|
10172
|
+
// so we must explicitly recurse into each template's content fragment,
|
|
10173
|
+
// mirroring the approach used by _sanitizeShadowDOM.
|
|
10174
|
+
const templates = (_node$querySelectorAl = node.querySelectorAll) === null || _node$querySelectorAl === void 0 ? void 0 : _node$querySelectorAl.call(node, 'template');
|
|
10175
|
+
if (templates) {
|
|
10176
|
+
arrayForEach(templates, tmpl => {
|
|
10177
|
+
if (_isDocumentFragment(tmpl.content)) {
|
|
10178
|
+
_scrubTemplateExpressions2(tmpl.content);
|
|
10179
|
+
}
|
|
10180
|
+
});
|
|
10181
|
+
}
|
|
10182
|
+
};
|
|
9242
10183
|
/**
|
|
9243
10184
|
* _isClobbered
|
|
9244
10185
|
*
|
|
10186
|
+
* Detect DOM-clobbering on HTMLFormElement nodes. Form is the only HTML
|
|
10187
|
+
* interface with [LegacyOverrideBuiltIns]; a descendant element with a
|
|
10188
|
+
* `name` attribute matching a prototype property shadows that property
|
|
10189
|
+
* on direct reads. We use this check at the IN_PLACE entry-point and
|
|
10190
|
+
* during attribute sanitization to refuse clobbered forms.
|
|
10191
|
+
*
|
|
9245
10192
|
* @param element element to check for clobbering attacks
|
|
9246
10193
|
* @return true if clobbered, false if safe
|
|
9247
10194
|
*/
|
|
9248
10195
|
const _isClobbered = function _isClobbered(element) {
|
|
9249
|
-
|
|
10196
|
+
// Realm-independent tag-name probe. If we can't determine the tag
|
|
10197
|
+
// name at all, we can't reason about clobbering — return false
|
|
10198
|
+
// (the caller's other defences still apply).
|
|
10199
|
+
const realTagName = getNodeName ? getNodeName(element) : null;
|
|
10200
|
+
if (typeof realTagName !== 'string') {
|
|
10201
|
+
return false;
|
|
10202
|
+
}
|
|
10203
|
+
if (transformCaseFunc(realTagName) !== 'form') {
|
|
10204
|
+
return false;
|
|
10205
|
+
}
|
|
10206
|
+
return typeof element.nodeName !== 'string' || typeof element.textContent !== 'string' || typeof element.removeChild !== 'function' ||
|
|
10207
|
+
// Realm-safe NamedNodeMap detection: equality against the cached
|
|
10208
|
+
// prototype getter. Clobbered .attributes (e.g. <input name="attributes">)
|
|
10209
|
+
// makes the direct read diverge from the cached read; a clean form
|
|
10210
|
+
// (same-realm OR foreign-realm) has both reads pointing at the same
|
|
10211
|
+
// canonical NamedNodeMap.
|
|
10212
|
+
element.attributes !== getAttributes(element) || typeof element.removeAttribute !== 'function' || typeof element.setAttribute !== 'function' || typeof element.namespaceURI !== 'string' || typeof element.insertBefore !== 'function' || typeof element.hasChildNodes !== 'function' ||
|
|
10213
|
+
// NodeType clobbering probe. Cached Node.prototype.nodeType getter
|
|
10214
|
+
// returns the integer 1 for any Element regardless of realm; direct
|
|
10215
|
+
// read on a clobbered form (e.g. <input name="nodeType">) returns
|
|
10216
|
+
// the named child element. Cheap addition — nodeType is read from
|
|
10217
|
+
// an internal slot, no serialization cost — and removes a residual
|
|
10218
|
+
// clobbering surface used by several mXSS / PI / comment branches
|
|
10219
|
+
// in _sanitizeElements that compare currentNode.nodeType directly.
|
|
10220
|
+
element.nodeType !== getNodeType(element) ||
|
|
10221
|
+
// HTMLFormElement has [LegacyOverrideBuiltIns]: a descendant named
|
|
10222
|
+
// "childNodes" shadows the prototype getter. Direct reads of
|
|
10223
|
+
// form.childNodes from a clobbered form return the named child
|
|
10224
|
+
// instead of the real NodeList, so any walk that reads it directly
|
|
10225
|
+
// skips the form's real children. Compare the direct read to the
|
|
10226
|
+
// cached Node.prototype getter — when the form's named-property
|
|
10227
|
+
// getter intercepts the read, the two values differ and we flag
|
|
10228
|
+
// the form. This catches every clobbering child type (input,
|
|
10229
|
+
// select, etc.) regardless of whether the named child happens to
|
|
10230
|
+
// carry a numeric .length, which a typeof-based probe would miss
|
|
10231
|
+
// (e.g. HTMLSelectElement.length is a defined unsigned-long).
|
|
10232
|
+
element.childNodes !== getChildNodes(element);
|
|
9250
10233
|
};
|
|
9251
10234
|
/**
|
|
9252
|
-
* Checks whether the given
|
|
10235
|
+
* Checks whether the given value is a DocumentFragment from any realm.
|
|
10236
|
+
*
|
|
10237
|
+
* The realm-independent replacement reads `nodeType` through the cached
|
|
10238
|
+
* Node.prototype getter and compares to the DOCUMENT_FRAGMENT_NODE
|
|
10239
|
+
* constant (11). nodeType is a numeric value resolved from the node's
|
|
10240
|
+
* internal slot, identical across realms for the same kind of node.
|
|
10241
|
+
*
|
|
10242
|
+
* @param value object to check
|
|
10243
|
+
* @return true if value is a DocumentFragment-shaped node from any realm
|
|
10244
|
+
*/
|
|
10245
|
+
const _isDocumentFragment = function _isDocumentFragment(value) {
|
|
10246
|
+
if (!getNodeType || typeof value !== 'object' || value === null) {
|
|
10247
|
+
return false;
|
|
10248
|
+
}
|
|
10249
|
+
try {
|
|
10250
|
+
return getNodeType(value) === NODE_TYPE.documentFragment;
|
|
10251
|
+
} catch (_) {
|
|
10252
|
+
return false;
|
|
10253
|
+
}
|
|
10254
|
+
};
|
|
10255
|
+
/**
|
|
10256
|
+
* Checks whether the given object is a DOM node, including nodes that
|
|
10257
|
+
* originate from a different window/realm (e.g. an iframe's
|
|
10258
|
+
* contentDocument). The previous `value instanceof Node` check was
|
|
10259
|
+
* realm-bound: nodes from a different window failed it, causing
|
|
10260
|
+
* sanitize() to silently stringify them and reset IN_PLACE to false,
|
|
10261
|
+
* returning the original node unsanitized. See GHSA-4w3q-35jp-p934.
|
|
9253
10262
|
*
|
|
9254
10263
|
* @param value object to check whether it's a DOM node
|
|
9255
|
-
* @return true
|
|
10264
|
+
* @return true if value is a DOM node from any realm
|
|
9256
10265
|
*/
|
|
9257
10266
|
const _isNode = function _isNode(value) {
|
|
9258
|
-
|
|
10267
|
+
if (!getNodeType || typeof value !== 'object' || value === null) {
|
|
10268
|
+
return false;
|
|
10269
|
+
}
|
|
10270
|
+
try {
|
|
10271
|
+
return typeof getNodeType(value) === 'number';
|
|
10272
|
+
} catch (_) {
|
|
10273
|
+
return false;
|
|
10274
|
+
}
|
|
9259
10275
|
};
|
|
9260
10276
|
function _executeHooks(hooks, currentNode, data) {
|
|
10277
|
+
if (hooks.length === 0) {
|
|
10278
|
+
return;
|
|
10279
|
+
}
|
|
9261
10280
|
arrayForEach(hooks, hook => {
|
|
9262
10281
|
hook.call(DOMPurify, currentNode, data, CONFIG);
|
|
9263
10282
|
});
|
|
9264
10283
|
}
|
|
10284
|
+
/**
|
|
10285
|
+
* Structural-threat checks that condemn a node regardless of the
|
|
10286
|
+
* allowlists: mXSS via namespace confusion, risky CSS construction,
|
|
10287
|
+
* processing instructions, markup-bearing comments. Pure predicate;
|
|
10288
|
+
* the caller removes. Check order is load-bearing.
|
|
10289
|
+
*
|
|
10290
|
+
* @param currentNode the node to inspect
|
|
10291
|
+
* @param tagName the node's transformCaseFunc'd tag name
|
|
10292
|
+
* @return true if the node must be removed
|
|
10293
|
+
*/
|
|
10294
|
+
const _isUnsafeNode = function _isUnsafeNode(currentNode, tagName) {
|
|
10295
|
+
/* Detect mXSS attempts abusing namespace confusion */
|
|
10296
|
+
if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.textContent) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.innerHTML)) {
|
|
10297
|
+
return true;
|
|
10298
|
+
}
|
|
10299
|
+
/* Remove risky CSS construction leading to mXSS */
|
|
10300
|
+
if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === 'style' && _isNode(currentNode.firstElementChild)) {
|
|
10301
|
+
return true;
|
|
10302
|
+
}
|
|
10303
|
+
/* Remove any occurrence of processing instructions */
|
|
10304
|
+
if (currentNode.nodeType === NODE_TYPE.processingInstruction) {
|
|
10305
|
+
return true;
|
|
10306
|
+
}
|
|
10307
|
+
/* Remove any kind of possibly harmful comments */
|
|
10308
|
+
if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, currentNode.data)) {
|
|
10309
|
+
return true;
|
|
10310
|
+
}
|
|
10311
|
+
return false;
|
|
10312
|
+
};
|
|
10313
|
+
/**
|
|
10314
|
+
* Handle a node whose tag is forbidden or not allowlisted: keep
|
|
10315
|
+
* allowed custom elements (false return exits _sanitizeElements
|
|
10316
|
+
* early - the namespace and fallback-tag removal checks are
|
|
10317
|
+
* intentionally skipped for kept custom elements), else hoist
|
|
10318
|
+
* content per KEEP_CONTENT and remove.
|
|
10319
|
+
*
|
|
10320
|
+
* A kept custom element is the ONLY case in which this function
|
|
10321
|
+
* returns false, so the caller uses that return value to run the
|
|
10322
|
+
* afterSanitizeElements hook on the kept element and keep the
|
|
10323
|
+
* element-hook lifecycle consistent with normal allowlisted
|
|
10324
|
+
* elements (GHSA-c2j3-45gr-mqc4).
|
|
10325
|
+
*
|
|
10326
|
+
* @param currentNode the disallowed node
|
|
10327
|
+
* @param tagName the node's transformCaseFunc'd tag name
|
|
10328
|
+
* @return true if the node was removed, false if kept
|
|
10329
|
+
*/
|
|
10330
|
+
const _sanitizeDisallowedNode = function _sanitizeDisallowedNode(currentNode, tagName, root) {
|
|
10331
|
+
/* Check if we have a custom element to handle */
|
|
10332
|
+
if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
|
|
10333
|
+
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
|
|
10334
|
+
return false;
|
|
10335
|
+
}
|
|
10336
|
+
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
|
|
10337
|
+
return false;
|
|
10338
|
+
}
|
|
10339
|
+
}
|
|
10340
|
+
/* Keep content except for bad-listed elements.
|
|
10341
|
+
Use the cached prototype getters exclusively — the previous code
|
|
10342
|
+
had `|| currentNode.parentNode` / `|| currentNode.childNodes`
|
|
10343
|
+
fallbacks, but the cached getters always return the canonical
|
|
10344
|
+
value (or null for a real parent-less node), so the fallback
|
|
10345
|
+
path was dead in safe cases and a clobbering surface in unsafe
|
|
10346
|
+
ones. Falsy cached results stay falsy; the `if (childNodes &&
|
|
10347
|
+
parentNode)` check already gates correctly. */
|
|
10348
|
+
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
|
|
10349
|
+
const parentNode = getParentNode(currentNode);
|
|
10350
|
+
const childNodes = getChildNodes(currentNode);
|
|
10351
|
+
if (childNodes && parentNode) {
|
|
10352
|
+
const childCount = childNodes.length;
|
|
10353
|
+
/* Hoist by moving each child up one level rather than deep-cloning
|
|
10354
|
+
it. Moving transfers every descendant exactly once, so a chain of
|
|
10355
|
+
nested disallowed elements costs O(n) instead of the O(n^2) that
|
|
10356
|
+
re-cloning the shrinking subtree at each level produced; it also
|
|
10357
|
+
empties the removed original, so `DOMPurify.removed` no longer
|
|
10358
|
+
pins whole subtrees. Moving preserves the in-place guarantee too:
|
|
10359
|
+
an original carrying already-queued resource events (`<img
|
|
10360
|
+
onerror>`, `<video>`/`<audio>` error, lazy/`onload`, …) is
|
|
10361
|
+
relocated and sanitised rather than left detached but still armed.
|
|
10362
|
+
The sole case that must clone is removing the walk root itself.
|
|
10363
|
+
The result is serialised from the root's subtree, so a restrictive
|
|
10364
|
+
ALLOWED_TAGS that strips the root (`body` on the string path) must
|
|
10365
|
+
leave the content inside it, which only cloning does. In IN_PLACE
|
|
10366
|
+
the root is pre-validated as an allowed tag and so is never removed
|
|
10367
|
+
here, so that path always takes the move branch.
|
|
10368
|
+
`childNodes` is live; a tail-to-head walk keeps `childNodes[i]`
|
|
10369
|
+
valid whether we move (drops the trailing entry) or clone (leaves
|
|
10370
|
+
the list intact). */
|
|
10371
|
+
for (let i = childCount - 1; i >= 0; --i) {
|
|
10372
|
+
const hoisted = currentNode === root ? cloneNode(childNodes[i], true) : childNodes[i];
|
|
10373
|
+
parentNode.insertBefore(hoisted, getNextSibling(currentNode));
|
|
10374
|
+
}
|
|
10375
|
+
}
|
|
10376
|
+
}
|
|
10377
|
+
_forceRemove(currentNode);
|
|
10378
|
+
return true;
|
|
10379
|
+
};
|
|
10380
|
+
/**
|
|
10381
|
+
* Fork a hook-mutable allowlist off its shared binding the first time a
|
|
10382
|
+
* (possibly lazily-installed) uponSanitize* hook is about to see it, so the
|
|
10383
|
+
* hook cannot widen the per-instance default or the setConfig binding by
|
|
10384
|
+
* reference and leak past the call. Returns the set unchanged once it is
|
|
10385
|
+
* already call-local, so repeated calls across elements are idempotent.
|
|
10386
|
+
*
|
|
10387
|
+
* @param hookList the uponSanitize* hook array for this event
|
|
10388
|
+
* @param set the current ALLOWED_TAGS / ALLOWED_ATTR binding
|
|
10389
|
+
* @param defaultSet the per-instance DEFAULT_ALLOWED_* constant
|
|
10390
|
+
* @param setConfigSet the captured setConfig() binding, or null
|
|
10391
|
+
* @return a call-local clone if a hook is present and set is still shared,
|
|
10392
|
+
* else set unchanged
|
|
10393
|
+
*/
|
|
10394
|
+
const _forkSharedAllowlist = function _forkSharedAllowlist(hookList, set, defaultSet, setConfigSet) {
|
|
10395
|
+
if (hookList.length === 0) {
|
|
10396
|
+
return set;
|
|
10397
|
+
}
|
|
10398
|
+
return set === defaultSet || set === setConfigSet ? clone(set) : set;
|
|
10399
|
+
};
|
|
9265
10400
|
/**
|
|
9266
10401
|
* _sanitizeElements
|
|
9267
10402
|
*
|
|
@@ -9271,86 +10406,112 @@ function requirePurify_cjs () {
|
|
|
9271
10406
|
* @param currentNode to check for permission to exist
|
|
9272
10407
|
* @return true if node was killed, false if left alive
|
|
9273
10408
|
*/
|
|
9274
|
-
|
|
9275
|
-
|
|
10409
|
+
// eslint-disable-next-line complexity
|
|
10410
|
+
const _sanitizeElements = function _sanitizeElements(currentNode, root) {
|
|
9276
10411
|
/* Execute a hook if present */
|
|
9277
10412
|
_executeHooks(hooks.beforeSanitizeElements, currentNode, null);
|
|
10413
|
+
/* A hook may have detached the node - treat it as removed (see the
|
|
10414
|
+
detached-node comment after the uponSanitizeElement hook below). On
|
|
10415
|
+
the IN_PLACE path, neutralize the detached subtree first so a queued
|
|
10416
|
+
resource handler on it cannot fire in page scope after we return. */
|
|
10417
|
+
if (currentNode !== root && getParentNode(currentNode) === null) {
|
|
10418
|
+
if (IN_PLACE) {
|
|
10419
|
+
_neutralizeSubtree(currentNode);
|
|
10420
|
+
}
|
|
10421
|
+
return true;
|
|
10422
|
+
}
|
|
9278
10423
|
/* Check if element is clobbered or can clobber */
|
|
9279
10424
|
if (_isClobbered(currentNode)) {
|
|
9280
10425
|
_forceRemove(currentNode);
|
|
9281
10426
|
return true;
|
|
9282
10427
|
}
|
|
9283
10428
|
/* Now let's check the element's type and name */
|
|
9284
|
-
const tagName = transformCaseFunc(currentNode.nodeName);
|
|
10429
|
+
const tagName = transformCaseFunc(getNodeName ? getNodeName(currentNode) : currentNode.nodeName);
|
|
10430
|
+
/* Close the pre-walk clone-guard's timing gap: an uponSanitizeElement
|
|
10431
|
+
hook may have been installed after that guard sampled the hook arrays
|
|
10432
|
+
(e.g. lazily from beforeSanitizeElements), leaving ALLOWED_TAGS still
|
|
10433
|
+
aliasing a shared binding that a widening hook would mutate by
|
|
10434
|
+
reference. Fork it before exposing it to the hook. */
|
|
10435
|
+
ALLOWED_TAGS = _forkSharedAllowlist(hooks.uponSanitizeElement, ALLOWED_TAGS, DEFAULT_ALLOWED_TAGS, SET_CONFIG_ALLOWED_TAGS);
|
|
9285
10436
|
/* Execute a hook if present */
|
|
9286
10437
|
_executeHooks(hooks.uponSanitizeElement, currentNode, {
|
|
9287
10438
|
tagName,
|
|
9288
10439
|
allowedTags: ALLOWED_TAGS
|
|
9289
10440
|
});
|
|
9290
|
-
/*
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9299
|
-
|
|
9300
|
-
|
|
9301
|
-
|
|
9302
|
-
|
|
10441
|
+
/* A hook may have detached the node from the tree — a long-standing
|
|
10442
|
+
user pattern (issue #469; draw.io-style foreignObject filtering).
|
|
10443
|
+
Per the cached, unclobberable parentNode getter the node is
|
|
10444
|
+
genuinely out of the tree, so it can reach neither the serialized
|
|
10445
|
+
output nor an IN_PLACE live tree; treat it as removed and stop
|
|
10446
|
+
processing it. Without this guard, the unsafe-node / namespace
|
|
10447
|
+
checks below would call _forceRemove on a parentless node and hit
|
|
10448
|
+
the REPORT-3 fail-closed throw — which exists for nodes DOMPurify
|
|
10449
|
+
wants gone but *cannot* detach (clobbered / parentless roots), the
|
|
10450
|
+
opposite of a node that is already safely gone. The walk root is
|
|
10451
|
+
exempt: a detached IN_PLACE root is legitimate input and must still
|
|
10452
|
+
be fully sanitized, and a kill-decision on it must keep hitting the
|
|
10453
|
+
REPORT-3 throw. Nodes detached by hooks stay the hook's
|
|
10454
|
+
responsibility for placement: they are not recorded in
|
|
10455
|
+
DOMPurify.removed, so the post-walk IN_PLACE pass (which iterates
|
|
10456
|
+
DOMPurify.removed) does not reach them. But a hook-detached subtree
|
|
10457
|
+
can still hold a queued resource-event handler - e.g. an <img onload>
|
|
10458
|
+
that began loading when the caller built the live tree - which fires
|
|
10459
|
+
in page scope after sanitize returns even though the handler never
|
|
10460
|
+
reached the returned tree. That is the audit-5 F1 hazard, and the
|
|
10461
|
+
documented node.remove() hook pattern walks straight into it. So on
|
|
10462
|
+
the IN_PLACE path we neutralize the detached subtree inline here,
|
|
10463
|
+
stripping its non-allow-listed attributes before returning, exactly
|
|
10464
|
+
as the post-walk pass does for _forceRemove'd subtrees. */
|
|
10465
|
+
if (currentNode !== root && getParentNode(currentNode) === null) {
|
|
10466
|
+
if (IN_PLACE) {
|
|
10467
|
+
_neutralizeSubtree(currentNode);
|
|
10468
|
+
}
|
|
9303
10469
|
return true;
|
|
9304
10470
|
}
|
|
9305
|
-
/* Remove
|
|
9306
|
-
if (
|
|
10471
|
+
/* Remove mXSS vectors, processing instructions and risky comments */
|
|
10472
|
+
if (_isUnsafeNode(currentNode, tagName)) {
|
|
9307
10473
|
_forceRemove(currentNode);
|
|
9308
10474
|
return true;
|
|
9309
10475
|
}
|
|
9310
10476
|
/* Remove element if anything forbids its presence */
|
|
9311
10477
|
if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) {
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9315
|
-
|
|
9316
|
-
|
|
9317
|
-
|
|
9318
|
-
|
|
9319
|
-
|
|
9320
|
-
|
|
9321
|
-
|
|
9322
|
-
|
|
9323
|
-
|
|
9324
|
-
|
|
9325
|
-
|
|
9326
|
-
const childCount = childNodes.length;
|
|
9327
|
-
for (let i = childCount - 1; i >= 0; --i) {
|
|
9328
|
-
const childClone = cloneNode(childNodes[i], true);
|
|
9329
|
-
childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
|
|
9330
|
-
parentNode.insertBefore(childClone, getNextSibling(currentNode));
|
|
9331
|
-
}
|
|
9332
|
-
}
|
|
10478
|
+
const removed = _sanitizeDisallowedNode(currentNode, tagName, root);
|
|
10479
|
+
/* A false return means the node is a custom element kept via
|
|
10480
|
+
CUSTOM_ELEMENT_HANDLING - the only keep path through
|
|
10481
|
+
_sanitizeDisallowedNode. Run afterSanitizeElements on it so the
|
|
10482
|
+
element-hook lifecycle matches normal allowlisted elements: a
|
|
10483
|
+
security policy applied in this hook (e.g. stripping an attribute
|
|
10484
|
+
from every surviving element) must not silently skip kept custom
|
|
10485
|
+
elements (GHSA-c2j3-45gr-mqc4). This mirrors the normal-element
|
|
10486
|
+
tail below - the hook runs, then the walker's subsequent
|
|
10487
|
+
_sanitizeAttributes pass sanitizes the element's attributes. The
|
|
10488
|
+
deliberately skipped namespace and fallback-tag removal checks stay
|
|
10489
|
+
skipped; they are removal decisions, not the hook contract. */
|
|
10490
|
+
if (removed === false) {
|
|
10491
|
+
_executeHooks(hooks.afterSanitizeElements, currentNode, null);
|
|
9333
10492
|
}
|
|
9334
|
-
|
|
9335
|
-
return true;
|
|
10493
|
+
return removed;
|
|
9336
10494
|
}
|
|
9337
|
-
/* Check whether element has a valid namespace
|
|
9338
|
-
|
|
10495
|
+
/* Check whether element has a valid namespace.
|
|
10496
|
+
Realm-safe check (GHSA-hpcv-96wg-7vj8): use the cached Node.prototype
|
|
10497
|
+
nodeType getter rather than `instanceof Element`, which is realm-
|
|
10498
|
+
bound and short-circuits to false for any node minted in a different
|
|
10499
|
+
realm — letting a foreign-realm element with a forbidden namespace
|
|
10500
|
+
slip past the namespace check entirely. */
|
|
10501
|
+
const nt = getNodeType ? getNodeType(currentNode) : currentNode.nodeType;
|
|
10502
|
+
if (nt === NODE_TYPE.element && !_checkValidNamespace(currentNode)) {
|
|
9339
10503
|
_forceRemove(currentNode);
|
|
9340
10504
|
return true;
|
|
9341
10505
|
}
|
|
9342
10506
|
/* Make sure that older browsers don't get fallback-tag mXSS */
|
|
9343
|
-
if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(
|
|
10507
|
+
if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(FALLBACK_TAG_CLOSE, currentNode.innerHTML)) {
|
|
9344
10508
|
_forceRemove(currentNode);
|
|
9345
10509
|
return true;
|
|
9346
10510
|
}
|
|
9347
10511
|
/* Sanitize element content to be template-safe */
|
|
9348
10512
|
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
|
|
9349
10513
|
/* Get the element's text content */
|
|
9350
|
-
content = currentNode.textContent;
|
|
9351
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
9352
|
-
content = stringReplace(content, expr, ' ');
|
|
9353
|
-
});
|
|
10514
|
+
const content = _stripTemplateExpressions(currentNode.textContent);
|
|
9354
10515
|
if (currentNode.textContent !== content) {
|
|
9355
10516
|
arrayPush(DOMPurify.removed, {
|
|
9356
10517
|
element: currentNode.cloneNode()
|
|
@@ -9376,15 +10537,43 @@ function requirePurify_cjs () {
|
|
|
9376
10537
|
if (FORBID_ATTR[lcName]) {
|
|
9377
10538
|
return false;
|
|
9378
10539
|
}
|
|
10540
|
+
/* Reject declarative-partial-updates patch-linkage attributes
|
|
10541
|
+
(https://github.com/WICG/declarative-partial-updates).
|
|
10542
|
+
Empirical note (Chrome 150, verified — see
|
|
10543
|
+
test/declarative-patch-probe-v3.html): expansion is NOT applied after
|
|
10544
|
+
sanitization. For the string path it fires during sanitize()'s own
|
|
10545
|
+
parse, so the walk sees and sanitizes the fully materialized expanded
|
|
10546
|
+
tree — teleports into MathML/SVG integration points included; a
|
|
10547
|
+
weaponized `<template for>`->`<img onerror>` comes back with the handler
|
|
10548
|
+
stripped. For the IN_PLACE path it fires on connection, before the walk.
|
|
10549
|
+
Either way DOMPurify is NOT blind to the patch.
|
|
10550
|
+
This removal is therefore defense-in-depth rather than the sole barrier:
|
|
10551
|
+
it prevents live linkage from surviving into the OUTPUT and re-expanding
|
|
10552
|
+
in the caller's context, and keeps behaviour deterministic if a future
|
|
10553
|
+
engine defers expansion. `for` is legitimate only on <label>/<output>;
|
|
10554
|
+
anywhere else (notably <template for>) it links the element to a patch
|
|
10555
|
+
target and teleports or removes an arbitrary DOM range by id/marker name.
|
|
10556
|
+
`patchsrc` fetches remote markup and is treated as a script-loading
|
|
10557
|
+
mechanism (CSP). Gated on SAFE_FOR_XML so the removal groups with the
|
|
10558
|
+
other structural-threat checks and stays overridable, consistent with
|
|
10559
|
+
the rest of the codebase. PI range markers are already removed by
|
|
10560
|
+
_isUnsafeNode. */
|
|
10561
|
+
if (SAFE_FOR_XML && lcName === 'patchsrc') {
|
|
10562
|
+
return false;
|
|
10563
|
+
}
|
|
10564
|
+
if (SAFE_FOR_XML && lcName === 'for' && lcTag !== 'label' && lcTag !== 'output') {
|
|
10565
|
+
return false;
|
|
10566
|
+
}
|
|
9379
10567
|
/* Make sure attribute cannot clobber */
|
|
9380
10568
|
if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
|
|
9381
10569
|
return false;
|
|
9382
10570
|
}
|
|
10571
|
+
const nameIsPermitted = ALLOWED_ATTR[lcName] || EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag);
|
|
9383
10572
|
/* Allow valid data-* attributes: At least one character after "-"
|
|
9384
10573
|
(https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
|
|
9385
10574
|
XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
|
|
9386
10575
|
We don't need to check the value; it's always URI safe. */
|
|
9387
|
-
if (ALLOW_DATA_ATTR &&
|
|
10576
|
+
if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR$1, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName)) ; else if (!nameIsPermitted) {
|
|
9388
10577
|
if (
|
|
9389
10578
|
// First condition does a very basic check if a) it's basically a valid custom element tagname AND
|
|
9390
10579
|
// b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
|
|
@@ -9396,11 +10585,15 @@ function requirePurify_cjs () {
|
|
|
9396
10585
|
return false;
|
|
9397
10586
|
}
|
|
9398
10587
|
/* Check value is safe. First, is attr inert? If so, is safe */
|
|
9399
|
-
} else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if (value) {
|
|
10588
|
+
} else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE$1, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA$1, stringReplace(value, ATTR_WHITESPACE$1, ''))) ; else if (value) {
|
|
9400
10589
|
return false;
|
|
9401
10590
|
} else ;
|
|
9402
10591
|
return true;
|
|
9403
10592
|
};
|
|
10593
|
+
/* Names the HTML spec reserves from valid-custom-element-name; these must
|
|
10594
|
+
* never be treated as basic custom elements even when a permissive
|
|
10595
|
+
* CUSTOM_ELEMENT_HANDLING.tagNameCheck is configured. */
|
|
10596
|
+
const RESERVED_CUSTOM_ELEMENT_NAMES = addToSet({}, ['annotation-xml', 'color-profile', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'missing-glyph']);
|
|
9404
10597
|
/**
|
|
9405
10598
|
* _isBasicCustomElement
|
|
9406
10599
|
* checks if at least one dash is included in tagName, and it's not the first char
|
|
@@ -9410,7 +10603,64 @@ function requirePurify_cjs () {
|
|
|
9410
10603
|
* @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
|
|
9411
10604
|
*/
|
|
9412
10605
|
const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
|
|
9413
|
-
return tagName
|
|
10606
|
+
return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT$1, tagName);
|
|
10607
|
+
};
|
|
10608
|
+
/**
|
|
10609
|
+
* Wrap an attribute value in the matching Trusted Types object when
|
|
10610
|
+
* the active policy requires it. Namespaced attributes pass through
|
|
10611
|
+
* unchanged (no TT support yet, see
|
|
10612
|
+
* https://bugs.chromium.org/p/chromium/issues/detail?id=1305293).
|
|
10613
|
+
*
|
|
10614
|
+
* @param lcTag lowercase tag name of the containing element
|
|
10615
|
+
* @param lcName lowercase attribute name
|
|
10616
|
+
* @param namespaceURI the attribute's namespace, if any
|
|
10617
|
+
* @param value the attribute value to wrap
|
|
10618
|
+
* @return the value, wrapped when Trusted Types demand it
|
|
10619
|
+
*/
|
|
10620
|
+
const _applyTrustedTypesToAttribute = function _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value) {
|
|
10621
|
+
if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function' && !namespaceURI) {
|
|
10622
|
+
switch (trustedTypes.getAttributeType(lcTag, lcName)) {
|
|
10623
|
+
case 'TrustedHTML':
|
|
10624
|
+
{
|
|
10625
|
+
return _createTrustedHTML(value);
|
|
10626
|
+
}
|
|
10627
|
+
case 'TrustedScriptURL':
|
|
10628
|
+
{
|
|
10629
|
+
return _createTrustedScriptURL(value);
|
|
10630
|
+
}
|
|
10631
|
+
}
|
|
10632
|
+
}
|
|
10633
|
+
return value;
|
|
10634
|
+
};
|
|
10635
|
+
/**
|
|
10636
|
+
* Write a modified attribute value back onto the element. On
|
|
10637
|
+
* success, re-probe for clobbering introduced by the new value and
|
|
10638
|
+
* remove the element when found; otherwise pop the removal entry
|
|
10639
|
+
* recorded by the earlier _removeAttribute (long-standing pairing
|
|
10640
|
+
* with the SANITIZE_NAMED_PROPS path - do not "fix" casually). On
|
|
10641
|
+
* failure, remove the attribute instead.
|
|
10642
|
+
*
|
|
10643
|
+
* @param currentNode the element carrying the attribute
|
|
10644
|
+
* @param name the attribute name as present on the element
|
|
10645
|
+
* @param namespaceURI the attribute's namespace, if any
|
|
10646
|
+
* @param value the new attribute value
|
|
10647
|
+
*/
|
|
10648
|
+
const _setAttributeValue = function _setAttributeValue(currentNode, name, namespaceURI, value) {
|
|
10649
|
+
try {
|
|
10650
|
+
if (namespaceURI) {
|
|
10651
|
+
currentNode.setAttributeNS(namespaceURI, name, value);
|
|
10652
|
+
} else {
|
|
10653
|
+
/* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
|
|
10654
|
+
currentNode.setAttribute(name, value);
|
|
10655
|
+
}
|
|
10656
|
+
if (_isClobbered(currentNode)) {
|
|
10657
|
+
_forceRemove(currentNode);
|
|
10658
|
+
} else {
|
|
10659
|
+
arrayPop(DOMPurify.removed);
|
|
10660
|
+
}
|
|
10661
|
+
} catch (_) {
|
|
10662
|
+
_removeAttribute(name, currentNode);
|
|
10663
|
+
}
|
|
9414
10664
|
};
|
|
9415
10665
|
/**
|
|
9416
10666
|
* _sanitizeAttributes
|
|
@@ -9425,13 +10675,14 @@ function requirePurify_cjs () {
|
|
|
9425
10675
|
const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
|
|
9426
10676
|
/* Execute a hook if present */
|
|
9427
10677
|
_executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
|
|
9428
|
-
const
|
|
9429
|
-
attributes
|
|
9430
|
-
} = currentNode;
|
|
10678
|
+
const attributes = currentNode.attributes;
|
|
9431
10679
|
/* Check if we have attributes; if not we might have a text node */
|
|
9432
10680
|
if (!attributes || _isClobbered(currentNode)) {
|
|
9433
10681
|
return;
|
|
9434
10682
|
}
|
|
10683
|
+
/* Same lazy-install guard as uponSanitizeElement (see there): fork the
|
|
10684
|
+
attribute allowlist off its shared binding before a hook can see it. */
|
|
10685
|
+
ALLOWED_ATTR = _forkSharedAllowlist(hooks.uponSanitizeAttribute, ALLOWED_ATTR, DEFAULT_ALLOWED_ATTR, SET_CONFIG_ALLOWED_ATTR);
|
|
9435
10686
|
const hookEvent = {
|
|
9436
10687
|
attrName: '',
|
|
9437
10688
|
attrValue: '',
|
|
@@ -9440,14 +10691,13 @@ function requirePurify_cjs () {
|
|
|
9440
10691
|
forceKeepAttr: undefined
|
|
9441
10692
|
};
|
|
9442
10693
|
let l = attributes.length;
|
|
10694
|
+
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
9443
10695
|
/* Go backwards over all attributes; safely remove bad ones */
|
|
9444
10696
|
while (l--) {
|
|
9445
10697
|
const attr = attributes[l];
|
|
9446
|
-
const
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
value: attrValue
|
|
9450
|
-
} = attr;
|
|
10698
|
+
const name = attr.name,
|
|
10699
|
+
namespaceURI = attr.namespaceURI,
|
|
10700
|
+
attrValue = attr.value;
|
|
9451
10701
|
const lcName = transformCaseFunc(name);
|
|
9452
10702
|
const initValue = attrValue;
|
|
9453
10703
|
let value = name === 'value' ? initValue : stringTrim(initValue);
|
|
@@ -9461,12 +10711,14 @@ function requirePurify_cjs () {
|
|
|
9461
10711
|
/* Full DOM Clobbering protection via namespace isolation,
|
|
9462
10712
|
* Prefix id and name attributes with `user-content-`
|
|
9463
10713
|
*/
|
|
9464
|
-
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
|
|
10714
|
+
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name') && stringIndexOf(value, SANITIZE_NAMED_PROPS_PREFIX) !== 0) {
|
|
9465
10715
|
// Remove the attribute with this value
|
|
9466
10716
|
_removeAttribute(name, currentNode);
|
|
9467
10717
|
// Prefix the value and later re-create the attribute with the sanitized value
|
|
9468
10718
|
value = SANITIZE_NAMED_PROPS_PREFIX + value;
|
|
9469
10719
|
}
|
|
10720
|
+
// Else: already prefixed, leave the attribute alone — the prefix is
|
|
10721
|
+
// itself the clobbering protection, and re-applying it is incorrect.
|
|
9470
10722
|
/* Work around a security issue with comments inside attributes */
|
|
9471
10723
|
if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
|
|
9472
10724
|
_removeAttribute(name, currentNode);
|
|
@@ -9477,7 +10729,7 @@ function requirePurify_cjs () {
|
|
|
9477
10729
|
_removeAttribute(name, currentNode);
|
|
9478
10730
|
continue;
|
|
9479
10731
|
}
|
|
9480
|
-
/* Did the hooks
|
|
10732
|
+
/* Did the hooks force-keep the attribute? */
|
|
9481
10733
|
if (hookEvent.forceKeepAttr) {
|
|
9482
10734
|
continue;
|
|
9483
10735
|
}
|
|
@@ -9487,56 +10739,24 @@ function requirePurify_cjs () {
|
|
|
9487
10739
|
continue;
|
|
9488
10740
|
}
|
|
9489
10741
|
/* Work around a security issue in jQuery 3.0 */
|
|
9490
|
-
if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(
|
|
10742
|
+
if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(SELF_CLOSING_TAG, value)) {
|
|
9491
10743
|
_removeAttribute(name, currentNode);
|
|
9492
10744
|
continue;
|
|
9493
10745
|
}
|
|
9494
10746
|
/* Sanitize attribute content to be template-safe */
|
|
9495
10747
|
if (SAFE_FOR_TEMPLATES) {
|
|
9496
|
-
|
|
9497
|
-
value = stringReplace(value, expr, ' ');
|
|
9498
|
-
});
|
|
10748
|
+
value = _stripTemplateExpressions(value);
|
|
9499
10749
|
}
|
|
9500
10750
|
/* Is `value` valid for this attribute? */
|
|
9501
|
-
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
9502
10751
|
if (!_isValidAttribute(lcTag, lcName, value)) {
|
|
9503
10752
|
_removeAttribute(name, currentNode);
|
|
9504
10753
|
continue;
|
|
9505
10754
|
}
|
|
9506
10755
|
/* Handle attributes that require Trusted Types */
|
|
9507
|
-
|
|
9508
|
-
if (namespaceURI) ; else {
|
|
9509
|
-
switch (trustedTypes.getAttributeType(lcTag, lcName)) {
|
|
9510
|
-
case 'TrustedHTML':
|
|
9511
|
-
{
|
|
9512
|
-
value = trustedTypesPolicy.createHTML(value);
|
|
9513
|
-
break;
|
|
9514
|
-
}
|
|
9515
|
-
case 'TrustedScriptURL':
|
|
9516
|
-
{
|
|
9517
|
-
value = trustedTypesPolicy.createScriptURL(value);
|
|
9518
|
-
break;
|
|
9519
|
-
}
|
|
9520
|
-
}
|
|
9521
|
-
}
|
|
9522
|
-
}
|
|
10756
|
+
value = _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value);
|
|
9523
10757
|
/* Handle invalid data-* attribute set by try-catching it */
|
|
9524
10758
|
if (value !== initValue) {
|
|
9525
|
-
|
|
9526
|
-
if (namespaceURI) {
|
|
9527
|
-
currentNode.setAttributeNS(namespaceURI, name, value);
|
|
9528
|
-
} else {
|
|
9529
|
-
/* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
|
|
9530
|
-
currentNode.setAttribute(name, value);
|
|
9531
|
-
}
|
|
9532
|
-
if (_isClobbered(currentNode)) {
|
|
9533
|
-
_forceRemove(currentNode);
|
|
9534
|
-
} else {
|
|
9535
|
-
arrayPop(DOMPurify.removed);
|
|
9536
|
-
}
|
|
9537
|
-
} catch (_) {
|
|
9538
|
-
_removeAttribute(name, currentNode);
|
|
9539
|
-
}
|
|
10759
|
+
_setAttributeValue(currentNode, name, namespaceURI, value);
|
|
9540
10760
|
}
|
|
9541
10761
|
}
|
|
9542
10762
|
/* Execute a hook if present */
|
|
@@ -9556,17 +10776,136 @@ function requirePurify_cjs () {
|
|
|
9556
10776
|
/* Execute a hook if present */
|
|
9557
10777
|
_executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
|
|
9558
10778
|
/* Sanitize tags and elements */
|
|
9559
|
-
_sanitizeElements(shadowNode);
|
|
10779
|
+
_sanitizeElements(shadowNode, fragment);
|
|
9560
10780
|
/* Check attributes next */
|
|
9561
10781
|
_sanitizeAttributes(shadowNode);
|
|
9562
|
-
/* Deep shadow DOM detected
|
|
9563
|
-
|
|
10782
|
+
/* Deep shadow DOM detected.
|
|
10783
|
+
Realm-safe check (GHSA-hpcv-96wg-7vj8): use nodeType against the
|
|
10784
|
+
DOCUMENT_FRAGMENT_NODE constant rather than instanceof, so we
|
|
10785
|
+
recurse into <template>.content from foreign realms too. */
|
|
10786
|
+
if (_isDocumentFragment(shadowNode.content)) {
|
|
9564
10787
|
_sanitizeShadowDOM2(shadowNode.content);
|
|
9565
10788
|
}
|
|
10789
|
+
/* An element iterated here may itself host an attached
|
|
10790
|
+
shadow root. The default NodeIterator does not enter shadow
|
|
10791
|
+
trees, so a shadow root nested inside template.content was
|
|
10792
|
+
previously reached by no walk at all (the pre-pass at
|
|
10793
|
+
_sanitizeAttachedShadowRoots descends via childNodes, which
|
|
10794
|
+
doesn't enter template.content; the template-content recursion
|
|
10795
|
+
above iterates the content but never inspected shadowRoot).
|
|
10796
|
+
Walk it explicitly. The nodeType guard avoids reading
|
|
10797
|
+
shadowRoot off text / comment / CDATA / PI nodes that the
|
|
10798
|
+
iterator also surfaces. */
|
|
10799
|
+
const shadowNodeType = getNodeType ? getNodeType(shadowNode) : shadowNode.nodeType;
|
|
10800
|
+
if (shadowNodeType === NODE_TYPE.element) {
|
|
10801
|
+
const innerSr = getShadowRoot(shadowNode);
|
|
10802
|
+
if (_isDocumentFragment(innerSr)) {
|
|
10803
|
+
_sanitizeAttachedShadowRoots(innerSr);
|
|
10804
|
+
_sanitizeShadowDOM2(innerSr);
|
|
10805
|
+
}
|
|
10806
|
+
}
|
|
9566
10807
|
}
|
|
9567
10808
|
/* Execute a hook if present */
|
|
9568
10809
|
_executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
|
|
9569
10810
|
};
|
|
10811
|
+
/**
|
|
10812
|
+
* _sanitizeAttachedShadowRoots
|
|
10813
|
+
*
|
|
10814
|
+
* Walks `root` and feeds every attached shadow root we encounter into
|
|
10815
|
+
* the existing _sanitizeShadowDOM pipeline. The default node iterator
|
|
10816
|
+
* does not descend into shadow trees, so nodes inside an attached
|
|
10817
|
+
* shadow root would otherwise be skipped entirely.
|
|
10818
|
+
*
|
|
10819
|
+
* Two real input paths put attached shadow roots in front of us:
|
|
10820
|
+
* 1. IN_PLACE on a DOM node that already has shadow roots attached.
|
|
10821
|
+
* 2. DOM-node input where importNode(dirty, true) deep-clones the
|
|
10822
|
+
* shadow root because it was created with `clonable: true`.
|
|
10823
|
+
*
|
|
10824
|
+
* This pass runs once, up front, so the main iteration loop (and the
|
|
10825
|
+
* existing _sanitizeShadowDOM template-content recursion) stay
|
|
10826
|
+
* untouched — string-input paths are not affected.
|
|
10827
|
+
*
|
|
10828
|
+
* @param root the subtree root to walk for attached shadow roots
|
|
10829
|
+
*/
|
|
10830
|
+
const _sanitizeAttachedShadowRoots = function _sanitizeAttachedShadowRoots(root) {
|
|
10831
|
+
/* Iterative (explicit stack) rather than per-child recursion. DOM APIs
|
|
10832
|
+
impose no depth cap, so an attacker-shaped tree (JSON/CRDT/editor data
|
|
10833
|
+
built straight into the DOM — the IN_PLACE surface) deeper than the JS
|
|
10834
|
+
call-stack budget would otherwise overflow native recursion here and
|
|
10835
|
+
throw at the IN_PLACE entry pre-pass, before a single node is
|
|
10836
|
+
sanitized, leaving the caller's live tree untouched (fail-open). See
|
|
10837
|
+
campaign-3 F4. A heap stack keeps depth off the call stack.
|
|
10838
|
+
Each work item is either a node to descend into, or a deferred
|
|
10839
|
+
`_sanitizeShadowDOM` for an already-walked shadow root. The deferred
|
|
10840
|
+
form preserves the original post-order discipline: a shadow root's
|
|
10841
|
+
nested shadow roots are discovered before the outer shadow is
|
|
10842
|
+
sanitized (which may remove hosts). Pushes are in reverse of the
|
|
10843
|
+
desired processing order (LIFO): template content, then children, then
|
|
10844
|
+
the shadow-sanitize, then the shadow walk — so the order matches the
|
|
10845
|
+
previous recursion exactly. */
|
|
10846
|
+
const stack = [{
|
|
10847
|
+
node: root,
|
|
10848
|
+
shadow: null
|
|
10849
|
+
}];
|
|
10850
|
+
while (stack.length > 0) {
|
|
10851
|
+
const item = stack.pop();
|
|
10852
|
+
/* Deferred shadow-DOM sanitisation: runs after its subtree was walked. */
|
|
10853
|
+
if (item.shadow) {
|
|
10854
|
+
_sanitizeShadowDOM2(item.shadow);
|
|
10855
|
+
continue;
|
|
10856
|
+
}
|
|
10857
|
+
const node = item.node;
|
|
10858
|
+
const nodeType = getNodeType ? getNodeType(node) : node.nodeType;
|
|
10859
|
+
const isElement = nodeType === NODE_TYPE.element;
|
|
10860
|
+
/* (pushed last → processed first) Children, snapshotted in reverse so
|
|
10861
|
+
the first child is processed first. Snapshotting matters because a
|
|
10862
|
+
hook may detach siblings mid-walk. */
|
|
10863
|
+
const childNodes = getChildNodes(node);
|
|
10864
|
+
if (childNodes) {
|
|
10865
|
+
for (let i = childNodes.length - 1; i >= 0; --i) {
|
|
10866
|
+
stack.push({
|
|
10867
|
+
node: childNodes[i],
|
|
10868
|
+
shadow: null
|
|
10869
|
+
});
|
|
10870
|
+
}
|
|
10871
|
+
}
|
|
10872
|
+
/* (pushed before children → processed after them, matching the old
|
|
10873
|
+
"template content last" order) When the node is a <template>,
|
|
10874
|
+
descend into its content. */
|
|
10875
|
+
if (isElement) {
|
|
10876
|
+
const rootName = getNodeName ? getNodeName(node) : null;
|
|
10877
|
+
if (typeof rootName === 'string' && transformCaseFunc(rootName) === 'template') {
|
|
10878
|
+
const content = node.content;
|
|
10879
|
+
if (_isDocumentFragment(content)) {
|
|
10880
|
+
stack.push({
|
|
10881
|
+
node: content,
|
|
10882
|
+
shadow: null
|
|
10883
|
+
});
|
|
10884
|
+
}
|
|
10885
|
+
}
|
|
10886
|
+
}
|
|
10887
|
+
/* Shadow root (processed first): walk its subtree, then sanitise it.
|
|
10888
|
+
Realm-safe check (GHSA-hpcv-96wg-7vj8): nodeType-based detection
|
|
10889
|
+
rather than `instanceof DocumentFragment`, which is realm-bound and
|
|
10890
|
+
silently skipped foreign-realm shadow roots (e.g.
|
|
10891
|
+
iframe.contentDocument attachShadow). */
|
|
10892
|
+
if (isElement) {
|
|
10893
|
+
const sr = getShadowRoot(node);
|
|
10894
|
+
if (_isDocumentFragment(sr)) {
|
|
10895
|
+
/* Push the deferred sanitise first so it pops after the shadow
|
|
10896
|
+
walk we push next, i.e. nested shadow roots are discovered
|
|
10897
|
+
before this one is sanitised. */
|
|
10898
|
+
stack.push({
|
|
10899
|
+
node: null,
|
|
10900
|
+
shadow: sr
|
|
10901
|
+
}, {
|
|
10902
|
+
node: sr,
|
|
10903
|
+
shadow: null
|
|
10904
|
+
});
|
|
10905
|
+
}
|
|
10906
|
+
}
|
|
10907
|
+
}
|
|
10908
|
+
};
|
|
9570
10909
|
// eslint-disable-next-line complexity
|
|
9571
10910
|
DOMPurify.sanitize = function (dirty) {
|
|
9572
10911
|
let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
@@ -9583,13 +10922,9 @@ function requirePurify_cjs () {
|
|
|
9583
10922
|
}
|
|
9584
10923
|
/* Stringify, in case dirty is an object */
|
|
9585
10924
|
if (typeof dirty !== 'string' && !_isNode(dirty)) {
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
throw typeErrorCreate('dirty is not a string, aborting');
|
|
9590
|
-
}
|
|
9591
|
-
} else {
|
|
9592
|
-
throw typeErrorCreate('toString is not a function');
|
|
10925
|
+
dirty = stringifyValue(dirty);
|
|
10926
|
+
if (typeof dirty !== 'string') {
|
|
10927
|
+
throw typeErrorCreate('dirty is not a string, aborting');
|
|
9593
10928
|
}
|
|
9594
10929
|
}
|
|
9595
10930
|
/* Return dirty HTML if DOMPurify cannot run */
|
|
@@ -9597,24 +10932,90 @@ function requirePurify_cjs () {
|
|
|
9597
10932
|
return dirty;
|
|
9598
10933
|
}
|
|
9599
10934
|
/* Assign config vars */
|
|
9600
|
-
if (
|
|
10935
|
+
if (SET_CONFIG) {
|
|
10936
|
+
/* Persistent setConfig() path: _parseConfig is skipped, so the sets are
|
|
10937
|
+
* not re-derived per call. Restore them from the pristine bindings
|
|
10938
|
+
* captured at setConfig() time so a previous call's hook clone (mutated
|
|
10939
|
+
* below) does not carry over. */
|
|
10940
|
+
ALLOWED_TAGS = SET_CONFIG_ALLOWED_TAGS;
|
|
10941
|
+
ALLOWED_ATTR = SET_CONFIG_ALLOWED_ATTR;
|
|
10942
|
+
} else {
|
|
9601
10943
|
_parseConfig(cfg);
|
|
9602
10944
|
}
|
|
10945
|
+
/* Clone the hook-mutable allowlists before the walk whenever an
|
|
10946
|
+
* uponSanitize* hook is registered. The hook event exposes ALLOWED_TAGS
|
|
10947
|
+
* and ALLOWED_ATTR by reference (as allowedTags / allowedAttributes), so
|
|
10948
|
+
* a hook that widens them would otherwise mutate the shared set
|
|
10949
|
+
* permanently: across later calls and across every element. Cloning per
|
|
10950
|
+
* walk keeps documented in-call widening working while scoping it to the
|
|
10951
|
+
* call. A single guard for both config paths - the per-call path rebinds
|
|
10952
|
+
* the sets in _parseConfig each call, the persistent path restores them
|
|
10953
|
+
* from the captured bindings just above - so the two cannot diverge. */
|
|
10954
|
+
if (hooks.uponSanitizeElement.length > 0 || hooks.uponSanitizeAttribute.length > 0) {
|
|
10955
|
+
ALLOWED_TAGS = clone(ALLOWED_TAGS);
|
|
10956
|
+
}
|
|
10957
|
+
if (hooks.uponSanitizeAttribute.length > 0) {
|
|
10958
|
+
ALLOWED_ATTR = clone(ALLOWED_ATTR);
|
|
10959
|
+
}
|
|
9603
10960
|
/* Clean up removed elements */
|
|
9604
10961
|
DOMPurify.removed = [];
|
|
9605
|
-
/*
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
9611
|
-
|
|
9612
|
-
|
|
10962
|
+
/* Resolve IN_PLACE for this call without mutating persistent config.
|
|
10963
|
+
Writing the IN_PLACE closure variable here leaks under setConfig(),
|
|
10964
|
+
where _parseConfig is skipped on later calls: a single string call would
|
|
10965
|
+
disable in-place mode for every subsequent node call, returning a
|
|
10966
|
+
sanitized copy while leaving the caller's node — which in-place callers
|
|
10967
|
+
keep using and whose return value they ignore — unsanitized. REPORT-2. */
|
|
10968
|
+
const inPlace = IN_PLACE && typeof dirty !== 'string' && _isNode(dirty);
|
|
10969
|
+
if (inPlace) {
|
|
10970
|
+
/* Declarative-partial-updates / streaming pre-pass: sever every patch
|
|
10971
|
+
linkage across the live tree BEFORE the walk, so no patch can fire
|
|
10972
|
+
mid-walk and inject into an already-processed region. Runs first, so
|
|
10973
|
+
it also covers the forbidden/clobbered roots that throw below. */
|
|
10974
|
+
_neutralizePatchLinkage(dirty);
|
|
10975
|
+
/* Do some early pre-sanitization to avoid unsafe root nodes.
|
|
10976
|
+
Read nodeName through the cached prototype getter — a clobbering
|
|
10977
|
+
child named "nodeName" on the form root would otherwise shadow
|
|
10978
|
+
the property and let this check skip the root-allowlist
|
|
10979
|
+
validation entirely. */
|
|
10980
|
+
const nn = getNodeName ? getNodeName(dirty) : dirty.nodeName;
|
|
10981
|
+
if (typeof nn === 'string') {
|
|
10982
|
+
const tagName = transformCaseFunc(nn);
|
|
9613
10983
|
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
|
|
10984
|
+
/* Fail closed on a live root: neutralize handlers/children before
|
|
10985
|
+
throwing, exactly as the mid-walk abort path does. */
|
|
10986
|
+
_neutralizeRoot(dirty);
|
|
9614
10987
|
throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
|
|
9615
10988
|
}
|
|
9616
10989
|
}
|
|
9617
|
-
|
|
10990
|
+
/* Pre-flight the root through _isClobbered. The iterator-driven
|
|
10991
|
+
removal path can not detach a parent-less root: _forceRemove
|
|
10992
|
+
falls through to Element.prototype.remove(), which per spec
|
|
10993
|
+
is a no-op on a node with no parent. A clobbered root would
|
|
10994
|
+
then survive the main loop with its attributes uninspected,
|
|
10995
|
+
because _sanitizeAttributes early-returns on _isClobbered. The
|
|
10996
|
+
result would be an attacker-controlled form, complete with any
|
|
10997
|
+
event-handler attributes the caller passed in, handed back to
|
|
10998
|
+
the application unsanitized. Refuse to sanitize such a root
|
|
10999
|
+
the same way we refuse a forbidden tag. GHSA-r47g-fvhr-h676. */
|
|
11000
|
+
if (_isClobbered(dirty)) {
|
|
11001
|
+
/* Fail closed on a live clobbered root before throwing.
|
|
11002
|
+
_neutralizeRoot's reads are clobber-safe (cached getters); the
|
|
11003
|
+
form's non-clobbered descendants, e.g. an armed <img>, are scrubbed. */
|
|
11004
|
+
_neutralizeRoot(dirty);
|
|
11005
|
+
throw typeErrorCreate('root node is clobbered and cannot be sanitized in-place');
|
|
11006
|
+
}
|
|
11007
|
+
/* Sanitize attached shadow roots before the main iterator runs.
|
|
11008
|
+
The iterator does not descend into shadow trees. Same fail-closed
|
|
11009
|
+
barrier as the main walk (campaign-3 F2): a custom-element reaction
|
|
11010
|
+
inside a shadow root could abort this pre-pass before the walk runs,
|
|
11011
|
+
which would otherwise leave the entire live tree unsanitized. */
|
|
11012
|
+
try {
|
|
11013
|
+
_sanitizeAttachedShadowRoots(dirty);
|
|
11014
|
+
} catch (error) {
|
|
11015
|
+
_neutralizeRoot(dirty);
|
|
11016
|
+
throw error;
|
|
11017
|
+
}
|
|
11018
|
+
} else if (_isNode(dirty)) {
|
|
9618
11019
|
/* If dirty is a DOM element, append to an empty document to avoid
|
|
9619
11020
|
elements being stripped by the parser */
|
|
9620
11021
|
body = _initDocument('<!---->');
|
|
@@ -9628,12 +11029,18 @@ function requirePurify_cjs () {
|
|
|
9628
11029
|
// eslint-disable-next-line unicorn/prefer-dom-node-append
|
|
9629
11030
|
body.appendChild(importedNode);
|
|
9630
11031
|
}
|
|
11032
|
+
/* Clonable shadow roots are deep-cloned by importNode(); sanitize
|
|
11033
|
+
them before the main iterator runs, since the iterator does not
|
|
11034
|
+
descend into shadow trees. The walk routes every read through a
|
|
11035
|
+
cached prototype getter so clobbering descendants on a form root
|
|
11036
|
+
cannot hide a shadow host from this pass. */
|
|
11037
|
+
_sanitizeAttachedShadowRoots(importedNode);
|
|
9631
11038
|
} else {
|
|
9632
11039
|
/* Exit directly if we have nothing to do */
|
|
9633
11040
|
if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
|
|
9634
11041
|
// eslint-disable-next-line unicorn/prefer-includes
|
|
9635
11042
|
dirty.indexOf('<') === -1) {
|
|
9636
|
-
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ?
|
|
11043
|
+
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(dirty) : dirty;
|
|
9637
11044
|
}
|
|
9638
11045
|
/* Initialize the document to work on */
|
|
9639
11046
|
body = _initDocument(dirty);
|
|
@@ -9647,31 +11054,71 @@ function requirePurify_cjs () {
|
|
|
9647
11054
|
_forceRemove(body.firstChild);
|
|
9648
11055
|
}
|
|
9649
11056
|
/* Get node iterator */
|
|
9650
|
-
const
|
|
9651
|
-
/* Now start iterating over the created document
|
|
9652
|
-
|
|
9653
|
-
|
|
9654
|
-
|
|
9655
|
-
|
|
9656
|
-
|
|
9657
|
-
|
|
9658
|
-
|
|
9659
|
-
|
|
11057
|
+
const walkRoot = inPlace ? dirty : body;
|
|
11058
|
+
/* Now start iterating over the created document.
|
|
11059
|
+
The walk runs inside an exception barrier (campaign-3 F2): a re-entrant
|
|
11060
|
+
engine/custom-element mutation can detach a node mid-walk so
|
|
11061
|
+
`_forceRemove`'s parentless guard throws, aborting the loop. Without the
|
|
11062
|
+
barrier the caller's in-place tree would be left half-sanitized with the
|
|
11063
|
+
unvisited tail still armed. _createNodeIterator itself is inside the
|
|
11064
|
+
barrier too: constructing the iterator dereferences the root's document,
|
|
11065
|
+
and any failure there (e.g. an exotic/clobbered root) must still fail
|
|
11066
|
+
closed rather than skip the neutralize. On any throw we fail closed -
|
|
11067
|
+
strip the in-place root bare - then rethrow so the existing throw
|
|
11068
|
+
contract is preserved. (String/DOM-copy paths never return the partial
|
|
11069
|
+
body, so the propagating throw is already fail-closed there.) */
|
|
11070
|
+
try {
|
|
11071
|
+
const nodeIterator = _createNodeIterator(walkRoot);
|
|
11072
|
+
while (currentNode = nodeIterator.nextNode()) {
|
|
11073
|
+
/* Sanitize tags and elements */
|
|
11074
|
+
_sanitizeElements(currentNode, walkRoot);
|
|
11075
|
+
/* Check attributes next */
|
|
11076
|
+
_sanitizeAttributes(currentNode);
|
|
11077
|
+
/* Shadow DOM detected, sanitize it.
|
|
11078
|
+
Realm-safe check (GHSA-hpcv-96wg-7vj8): nodeType-based detection
|
|
11079
|
+
instead of instanceof, so foreign-realm <template>.content is
|
|
11080
|
+
walked correctly. */
|
|
11081
|
+
if (_isDocumentFragment(currentNode.content)) {
|
|
11082
|
+
_sanitizeShadowDOM2(currentNode.content);
|
|
11083
|
+
}
|
|
11084
|
+
}
|
|
11085
|
+
} catch (error) {
|
|
11086
|
+
if (inPlace) {
|
|
11087
|
+
_neutralizeRoot(dirty);
|
|
11088
|
+
/* Nodes _forceRemove'd earlier in the aborted walk are already
|
|
11089
|
+
detached from the root, so _neutralizeRoot's subtree pass does not
|
|
11090
|
+
reach them. Defuse them too, mirroring the success-path loop below. */
|
|
11091
|
+
arrayForEach(DOMPurify.removed, entry => {
|
|
11092
|
+
if (entry.element) {
|
|
11093
|
+
_neutralizeSubtree(entry.element);
|
|
11094
|
+
}
|
|
11095
|
+
});
|
|
9660
11096
|
}
|
|
11097
|
+
throw error;
|
|
9661
11098
|
}
|
|
9662
11099
|
/* If we sanitized `dirty` in-place, return it. */
|
|
9663
|
-
if (
|
|
11100
|
+
if (inPlace) {
|
|
11101
|
+
/* Fail-closed completion of the audit-5 F1 fix: every node removed from
|
|
11102
|
+
the caller's live tree is detached but may still hold a queued
|
|
11103
|
+
resource-event handler that fires in page scope after we return. The
|
|
11104
|
+
move-hoist covers only disallowed-tag KEEP_CONTENT removals; strip the
|
|
11105
|
+
non-allow-listed attributes off every other removed subtree (clobber,
|
|
11106
|
+
mXSS, namespace, comments, KEEP_CONTENT:false, …) so those handlers are
|
|
11107
|
+
cancelled before any event can fire. Runs synchronously, pre-return. */
|
|
11108
|
+
arrayForEach(DOMPurify.removed, entry => {
|
|
11109
|
+
if (entry.element) {
|
|
11110
|
+
_neutralizeSubtree(entry.element);
|
|
11111
|
+
}
|
|
11112
|
+
});
|
|
11113
|
+
if (SAFE_FOR_TEMPLATES) {
|
|
11114
|
+
_scrubTemplateExpressions2(dirty);
|
|
11115
|
+
}
|
|
9664
11116
|
return dirty;
|
|
9665
11117
|
}
|
|
9666
11118
|
/* Return sanitized string or DOM */
|
|
9667
11119
|
if (RETURN_DOM) {
|
|
9668
11120
|
if (SAFE_FOR_TEMPLATES) {
|
|
9669
|
-
body
|
|
9670
|
-
let html = body.innerHTML;
|
|
9671
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
9672
|
-
html = stringReplace(html, expr, ' ');
|
|
9673
|
-
});
|
|
9674
|
-
body.innerHTML = html;
|
|
11121
|
+
_scrubTemplateExpressions2(body);
|
|
9675
11122
|
}
|
|
9676
11123
|
if (RETURN_DOM_FRAGMENT) {
|
|
9677
11124
|
returnNode = createDocumentFragment.call(body.ownerDocument);
|
|
@@ -9701,20 +11148,28 @@ function requirePurify_cjs () {
|
|
|
9701
11148
|
}
|
|
9702
11149
|
/* Sanitize final string template-safe */
|
|
9703
11150
|
if (SAFE_FOR_TEMPLATES) {
|
|
9704
|
-
|
|
9705
|
-
serializedHTML = stringReplace(serializedHTML, expr, ' ');
|
|
9706
|
-
});
|
|
11151
|
+
serializedHTML = _stripTemplateExpressions(serializedHTML);
|
|
9707
11152
|
}
|
|
9708
|
-
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ?
|
|
11153
|
+
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(serializedHTML) : serializedHTML;
|
|
9709
11154
|
};
|
|
9710
11155
|
DOMPurify.setConfig = function () {
|
|
9711
11156
|
let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
9712
11157
|
_parseConfig(cfg);
|
|
9713
11158
|
SET_CONFIG = true;
|
|
11159
|
+
SET_CONFIG_ALLOWED_TAGS = ALLOWED_TAGS;
|
|
11160
|
+
SET_CONFIG_ALLOWED_ATTR = ALLOWED_ATTR;
|
|
9714
11161
|
};
|
|
9715
11162
|
DOMPurify.clearConfig = function () {
|
|
9716
11163
|
CONFIG = null;
|
|
9717
11164
|
SET_CONFIG = false;
|
|
11165
|
+
SET_CONFIG_ALLOWED_TAGS = null;
|
|
11166
|
+
SET_CONFIG_ALLOWED_ATTR = null;
|
|
11167
|
+
// Drop any caller-supplied Trusted Types policy so it cannot poison later
|
|
11168
|
+
// `RETURN_TRUSTED_TYPE` output. The internal default policy (cached, and
|
|
11169
|
+
// never recreated — Trusted Types throws on duplicate names) is restored by
|
|
11170
|
+
// the next `_parseConfig`. See GHSA-vxr8-fq34-vvx9.
|
|
11171
|
+
trustedTypesPolicy = defaultTrustedTypesPolicy;
|
|
11172
|
+
emptyHTML = '';
|
|
9718
11173
|
};
|
|
9719
11174
|
DOMPurify.isValidAttribute = function (tag, attr, value) {
|
|
9720
11175
|
/* Initialize shared config vars if necessary. */
|
|
@@ -9729,9 +11184,19 @@ function requirePurify_cjs () {
|
|
|
9729
11184
|
if (typeof hookFunction !== 'function') {
|
|
9730
11185
|
return;
|
|
9731
11186
|
}
|
|
11187
|
+
/* Reject unknown entry points. Without this, a non-hook key (e.g.
|
|
11188
|
+
* '__proto__') indexes off the prototype chain rather than a real
|
|
11189
|
+
* hook array, and arrayPush then writes to Object.prototype. Guard
|
|
11190
|
+
* with an own-property check against the known hook names. */
|
|
11191
|
+
if (!objectHasOwnProperty(hooks, entryPoint)) {
|
|
11192
|
+
return;
|
|
11193
|
+
}
|
|
9732
11194
|
arrayPush(hooks[entryPoint], hookFunction);
|
|
9733
11195
|
};
|
|
9734
11196
|
DOMPurify.removeHook = function (entryPoint, hookFunction) {
|
|
11197
|
+
if (!objectHasOwnProperty(hooks, entryPoint)) {
|
|
11198
|
+
return undefined;
|
|
11199
|
+
}
|
|
9735
11200
|
if (hookFunction !== undefined) {
|
|
9736
11201
|
const index = arrayLastIndexOf(hooks[entryPoint], hookFunction);
|
|
9737
11202
|
return index === -1 ? undefined : arraySplice(hooks[entryPoint], index, 1)[0];
|
|
@@ -9739,6 +11204,9 @@ function requirePurify_cjs () {
|
|
|
9739
11204
|
return arrayPop(hooks[entryPoint]);
|
|
9740
11205
|
};
|
|
9741
11206
|
DOMPurify.removeHooks = function (entryPoint) {
|
|
11207
|
+
if (!objectHasOwnProperty(hooks, entryPoint)) {
|
|
11208
|
+
return;
|
|
11209
|
+
}
|
|
9742
11210
|
hooks[entryPoint] = [];
|
|
9743
11211
|
};
|
|
9744
11212
|
DOMPurify.removeAllHooks = function () {
|
|
@@ -9999,10 +11467,26 @@ var WalletBackground = /*#__PURE__*/function (_Background) {
|
|
|
9999
11467
|
}(Background);
|
|
10000
11468
|
|
|
10001
11469
|
/**
|
|
10002
|
-
* Generate a unique id compatible with HTML element id
|
|
11470
|
+
* Generate a unique id compatible with HTML element id.
|
|
11471
|
+
* Uses the Web Crypto API for cryptographically strong randomness, with a
|
|
11472
|
+
* deterministic fallback only when crypto is unavailable.
|
|
10003
11473
|
*/
|
|
10004
11474
|
function getUniqueId() {
|
|
10005
|
-
|
|
11475
|
+
var letters = 'abcdefghijklmnopqrstuvwxyz';
|
|
11476
|
+
var length = 10;
|
|
11477
|
+
var bytes = new Uint8Array(length);
|
|
11478
|
+
if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {
|
|
11479
|
+
crypto.getRandomValues(bytes);
|
|
11480
|
+
} else {
|
|
11481
|
+
for (var i = 0; i < length; i++) {
|
|
11482
|
+
bytes[i] = (i * 31 + 7) % 256;
|
|
11483
|
+
}
|
|
11484
|
+
}
|
|
11485
|
+
var id = '';
|
|
11486
|
+
for (var _i = 0; _i < length; _i++) {
|
|
11487
|
+
id += letters[bytes[_i] % letters.length];
|
|
11488
|
+
}
|
|
11489
|
+
return id;
|
|
10006
11490
|
}
|
|
10007
11491
|
|
|
10008
11492
|
function getFlypayV2Button(widgetId) {
|
|
@@ -14035,6 +15519,54 @@ var MultiWidget = /*#__PURE__*/function () {
|
|
|
14035
15519
|
validate_card_types: validateCardNumberInput
|
|
14036
15520
|
});
|
|
14037
15521
|
}
|
|
15522
|
+
/**
|
|
15523
|
+
* Current method restricts which card schemes the card number input accepts, without changing which card
|
|
15524
|
+
* icons the widget displays. Scheme identifiers are matched case-insensitively and ignoring surrounding
|
|
15525
|
+
* whitespace; unsupported identifiers are ignored with a warning, so a list in which every identifier is
|
|
15526
|
+
* unsupported applies no restriction at all.
|
|
15527
|
+
*
|
|
15528
|
+
* Takes precedence over setSupportedCardIcons: when a non-empty list is supplied here, it is the only list
|
|
15529
|
+
* used for validation and the validateCardNumberInput flag of setSupportedCardIcons no longer affects it.
|
|
15530
|
+
* Calling this method more than once replaces the previous list rather than merging with it.
|
|
15531
|
+
*
|
|
15532
|
+
* Must be called before load().
|
|
15533
|
+
*
|
|
15534
|
+
* @example
|
|
15535
|
+
*
|
|
15536
|
+
* widget.setAllowedCardSchemes(['visa', 'mastercard']);
|
|
15537
|
+
*
|
|
15538
|
+
* @param {string[]} schemes - [CARD_SCHEME]{@link CARD_SCHEME}
|
|
15539
|
+
*/
|
|
15540
|
+
}, {
|
|
15541
|
+
key: "setAllowedCardSchemes",
|
|
15542
|
+
value: function setAllowedCardSchemes(schemes) {
|
|
15543
|
+
var vocabulary = ObjectHelper.values(CARD_SCHEME);
|
|
15544
|
+
var allowedSchemes = [];
|
|
15545
|
+
var _iterator = _createForOfIteratorHelper(Array.isArray(schemes) ? schemes : []),
|
|
15546
|
+
_step;
|
|
15547
|
+
try {
|
|
15548
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
15549
|
+
var entry = _step.value;
|
|
15550
|
+
var scheme = typeof entry === 'string' ? entry.trim().toLowerCase() : '';
|
|
15551
|
+
if (vocabulary.indexOf(scheme) === -1) {
|
|
15552
|
+
console.warn("Widget::setAllowedCardSchemes: unsupported card scheme ".concat(entry));
|
|
15553
|
+
continue;
|
|
15554
|
+
}
|
|
15555
|
+
if (allowedSchemes.indexOf(scheme) === -1) {
|
|
15556
|
+
allowedSchemes.push(scheme);
|
|
15557
|
+
}
|
|
15558
|
+
}
|
|
15559
|
+
} catch (err) {
|
|
15560
|
+
_iterator.e(err);
|
|
15561
|
+
} finally {
|
|
15562
|
+
_iterator.f();
|
|
15563
|
+
}
|
|
15564
|
+
if (allowedSchemes.length) {
|
|
15565
|
+
this.link.setParams({
|
|
15566
|
+
allowed_card_schemes: allowedSchemes.join(',')
|
|
15567
|
+
});
|
|
15568
|
+
}
|
|
15569
|
+
}
|
|
14038
15570
|
/**
|
|
14039
15571
|
* Current method can hide prevent the widget from showing the error messages
|
|
14040
15572
|
*
|
|
@@ -19134,6 +20666,90 @@ var EVENT$1;
|
|
|
19134
20666
|
EVENT["ON_SHIPPING_OPTIONS_CHANGE"] = "onShippingOptionsChange";
|
|
19135
20667
|
})(EVENT$1 || (EVENT$1 = {}));
|
|
19136
20668
|
|
|
20669
|
+
/** TTL for a cached service configuration: 1 day, in milliseconds. */
|
|
20670
|
+
var CACHE_TTL_MS = 86400000;
|
|
20671
|
+
/** Builds the `localStorage` key used to cache a service's configuration. */
|
|
20672
|
+
function serviceConfigStorageKey(serviceId) {
|
|
20673
|
+
return "paydock-serviceConfig-".concat(serviceId);
|
|
20674
|
+
}
|
|
20675
|
+
/**
|
|
20676
|
+
* Removes the cached service configuration for the given service ID, forcing the next
|
|
20677
|
+
* fetch to go to the network. Called on error / unavailable flows so a potentially
|
|
20678
|
+
* stale cached config is not reused after something changed server-side. No-throw when
|
|
20679
|
+
* `localStorage` is unavailable.
|
|
20680
|
+
*
|
|
20681
|
+
* @param serviceId - The service ID whose cached configuration should be cleared.
|
|
20682
|
+
*/
|
|
20683
|
+
function clearServiceConfigCache(serviceId) {
|
|
20684
|
+
try {
|
|
20685
|
+
localStorage.removeItem(serviceConfigStorageKey(serviceId));
|
|
20686
|
+
} catch (_error) {
|
|
20687
|
+
// localStorage may be unavailable; nothing to clear
|
|
20688
|
+
}
|
|
20689
|
+
}
|
|
20690
|
+
/**
|
|
20691
|
+
* Fetches the wallet service configuration, backed by a 1-day `localStorage` cache
|
|
20692
|
+
* keyed on the service ID. Falls back gracefully (no caching) when `localStorage`
|
|
20693
|
+
* is unavailable (e.g. private browsing). Shared by the instance `load()` flow and
|
|
20694
|
+
* the static availability checks so both reuse the same cached fetch.
|
|
20695
|
+
*
|
|
20696
|
+
* @param api - The configured API client (env must already be set).
|
|
20697
|
+
* @param serviceId - The service ID to fetch the configuration for.
|
|
20698
|
+
* @returns The service configuration response.
|
|
20699
|
+
*/
|
|
20700
|
+
function getServiceConfigCached(api, serviceId) {
|
|
20701
|
+
return __awaiter(this, void 0, void 0, /*#__PURE__*/_regenerator().m(function _callee() {
|
|
20702
|
+
var storageKey, cachedData, _JSON$parse, data, timestamp, serviceConfigResponse, encodedData;
|
|
20703
|
+
return _regenerator().w(function (_context) {
|
|
20704
|
+
while (1) switch (_context.p = _context.n) {
|
|
20705
|
+
case 0:
|
|
20706
|
+
storageKey = serviceConfigStorageKey(serviceId);
|
|
20707
|
+
_context.p = 1;
|
|
20708
|
+
cachedData = localStorage.getItem(storageKey);
|
|
20709
|
+
if (!cachedData) {
|
|
20710
|
+
_context.n = 5;
|
|
20711
|
+
break;
|
|
20712
|
+
}
|
|
20713
|
+
_context.p = 2;
|
|
20714
|
+
_JSON$parse = JSON.parse(atob(cachedData)), data = _JSON$parse.data, timestamp = _JSON$parse.timestamp;
|
|
20715
|
+
if (!(Date.now() - timestamp < CACHE_TTL_MS)) {
|
|
20716
|
+
_context.n = 3;
|
|
20717
|
+
break;
|
|
20718
|
+
}
|
|
20719
|
+
return _context.a(2, JSON.parse(data));
|
|
20720
|
+
case 3:
|
|
20721
|
+
_context.n = 5;
|
|
20722
|
+
break;
|
|
20723
|
+
case 4:
|
|
20724
|
+
_context.p = 4;
|
|
20725
|
+
_context.v;
|
|
20726
|
+
localStorage.removeItem(storageKey);
|
|
20727
|
+
case 5:
|
|
20728
|
+
_context.n = 7;
|
|
20729
|
+
break;
|
|
20730
|
+
case 6:
|
|
20731
|
+
_context.p = 6;
|
|
20732
|
+
_context.v;
|
|
20733
|
+
case 7:
|
|
20734
|
+
_context.n = 8;
|
|
20735
|
+
return api.service().getConfig(serviceId);
|
|
20736
|
+
case 8:
|
|
20737
|
+
serviceConfigResponse = _context.v;
|
|
20738
|
+
try {
|
|
20739
|
+
encodedData = btoa(JSON.stringify({
|
|
20740
|
+
data: JSON.stringify(serviceConfigResponse),
|
|
20741
|
+
timestamp: Date.now()
|
|
20742
|
+
}));
|
|
20743
|
+
localStorage.setItem(storageKey, encodedData);
|
|
20744
|
+
} catch (_error) {
|
|
20745
|
+
// localStorage may be unavailable; proceed without caching
|
|
20746
|
+
}
|
|
20747
|
+
return _context.a(2, serviceConfigResponse);
|
|
20748
|
+
}
|
|
20749
|
+
}, _callee, null, [[2, 4], [1, 6]]);
|
|
20750
|
+
}));
|
|
20751
|
+
}
|
|
20752
|
+
|
|
19137
20753
|
/**
|
|
19138
20754
|
* @classdesc Abstract base class for Open Wallet buttons. **Do not instantiate directly.**
|
|
19139
20755
|
* Use {@link ApplePayOpenWalletButton} or {@link GooglePayOpenWalletButton} instead.
|
|
@@ -19167,10 +20783,30 @@ var OpenWalletButtons = /*#__PURE__*/function () {
|
|
|
19167
20783
|
this.api = new ApiInternal(publicKeyOrAccessToken, this.getApiAuthType(publicKeyOrAccessToken));
|
|
19168
20784
|
this.serviceId = serviceId;
|
|
19169
20785
|
this.meta = meta;
|
|
20786
|
+
this.registerCacheInvalidationOnError();
|
|
19170
20787
|
this.validateBaseMeta();
|
|
19171
20788
|
this.validateWalletMeta();
|
|
19172
20789
|
}
|
|
20790
|
+
/**
|
|
20791
|
+
* Invalidates the cached service configuration whenever an error or unavailable
|
|
20792
|
+
* event fires, so the next `load()` re-fetches a fresh config. The cache is keyed
|
|
20793
|
+
* on the service ID with a 1-day TTL; clearing it on failure means a config that
|
|
20794
|
+
* changed server-side (and caused the failure) is not reused until the TTL expires.
|
|
20795
|
+
* Registered once per button, independently of any merchant-registered handlers.
|
|
20796
|
+
*
|
|
20797
|
+
* @private
|
|
20798
|
+
*/
|
|
19173
20799
|
return _createClass(OpenWalletButtons, [{
|
|
20800
|
+
key: "registerCacheInvalidationOnError",
|
|
20801
|
+
value: function registerCacheInvalidationOnError() {
|
|
20802
|
+
var _this = this;
|
|
20803
|
+
var invalidate = function invalidate() {
|
|
20804
|
+
return clearServiceConfigCache(_this.serviceId);
|
|
20805
|
+
};
|
|
20806
|
+
this.eventEmitter.subscribe(EVENT$1.ERROR, invalidate);
|
|
20807
|
+
this.eventEmitter.subscribe(EVENT$1.UNAVAILABLE, invalidate);
|
|
20808
|
+
}
|
|
20809
|
+
}, {
|
|
19174
20810
|
key: "getApiAuthType",
|
|
19175
20811
|
value: function getApiAuthType(publicKeyOrAccessToken) {
|
|
19176
20812
|
if (AccessToken.validateJWT(publicKeyOrAccessToken)) {
|
|
@@ -19204,57 +20840,7 @@ var OpenWalletButtons = /*#__PURE__*/function () {
|
|
|
19204
20840
|
}, {
|
|
19205
20841
|
key: "getOpenWalletServiceConfig",
|
|
19206
20842
|
value: function getOpenWalletServiceConfig() {
|
|
19207
|
-
return
|
|
19208
|
-
var storageKey, cachedData, _JSON$parse, data, timestamp, oneDay, serviceConfigResponse, encodedData;
|
|
19209
|
-
return _regenerator().w(function (_context) {
|
|
19210
|
-
while (1) switch (_context.p = _context.n) {
|
|
19211
|
-
case 0:
|
|
19212
|
-
storageKey = "serviceConfig-".concat(this.serviceId);
|
|
19213
|
-
_context.p = 1;
|
|
19214
|
-
cachedData = localStorage.getItem(storageKey);
|
|
19215
|
-
if (!cachedData) {
|
|
19216
|
-
_context.n = 5;
|
|
19217
|
-
break;
|
|
19218
|
-
}
|
|
19219
|
-
_context.p = 2;
|
|
19220
|
-
_JSON$parse = JSON.parse(atob(cachedData)), data = _JSON$parse.data, timestamp = _JSON$parse.timestamp;
|
|
19221
|
-
oneDay = 86400000;
|
|
19222
|
-
if (!(Date.now() - timestamp < oneDay)) {
|
|
19223
|
-
_context.n = 3;
|
|
19224
|
-
break;
|
|
19225
|
-
}
|
|
19226
|
-
return _context.a(2, JSON.parse(data));
|
|
19227
|
-
case 3:
|
|
19228
|
-
_context.n = 5;
|
|
19229
|
-
break;
|
|
19230
|
-
case 4:
|
|
19231
|
-
_context.p = 4;
|
|
19232
|
-
_context.v;
|
|
19233
|
-
localStorage.removeItem(storageKey);
|
|
19234
|
-
case 5:
|
|
19235
|
-
_context.n = 7;
|
|
19236
|
-
break;
|
|
19237
|
-
case 6:
|
|
19238
|
-
_context.p = 6;
|
|
19239
|
-
_context.v;
|
|
19240
|
-
case 7:
|
|
19241
|
-
_context.n = 8;
|
|
19242
|
-
return this.api.service().getConfig(this.serviceId);
|
|
19243
|
-
case 8:
|
|
19244
|
-
serviceConfigResponse = _context.v;
|
|
19245
|
-
try {
|
|
19246
|
-
encodedData = btoa(JSON.stringify({
|
|
19247
|
-
data: JSON.stringify(serviceConfigResponse),
|
|
19248
|
-
timestamp: Date.now()
|
|
19249
|
-
}));
|
|
19250
|
-
localStorage.setItem(storageKey, encodedData);
|
|
19251
|
-
} catch (_error) {
|
|
19252
|
-
// localStorage may be unavailable; proceed without caching
|
|
19253
|
-
}
|
|
19254
|
-
return _context.a(2, serviceConfigResponse);
|
|
19255
|
-
}
|
|
19256
|
-
}, _callee, this, [[2, 4], [1, 6]]);
|
|
19257
|
-
}));
|
|
20843
|
+
return getServiceConfigCached(this.api, this.serviceId);
|
|
19258
20844
|
}
|
|
19259
20845
|
}, {
|
|
19260
20846
|
key: "handleOnError",
|
|
@@ -19289,13 +20875,13 @@ var OpenWalletButtons = /*#__PURE__*/function () {
|
|
|
19289
20875
|
}, {
|
|
19290
20876
|
key: "load",
|
|
19291
20877
|
value: function load() {
|
|
19292
|
-
var
|
|
20878
|
+
var _this2 = this;
|
|
19293
20879
|
this.getOpenWalletServiceConfig().then(function (serviceConfig) {
|
|
19294
|
-
|
|
19295
|
-
|
|
19296
|
-
|
|
20880
|
+
_this2.validateServiceType(serviceConfig);
|
|
20881
|
+
_this2.walletService = _this2.createWalletService(serviceConfig);
|
|
20882
|
+
_this2.walletService.load();
|
|
19297
20883
|
})["catch"](function (error) {
|
|
19298
|
-
|
|
20884
|
+
_this2.handleOnError(error instanceof Error ? error : new Error(String(error)), exports.ERROR_OPERATION.CONFIGURATION);
|
|
19299
20885
|
});
|
|
19300
20886
|
}
|
|
19301
20887
|
/**
|
|
@@ -19493,28 +21079,28 @@ var OpenWalletButtons = /*#__PURE__*/function () {
|
|
|
19493
21079
|
}, {
|
|
19494
21080
|
key: "onSuccess",
|
|
19495
21081
|
value: function onSuccess(handler) {
|
|
19496
|
-
var
|
|
21082
|
+
var _this3 = this;
|
|
19497
21083
|
if (typeof handler === 'function') {
|
|
19498
21084
|
this.eventEmitter.subscribe(EVENT$1.SUCCESS, function (data) {
|
|
19499
|
-
return __awaiter(
|
|
19500
|
-
var
|
|
19501
|
-
return _regenerator().w(function (
|
|
19502
|
-
while (1) switch (
|
|
21085
|
+
return __awaiter(_this3, void 0, void 0, /*#__PURE__*/_regenerator().m(function _callee() {
|
|
21086
|
+
var _t;
|
|
21087
|
+
return _regenerator().w(function (_context) {
|
|
21088
|
+
while (1) switch (_context.p = _context.n) {
|
|
19503
21089
|
case 0:
|
|
19504
|
-
|
|
19505
|
-
|
|
21090
|
+
_context.p = 0;
|
|
21091
|
+
_context.n = 1;
|
|
19506
21092
|
return handler(data);
|
|
19507
21093
|
case 1:
|
|
19508
|
-
|
|
21094
|
+
_context.n = 3;
|
|
19509
21095
|
break;
|
|
19510
21096
|
case 2:
|
|
19511
|
-
|
|
19512
|
-
|
|
19513
|
-
console.warn('Merchant onSuccess handler threw (swallowed):',
|
|
21097
|
+
_context.p = 2;
|
|
21098
|
+
_t = _context.v;
|
|
21099
|
+
console.warn('Merchant onSuccess handler threw (swallowed):', _t);
|
|
19514
21100
|
case 3:
|
|
19515
|
-
return
|
|
21101
|
+
return _context.a(2);
|
|
19516
21102
|
}
|
|
19517
|
-
},
|
|
21103
|
+
}, _callee, null, [[0, 2]]);
|
|
19518
21104
|
}));
|
|
19519
21105
|
});
|
|
19520
21106
|
return;
|
|
@@ -19545,28 +21131,28 @@ var OpenWalletButtons = /*#__PURE__*/function () {
|
|
|
19545
21131
|
}, {
|
|
19546
21132
|
key: "onUnavailable",
|
|
19547
21133
|
value: function onUnavailable(handler) {
|
|
19548
|
-
var
|
|
21134
|
+
var _this4 = this;
|
|
19549
21135
|
if (typeof handler === 'function') {
|
|
19550
21136
|
this.eventEmitter.subscribe(EVENT$1.UNAVAILABLE, function (data) {
|
|
19551
|
-
return __awaiter(
|
|
19552
|
-
var
|
|
19553
|
-
return _regenerator().w(function (
|
|
19554
|
-
while (1) switch (
|
|
21137
|
+
return __awaiter(_this4, void 0, void 0, /*#__PURE__*/_regenerator().m(function _callee2() {
|
|
21138
|
+
var _t2;
|
|
21139
|
+
return _regenerator().w(function (_context2) {
|
|
21140
|
+
while (1) switch (_context2.p = _context2.n) {
|
|
19555
21141
|
case 0:
|
|
19556
|
-
|
|
19557
|
-
|
|
21142
|
+
_context2.p = 0;
|
|
21143
|
+
_context2.n = 1;
|
|
19558
21144
|
return handler(data);
|
|
19559
21145
|
case 1:
|
|
19560
|
-
|
|
21146
|
+
_context2.n = 3;
|
|
19561
21147
|
break;
|
|
19562
21148
|
case 2:
|
|
19563
|
-
|
|
19564
|
-
|
|
19565
|
-
console.warn('Merchant onUnavailable handler threw (swallowed):',
|
|
21149
|
+
_context2.p = 2;
|
|
21150
|
+
_t2 = _context2.v;
|
|
21151
|
+
console.warn('Merchant onUnavailable handler threw (swallowed):', _t2);
|
|
19566
21152
|
case 3:
|
|
19567
|
-
return
|
|
21153
|
+
return _context2.a(2);
|
|
19568
21154
|
}
|
|
19569
|
-
},
|
|
21155
|
+
}, _callee2, null, [[0, 2]]);
|
|
19570
21156
|
}));
|
|
19571
21157
|
});
|
|
19572
21158
|
return;
|
|
@@ -19598,28 +21184,28 @@ var OpenWalletButtons = /*#__PURE__*/function () {
|
|
|
19598
21184
|
}, {
|
|
19599
21185
|
key: "onError",
|
|
19600
21186
|
value: function onError(handler) {
|
|
19601
|
-
var
|
|
21187
|
+
var _this5 = this;
|
|
19602
21188
|
if (typeof handler === 'function') {
|
|
19603
21189
|
this.eventEmitter.subscribe(EVENT$1.ERROR, function (data) {
|
|
19604
|
-
return __awaiter(
|
|
19605
|
-
var
|
|
19606
|
-
return _regenerator().w(function (
|
|
19607
|
-
while (1) switch (
|
|
21190
|
+
return __awaiter(_this5, void 0, void 0, /*#__PURE__*/_regenerator().m(function _callee3() {
|
|
21191
|
+
var _t3;
|
|
21192
|
+
return _regenerator().w(function (_context3) {
|
|
21193
|
+
while (1) switch (_context3.p = _context3.n) {
|
|
19608
21194
|
case 0:
|
|
19609
|
-
|
|
19610
|
-
|
|
21195
|
+
_context3.p = 0;
|
|
21196
|
+
_context3.n = 1;
|
|
19611
21197
|
return handler(data);
|
|
19612
21198
|
case 1:
|
|
19613
|
-
|
|
21199
|
+
_context3.n = 3;
|
|
19614
21200
|
break;
|
|
19615
21201
|
case 2:
|
|
19616
|
-
|
|
19617
|
-
|
|
19618
|
-
console.warn('Merchant onError handler threw (swallowed):',
|
|
21202
|
+
_context3.p = 2;
|
|
21203
|
+
_t3 = _context3.v;
|
|
21204
|
+
console.warn('Merchant onError handler threw (swallowed):', _t3);
|
|
19619
21205
|
case 3:
|
|
19620
|
-
return
|
|
21206
|
+
return _context3.a(2);
|
|
19621
21207
|
}
|
|
19622
|
-
},
|
|
21208
|
+
}, _callee3, null, [[0, 2]]);
|
|
19623
21209
|
}));
|
|
19624
21210
|
});
|
|
19625
21211
|
return;
|
|
@@ -19650,28 +21236,28 @@ var OpenWalletButtons = /*#__PURE__*/function () {
|
|
|
19650
21236
|
}, {
|
|
19651
21237
|
key: "onCancel",
|
|
19652
21238
|
value: function onCancel(handler) {
|
|
19653
|
-
var
|
|
21239
|
+
var _this6 = this;
|
|
19654
21240
|
if (typeof handler === 'function') {
|
|
19655
21241
|
this.eventEmitter.subscribe(EVENT$1.CHECKOUT_CLOSE, function (data) {
|
|
19656
|
-
return __awaiter(
|
|
19657
|
-
var
|
|
19658
|
-
return _regenerator().w(function (
|
|
19659
|
-
while (1) switch (
|
|
21242
|
+
return __awaiter(_this6, void 0, void 0, /*#__PURE__*/_regenerator().m(function _callee4() {
|
|
21243
|
+
var _t4;
|
|
21244
|
+
return _regenerator().w(function (_context4) {
|
|
21245
|
+
while (1) switch (_context4.p = _context4.n) {
|
|
19660
21246
|
case 0:
|
|
19661
|
-
|
|
19662
|
-
|
|
21247
|
+
_context4.p = 0;
|
|
21248
|
+
_context4.n = 1;
|
|
19663
21249
|
return handler(data);
|
|
19664
21250
|
case 1:
|
|
19665
|
-
|
|
21251
|
+
_context4.n = 3;
|
|
19666
21252
|
break;
|
|
19667
21253
|
case 2:
|
|
19668
|
-
|
|
19669
|
-
|
|
19670
|
-
console.warn('Merchant onCancel handler threw (swallowed):',
|
|
21254
|
+
_context4.p = 2;
|
|
21255
|
+
_t4 = _context4.v;
|
|
21256
|
+
console.warn('Merchant onCancel handler threw (swallowed):', _t4);
|
|
19671
21257
|
case 3:
|
|
19672
|
-
return
|
|
21258
|
+
return _context4.a(2);
|
|
19673
21259
|
}
|
|
19674
|
-
},
|
|
21260
|
+
}, _callee4, null, [[0, 2]]);
|
|
19675
21261
|
}));
|
|
19676
21262
|
});
|
|
19677
21263
|
return;
|
|
@@ -19695,28 +21281,28 @@ var OpenWalletButtons = /*#__PURE__*/function () {
|
|
|
19695
21281
|
}, {
|
|
19696
21282
|
key: "onLoaded",
|
|
19697
21283
|
value: function onLoaded(handler) {
|
|
19698
|
-
var
|
|
21284
|
+
var _this7 = this;
|
|
19699
21285
|
if (typeof handler === 'function') {
|
|
19700
21286
|
this.eventEmitter.subscribe(EVENT$1.LOADED, function (data) {
|
|
19701
|
-
return __awaiter(
|
|
19702
|
-
var
|
|
19703
|
-
return _regenerator().w(function (
|
|
19704
|
-
while (1) switch (
|
|
21287
|
+
return __awaiter(_this7, void 0, void 0, /*#__PURE__*/_regenerator().m(function _callee5() {
|
|
21288
|
+
var _t5;
|
|
21289
|
+
return _regenerator().w(function (_context5) {
|
|
21290
|
+
while (1) switch (_context5.p = _context5.n) {
|
|
19705
21291
|
case 0:
|
|
19706
|
-
|
|
19707
|
-
|
|
21292
|
+
_context5.p = 0;
|
|
21293
|
+
_context5.n = 1;
|
|
19708
21294
|
return handler(data);
|
|
19709
21295
|
case 1:
|
|
19710
|
-
|
|
21296
|
+
_context5.n = 3;
|
|
19711
21297
|
break;
|
|
19712
21298
|
case 2:
|
|
19713
|
-
|
|
19714
|
-
|
|
19715
|
-
console.warn('Merchant onLoaded handler threw (swallowed):',
|
|
21299
|
+
_context5.p = 2;
|
|
21300
|
+
_t5 = _context5.v;
|
|
21301
|
+
console.warn('Merchant onLoaded handler threw (swallowed):', _t5);
|
|
19716
21302
|
case 3:
|
|
19717
|
-
return
|
|
21303
|
+
return _context5.a(2);
|
|
19718
21304
|
}
|
|
19719
|
-
},
|
|
21305
|
+
}, _callee5, null, [[0, 2]]);
|
|
19720
21306
|
}));
|
|
19721
21307
|
});
|
|
19722
21308
|
return;
|
|
@@ -20064,7 +21650,143 @@ exports.WALLET_TYPES = void 0;
|
|
|
20064
21650
|
WALLET_TYPES["GOOGLE_PAY"] = "google";
|
|
20065
21651
|
})(exports.WALLET_TYPES || (exports.WALLET_TYPES = {}));
|
|
20066
21652
|
|
|
20067
|
-
|
|
21653
|
+
/** CDN location of Apple's web SDK (provides the `<apple-pay-button>` web component). */
|
|
21654
|
+
var APPLE_PAY_SDK_URL = 'https://applepay.cdn-apple.com/jsapi/1.latest/apple-pay-sdk.js';
|
|
21655
|
+
/**
|
|
21656
|
+
* Statuses that count as available when the merchant does not override them.
|
|
21657
|
+
* Includes `paymentCredentialStatusUnknown` so the button is shown in third-party /
|
|
21658
|
+
* cross-device (QR) browsers, per Apple's guidance.
|
|
21659
|
+
*/
|
|
21660
|
+
var DEFAULT_APPLE_PAY_CAPABILITIES$1 = ['paymentCredentialsAvailable', 'paymentCredentialStatusUnknown'];
|
|
21661
|
+
/**
|
|
21662
|
+
* Maps the SDK's friendly capability tokens to Apple's `PaymentCredentialStatus` values.
|
|
21663
|
+
* Unknown tokens are passed through unchanged.
|
|
21664
|
+
*/
|
|
21665
|
+
function formatCapabilities(capabilities) {
|
|
21666
|
+
var fieldMap = {
|
|
21667
|
+
credentials_available: 'paymentCredentialsAvailable',
|
|
21668
|
+
credentials_status_unknown: 'paymentCredentialStatusUnknown',
|
|
21669
|
+
credentials_unavailable: 'paymentCredentialsUnavailable'
|
|
21670
|
+
};
|
|
21671
|
+
return capabilities.map(function (capability) {
|
|
21672
|
+
return fieldMap[capability] || capability;
|
|
21673
|
+
});
|
|
21674
|
+
}
|
|
21675
|
+
/**
|
|
21676
|
+
* Synchronous, network-free check for a **native, same-device** Apple Pay session
|
|
21677
|
+
* (`window.ApplePaySession` exists and `canMakePayments()` is `true`). Safe to call in
|
|
21678
|
+
* any environment (never throws).
|
|
21679
|
+
*
|
|
21680
|
+
* Note: this is a legacy/Safari-oriented signal. It is **not** authoritative for
|
|
21681
|
+
* cross-browser / cross-device (QR) availability — in Chrome/Edge/Firefox
|
|
21682
|
+
* `window.ApplePaySession` is supplied by the CDN SDK and may be absent until it loads,
|
|
21683
|
+
* and the cross-device flow is reflected by `applePayCapabilities()`, not this call.
|
|
21684
|
+
* Use {@link evaluateApplePayAvailability} for the authoritative, cross-browser answer.
|
|
21685
|
+
*/
|
|
21686
|
+
function hasNativeApplePaySession() {
|
|
21687
|
+
try {
|
|
21688
|
+
return typeof window !== 'undefined' && !!window.ApplePaySession && typeof ApplePaySession !== 'undefined' && ApplePaySession.canMakePayments();
|
|
21689
|
+
} catch (_a) {
|
|
21690
|
+
return false;
|
|
21691
|
+
}
|
|
21692
|
+
}
|
|
21693
|
+
var sdkLoadPromise = null;
|
|
21694
|
+
/**
|
|
21695
|
+
* Idempotently loads Apple's web SDK from the CDN.
|
|
21696
|
+
* Resolves immediately if the `<apple-pay-button>` custom element is already
|
|
21697
|
+
* registered, and de-duplicates concurrent loads. Rejects if the script fails to load.
|
|
21698
|
+
*/
|
|
21699
|
+
function loadApplePaySdk() {
|
|
21700
|
+
if (typeof customElements !== 'undefined' && customElements.get('apple-pay-button')) {
|
|
21701
|
+
return Promise.resolve();
|
|
21702
|
+
}
|
|
21703
|
+
if (sdkLoadPromise) {
|
|
21704
|
+
return sdkLoadPromise;
|
|
21705
|
+
}
|
|
21706
|
+
sdkLoadPromise = new Promise(function (resolve, reject) {
|
|
21707
|
+
var script = document.createElement('script');
|
|
21708
|
+
script.src = APPLE_PAY_SDK_URL;
|
|
21709
|
+
script.type = 'text/javascript';
|
|
21710
|
+
script.crossOrigin = 'anonymous';
|
|
21711
|
+
script.onload = function () {
|
|
21712
|
+
return resolve();
|
|
21713
|
+
};
|
|
21714
|
+
script.onerror = function () {
|
|
21715
|
+
// Allow a future retry after a transient failure.
|
|
21716
|
+
sdkLoadPromise = null;
|
|
21717
|
+
reject(new Error('Failed to load Apple Pay script'));
|
|
21718
|
+
};
|
|
21719
|
+
document.head.appendChild(script);
|
|
21720
|
+
});
|
|
21721
|
+
return sdkLoadPromise;
|
|
21722
|
+
}
|
|
21723
|
+
/**
|
|
21724
|
+
* Core Apple Pay availability evaluation, shared by the instance flow
|
|
21725
|
+
* (`ApplePayOpenWalletService`) and the static `ApplePayOpenWalletButton.checkAvailability`.
|
|
21726
|
+
*
|
|
21727
|
+
* Assumes the Apple Pay SDK is already loaded (call {@link loadApplePaySdk} first).
|
|
21728
|
+
*/
|
|
21729
|
+
function evaluateApplePayAvailability(params) {
|
|
21730
|
+
return __awaiter(this, void 0, void 0, /*#__PURE__*/_regenerator().m(function _callee() {
|
|
21731
|
+
var _a, hasNativeSession, allowed, capabilities, paymentCredentialStatus, available;
|
|
21732
|
+
return _regenerator().w(function (_context) {
|
|
21733
|
+
while (1) switch (_context.p = _context.n) {
|
|
21734
|
+
case 0:
|
|
21735
|
+
if (!(typeof window === 'undefined' || !window.ApplePaySession)) {
|
|
21736
|
+
_context.n = 1;
|
|
21737
|
+
break;
|
|
21738
|
+
}
|
|
21739
|
+
return _context.a(2, {
|
|
21740
|
+
available: false,
|
|
21741
|
+
supported: false,
|
|
21742
|
+
has_native_session: false,
|
|
21743
|
+
reason: 'unsupported_browser'
|
|
21744
|
+
});
|
|
21745
|
+
case 1:
|
|
21746
|
+
hasNativeSession = hasNativeApplePaySession();
|
|
21747
|
+
allowed = ((_a = params.apple_pay_capabilities) === null || _a === void 0 ? void 0 : _a.length) ? formatCapabilities(params.apple_pay_capabilities) : DEFAULT_APPLE_PAY_CAPABILITIES$1;
|
|
21748
|
+
_context.p = 2;
|
|
21749
|
+
_context.n = 3;
|
|
21750
|
+
return ApplePaySession.applePayCapabilities(params.merchantId);
|
|
21751
|
+
case 3:
|
|
21752
|
+
capabilities = _context.v;
|
|
21753
|
+
paymentCredentialStatus = capabilities === null || capabilities === void 0 ? void 0 : capabilities.paymentCredentialStatus; // `applePayCapabilities` is authoritative: trust an explicit `applePayUnsupported`
|
|
21754
|
+
// (e.g. an unregistered/misconfigured merchant) rather than overriding it with the
|
|
21755
|
+
// coarse `canMakePayments()` signal.
|
|
21756
|
+
if (!(paymentCredentialStatus === 'applePayUnsupported')) {
|
|
21757
|
+
_context.n = 4;
|
|
21758
|
+
break;
|
|
21759
|
+
}
|
|
21760
|
+
return _context.a(2, {
|
|
21761
|
+
available: false,
|
|
21762
|
+
supported: false,
|
|
21763
|
+
has_native_session: hasNativeSession,
|
|
21764
|
+
payment_credential_status: paymentCredentialStatus,
|
|
21765
|
+
reason: 'unsupported_browser'
|
|
21766
|
+
});
|
|
21767
|
+
case 4:
|
|
21768
|
+
available = allowed.includes(paymentCredentialStatus);
|
|
21769
|
+
return _context.a(2, {
|
|
21770
|
+
available: available,
|
|
21771
|
+
supported: true,
|
|
21772
|
+
has_native_session: hasNativeSession,
|
|
21773
|
+
payment_credential_status: paymentCredentialStatus,
|
|
21774
|
+
reason: available ? undefined : 'no_credentials'
|
|
21775
|
+
});
|
|
21776
|
+
case 5:
|
|
21777
|
+
_context.p = 5;
|
|
21778
|
+
_context.v;
|
|
21779
|
+
return _context.a(2, {
|
|
21780
|
+
available: false,
|
|
21781
|
+
supported: hasNativeSession,
|
|
21782
|
+
has_native_session: hasNativeSession,
|
|
21783
|
+
reason: 'query_failed'
|
|
21784
|
+
});
|
|
21785
|
+
}
|
|
21786
|
+
}, _callee, null, [[2, 5]]);
|
|
21787
|
+
}));
|
|
21788
|
+
}
|
|
21789
|
+
|
|
20068
21790
|
/**
|
|
20069
21791
|
* Apple Pay implementation of the OpenWallet service.
|
|
20070
21792
|
*
|
|
@@ -20340,7 +22062,6 @@ var ApplePayOpenWalletService = /*#__PURE__*/function (_OpenWalletService) {
|
|
|
20340
22062
|
value: function load() {
|
|
20341
22063
|
return __awaiter(this, void 0, void 0, /*#__PURE__*/_regenerator().m(function _callee3() {
|
|
20342
22064
|
var _this4 = this;
|
|
20343
|
-
var applePayScript;
|
|
20344
22065
|
return _regenerator().w(function (_context3) {
|
|
20345
22066
|
while (1) switch (_context3.n) {
|
|
20346
22067
|
case 0:
|
|
@@ -20351,26 +22072,12 @@ var ApplePayOpenWalletService = /*#__PURE__*/function (_OpenWalletService) {
|
|
|
20351
22072
|
this.handleOnUnavailable('Promises not supported');
|
|
20352
22073
|
return _context3.a(2);
|
|
20353
22074
|
case 1:
|
|
20354
|
-
|
|
20355
|
-
_context3.n = 2;
|
|
20356
|
-
break;
|
|
20357
|
-
}
|
|
20358
|
-
this.onScriptLoaded();
|
|
20359
|
-
return _context3.a(2);
|
|
20360
|
-
case 2:
|
|
20361
|
-
applePayScript = document.createElement('script');
|
|
20362
|
-
applePayScript.src = 'https://applepay.cdn-apple.com/jsapi/1.latest/apple-pay-sdk.js';
|
|
20363
|
-
applePayScript.type = 'text/javascript';
|
|
20364
|
-
applePayScript.crossOrigin = 'anonymous';
|
|
20365
|
-
applePayScript.onload = function () {
|
|
22075
|
+
loadApplePaySdk().then(function () {
|
|
20366
22076
|
_this4.onScriptLoaded();
|
|
20367
|
-
}
|
|
20368
|
-
applePayScript.onerror = function () {
|
|
22077
|
+
})["catch"](function () {
|
|
20369
22078
|
_this4.handleOnUnavailable('Failed to load Apple Pay script');
|
|
20370
|
-
|
|
20371
|
-
|
|
20372
|
-
document.head.appendChild(applePayScript);
|
|
20373
|
-
case 3:
|
|
22079
|
+
});
|
|
22080
|
+
case 2:
|
|
20374
22081
|
return _context3.a(2);
|
|
20375
22082
|
}
|
|
20376
22083
|
}, _callee3, this);
|
|
@@ -20398,26 +22105,22 @@ var ApplePayOpenWalletService = /*#__PURE__*/function (_OpenWalletService) {
|
|
|
20398
22105
|
}, {
|
|
20399
22106
|
key: "checkAvailability",
|
|
20400
22107
|
value: function checkAvailability() {
|
|
20401
|
-
|
|
20402
|
-
|
|
20403
|
-
|
|
20404
|
-
|
|
20405
|
-
|
|
20406
|
-
|
|
20407
|
-
|
|
20408
|
-
|
|
20409
|
-
|
|
20410
|
-
|
|
20411
|
-
|
|
20412
|
-
|
|
20413
|
-
|
|
22108
|
+
return __awaiter(this, void 0, void 0, /*#__PURE__*/_regenerator().m(function _callee4() {
|
|
22109
|
+
var result;
|
|
22110
|
+
return _regenerator().w(function (_context4) {
|
|
22111
|
+
while (1) switch (_context4.n) {
|
|
22112
|
+
case 0:
|
|
22113
|
+
_context4.n = 1;
|
|
22114
|
+
return evaluateApplePayAvailability({
|
|
22115
|
+
merchantId: this.getMerchantId(),
|
|
22116
|
+
apple_pay_capabilities: this.meta.apple_pay_capabilities
|
|
22117
|
+
});
|
|
22118
|
+
case 1:
|
|
22119
|
+
result = _context4.v;
|
|
22120
|
+
return _context4.a(2, result.available);
|
|
20414
22121
|
}
|
|
20415
|
-
|
|
20416
|
-
|
|
20417
|
-
})["catch"](function () {
|
|
20418
|
-
resolve(false);
|
|
20419
|
-
});
|
|
20420
|
-
});
|
|
22122
|
+
}, _callee4, this);
|
|
22123
|
+
}));
|
|
20421
22124
|
}
|
|
20422
22125
|
}, {
|
|
20423
22126
|
key: "mount",
|
|
@@ -20443,7 +22146,7 @@ var ApplePayOpenWalletService = /*#__PURE__*/function (_OpenWalletService) {
|
|
|
20443
22146
|
}, {
|
|
20444
22147
|
key: "onApplePayButtonClicked",
|
|
20445
22148
|
value: function onApplePayButtonClicked() {
|
|
20446
|
-
var
|
|
22149
|
+
var _this6 = this;
|
|
20447
22150
|
if (this.isButtonDisabled()) {
|
|
20448
22151
|
return;
|
|
20449
22152
|
}
|
|
@@ -20462,26 +22165,27 @@ var ApplePayOpenWalletService = /*#__PURE__*/function (_OpenWalletService) {
|
|
|
20462
22165
|
// As part of PAYCA-2065 we detect that the paymentSession.onvalidatemerchant is not
|
|
20463
22166
|
// executed if the browser is not safari. In that case, we handle it outside the
|
|
20464
22167
|
// actual merchant validation function.
|
|
20465
|
-
var
|
|
20466
|
-
var
|
|
22168
|
+
var userAgent = navigator.userAgent || '';
|
|
22169
|
+
var isIOS = userAgent.includes('iPhone') || userAgent.includes('iPad') || userAgent.includes('iPod');
|
|
22170
|
+
var isDesktopRequestIOS = navigator.maxTouchPoints > 1 && userAgent.includes('Macintosh');
|
|
20467
22171
|
if (!window.safari && !isIOS && !isDesktopRequestIOS) {
|
|
20468
22172
|
this.handleMerchantOnButtonClickEvent()["catch"](function (err) {
|
|
20469
22173
|
console.error('Error while processing merchant callback', err);
|
|
20470
|
-
|
|
22174
|
+
_this6.paymentSession.abort();
|
|
20471
22175
|
});
|
|
20472
22176
|
}
|
|
20473
22177
|
}
|
|
20474
22178
|
}, {
|
|
20475
22179
|
key: "getMerchantSession",
|
|
20476
22180
|
value: function getMerchantSession() {
|
|
20477
|
-
var
|
|
22181
|
+
var _this7 = this;
|
|
20478
22182
|
return new Promise(function (resolve, reject) {
|
|
20479
|
-
return
|
|
20480
|
-
service_id:
|
|
22183
|
+
return _this7.api.paymentSource().createSession(_extends({
|
|
22184
|
+
service_id: _this7.serviceId,
|
|
20481
22185
|
session_id: window.location.hostname,
|
|
20482
|
-
store_name:
|
|
20483
|
-
},
|
|
20484
|
-
request_shipping:
|
|
22186
|
+
store_name: _this7.meta.store_name
|
|
22187
|
+
}, _this7.isShippingRequired() && {
|
|
22188
|
+
request_shipping: _this7.meta.request_shipping
|
|
20485
22189
|
})).then(function (res) {
|
|
20486
22190
|
resolve(res);
|
|
20487
22191
|
})["catch"](function (err) {
|
|
@@ -20492,42 +22196,30 @@ var ApplePayOpenWalletService = /*#__PURE__*/function (_OpenWalletService) {
|
|
|
20492
22196
|
}, {
|
|
20493
22197
|
key: "handleMerchantOnButtonClickEvent",
|
|
20494
22198
|
value: function handleMerchantOnButtonClickEvent() {
|
|
20495
|
-
return __awaiter(this, void 0, void 0, /*#__PURE__*/_regenerator().m(function
|
|
22199
|
+
return __awaiter(this, void 0, void 0, /*#__PURE__*/_regenerator().m(function _callee5() {
|
|
20496
22200
|
var results;
|
|
20497
|
-
return _regenerator().w(function (
|
|
20498
|
-
while (1) switch (
|
|
22201
|
+
return _regenerator().w(function (_context5) {
|
|
22202
|
+
while (1) switch (_context5.n) {
|
|
20499
22203
|
case 0:
|
|
20500
|
-
|
|
22204
|
+
_context5.n = 1;
|
|
20501
22205
|
return this.eventEmitter.emitWithResult(EVENT$1.ON_CLICK, {
|
|
20502
22206
|
event: EVENT$1.ON_CLICK
|
|
20503
22207
|
});
|
|
20504
22208
|
case 1:
|
|
20505
|
-
results =
|
|
22209
|
+
results = _context5.v;
|
|
20506
22210
|
if (!results.some(function (r) {
|
|
20507
22211
|
return r === false;
|
|
20508
22212
|
})) {
|
|
20509
|
-
|
|
22213
|
+
_context5.n = 2;
|
|
20510
22214
|
break;
|
|
20511
22215
|
}
|
|
20512
22216
|
throw new Error('onClick handler aborted the payment flow');
|
|
20513
22217
|
case 2:
|
|
20514
|
-
return
|
|
22218
|
+
return _context5.a(2);
|
|
20515
22219
|
}
|
|
20516
|
-
},
|
|
22220
|
+
}, _callee5, this);
|
|
20517
22221
|
}));
|
|
20518
22222
|
}
|
|
20519
|
-
}, {
|
|
20520
|
-
key: "formatCapabilities",
|
|
20521
|
-
value: function formatCapabilities(capabilities) {
|
|
20522
|
-
var fieldMap = {
|
|
20523
|
-
credentials_available: 'paymentCredentialsAvailable',
|
|
20524
|
-
credentials_status_unknown: 'paymentCredentialStatusUnknown',
|
|
20525
|
-
credentials_unavailable: 'paymentCredentialsUnavailable'
|
|
20526
|
-
};
|
|
20527
|
-
return capabilities.map(function (capability) {
|
|
20528
|
-
return fieldMap[capability] || capability;
|
|
20529
|
-
});
|
|
20530
|
-
}
|
|
20531
22223
|
}, {
|
|
20532
22224
|
key: "formatErrorFields",
|
|
20533
22225
|
value: function formatErrorFields(error) {
|
|
@@ -20694,11 +22386,6 @@ var ApplePayOpenWalletService = /*#__PURE__*/function (_OpenWalletService) {
|
|
|
20694
22386
|
applePayLaterAvailability: this.meta.apple_pay_later_availability
|
|
20695
22387
|
});
|
|
20696
22388
|
}
|
|
20697
|
-
}, {
|
|
20698
|
-
key: "isUnsuportedBrowser",
|
|
20699
|
-
value: function isUnsuportedBrowser(capabilities) {
|
|
20700
|
-
return (capabilities === null || capabilities === void 0 ? void 0 : capabilities.paymentCredentialStatus) === 'applePayUnsupported';
|
|
20701
|
-
}
|
|
20702
22389
|
}]);
|
|
20703
22390
|
}(OpenWalletService);
|
|
20704
22391
|
|
|
@@ -20747,17 +22434,57 @@ var ApplePayOpenWalletButton = /*#__PURE__*/function (_OpenWalletButtons) {
|
|
|
20747
22434
|
return _this;
|
|
20748
22435
|
}
|
|
20749
22436
|
/**
|
|
20750
|
-
*
|
|
20751
|
-
*
|
|
22437
|
+
* Checks Apple Pay availability **without instantiating the button** — the single,
|
|
22438
|
+
* authoritative, cross-browser-aware availability entry point.
|
|
20752
22439
|
*
|
|
20753
|
-
*
|
|
20754
|
-
*
|
|
20755
|
-
*
|
|
22440
|
+
* Resolves the merchant identifier (either supplied directly or fetched from the
|
|
22441
|
+
* service configuration), loads Apple's web SDK if needed, then queries
|
|
22442
|
+
* `ApplePaySession.applePayCapabilities()`. Returns a convenience `available` verdict
|
|
22443
|
+
* (computed against `apple_pay_capabilities`), the raw device-reported
|
|
22444
|
+
* `payment_credential_status`, and `has_native_session` so callers can infer the transport:
|
|
22445
|
+
* `available && has_native_session` ⇒ native sheet likely; `available && !has_native_session`
|
|
22446
|
+
* ⇒ cross-device / QR handoff likely.
|
|
22447
|
+
*
|
|
22448
|
+
* Pass `{ sync: true }` for a cheap, network-free native same-device pre-check that
|
|
22449
|
+
* resolves immediately (see {@link ApplePayAvailabilityOptions.sync}).
|
|
22450
|
+
*
|
|
22451
|
+
* @param {ApplePayAvailabilityOptions} [options] - Merchant context, allowed capabilities, and mode.
|
|
22452
|
+
* @returns {Promise<ApplePayAvailabilityResult>} The availability result.
|
|
22453
|
+
*
|
|
22454
|
+
* @example
|
|
22455
|
+
* // Lightest path — no network, when you already know the merchant identifier:
|
|
22456
|
+
* const result = await ApplePayOpenWalletButton.checkAvailability({ merchant_id });
|
|
22457
|
+
*
|
|
22458
|
+
* @example
|
|
22459
|
+
* // Resolve the merchant identifier from the service configuration:
|
|
22460
|
+
* const result = await ApplePayOpenWalletButton.checkAvailability({
|
|
22461
|
+
* public_key_or_access_token,
|
|
22462
|
+
* service_id,
|
|
22463
|
+
* env: 'sandbox',
|
|
22464
|
+
* });
|
|
22465
|
+
* if (result.available) {
|
|
22466
|
+
* // Render the Apple Pay button (native sheet or cross-device QR, per has_native_session).
|
|
22467
|
+
* } else {
|
|
22468
|
+
* console.log('Unavailable:', result.reason, result.payment_credential_status);
|
|
22469
|
+
* }
|
|
22470
|
+
*
|
|
22471
|
+
* @example
|
|
22472
|
+
* // Synchronous native-only fast check (no network / SDK load):
|
|
22473
|
+
* const { has_native_session } = await ApplePayOpenWalletButton.checkAvailability({ sync: true });
|
|
20756
22474
|
*/
|
|
20757
22475
|
_inherits(ApplePayOpenWalletButton, _OpenWalletButtons);
|
|
20758
22476
|
return _createClass(ApplePayOpenWalletButton, [{
|
|
20759
22477
|
key: "validateWalletMeta",
|
|
20760
|
-
value:
|
|
22478
|
+
value:
|
|
22479
|
+
/**
|
|
22480
|
+
* Validates Apple Pay-specific required metadata fields.
|
|
22481
|
+
* Apple Pay requires `amount_label` and `store_name` to be present and strings.
|
|
22482
|
+
*
|
|
22483
|
+
* @private
|
|
22484
|
+
* @throws {Error} If `amount_label` is missing or not a string.
|
|
22485
|
+
* @throws {Error} If `store_name` is missing or not a string.
|
|
22486
|
+
*/
|
|
22487
|
+
function validateWalletMeta() {
|
|
20761
22488
|
if (typeof this.meta.amount_label !== 'string' || this.meta.amount_label.trim() === '') {
|
|
20762
22489
|
throw new Error("meta.amount_label is required for Apple Pay and must be a non-empty string (e.g. 'TOTAL').");
|
|
20763
22490
|
}
|
|
@@ -20791,6 +22518,114 @@ var ApplePayOpenWalletButton = /*#__PURE__*/function (_OpenWalletButtons) {
|
|
|
20791
22518
|
value: function createWalletService(serviceConfig) {
|
|
20792
22519
|
return new ApplePayOpenWalletService(this.api, this.eventEmitter, this.serviceId, this.container, this.meta, serviceConfig);
|
|
20793
22520
|
}
|
|
22521
|
+
}], [{
|
|
22522
|
+
key: "checkAvailability",
|
|
22523
|
+
value: function checkAvailability() {
|
|
22524
|
+
return __awaiter(this, arguments, void 0, function () {
|
|
22525
|
+
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
22526
|
+
return /*#__PURE__*/_regenerator().m(function _callee() {
|
|
22527
|
+
var hasNativeSession, merchantId, authType, api, serviceConfig;
|
|
22528
|
+
return _regenerator().w(function (_context) {
|
|
22529
|
+
while (1) switch (_context.p = _context.n) {
|
|
22530
|
+
case 0:
|
|
22531
|
+
hasNativeSession = hasNativeApplePaySession();
|
|
22532
|
+
if (!options.sync) {
|
|
22533
|
+
_context.n = 1;
|
|
22534
|
+
break;
|
|
22535
|
+
}
|
|
22536
|
+
return _context.a(2, {
|
|
22537
|
+
available: hasNativeSession,
|
|
22538
|
+
supported: hasNativeSession,
|
|
22539
|
+
has_native_session: hasNativeSession,
|
|
22540
|
+
reason: hasNativeSession ? undefined : 'no_native_session'
|
|
22541
|
+
});
|
|
22542
|
+
case 1:
|
|
22543
|
+
merchantId = options.merchant_id;
|
|
22544
|
+
if (merchantId) {
|
|
22545
|
+
_context.n = 6;
|
|
22546
|
+
break;
|
|
22547
|
+
}
|
|
22548
|
+
if (!(!options.public_key_or_access_token || !options.service_id)) {
|
|
22549
|
+
_context.n = 2;
|
|
22550
|
+
break;
|
|
22551
|
+
}
|
|
22552
|
+
return _context.a(2, {
|
|
22553
|
+
available: false,
|
|
22554
|
+
supported: hasNativeSession,
|
|
22555
|
+
has_native_session: hasNativeSession,
|
|
22556
|
+
reason: 'missing_merchant_id'
|
|
22557
|
+
});
|
|
22558
|
+
case 2:
|
|
22559
|
+
_context.p = 2;
|
|
22560
|
+
authType = AccessToken.validateJWT(options.public_key_or_access_token) ? API_AUTH_TYPE.TOKEN : API_AUTH_TYPE.PUBLIC_KEY;
|
|
22561
|
+
api = new ApiInternal(options.public_key_or_access_token, authType);
|
|
22562
|
+
if (options.env) {
|
|
22563
|
+
api.setEnv(options.env);
|
|
22564
|
+
}
|
|
22565
|
+
_context.n = 3;
|
|
22566
|
+
return getServiceConfigCached(api, options.service_id);
|
|
22567
|
+
case 3:
|
|
22568
|
+
serviceConfig = _context.v;
|
|
22569
|
+
if (!(serviceConfig.type !== SERVICE_TYPES.APPLE_PAY)) {
|
|
22570
|
+
_context.n = 4;
|
|
22571
|
+
break;
|
|
22572
|
+
}
|
|
22573
|
+
return _context.a(2, {
|
|
22574
|
+
available: false,
|
|
22575
|
+
supported: hasNativeSession,
|
|
22576
|
+
has_native_session: hasNativeSession,
|
|
22577
|
+
reason: 'service_type_mismatch'
|
|
22578
|
+
});
|
|
22579
|
+
case 4:
|
|
22580
|
+
merchantId = serviceConfig.merchant;
|
|
22581
|
+
_context.n = 6;
|
|
22582
|
+
break;
|
|
22583
|
+
case 5:
|
|
22584
|
+
_context.p = 5;
|
|
22585
|
+
_context.v;
|
|
22586
|
+
return _context.a(2, {
|
|
22587
|
+
available: false,
|
|
22588
|
+
supported: hasNativeSession,
|
|
22589
|
+
has_native_session: hasNativeSession,
|
|
22590
|
+
reason: 'config_fetch_failed'
|
|
22591
|
+
});
|
|
22592
|
+
case 6:
|
|
22593
|
+
if (merchantId) {
|
|
22594
|
+
_context.n = 7;
|
|
22595
|
+
break;
|
|
22596
|
+
}
|
|
22597
|
+
return _context.a(2, {
|
|
22598
|
+
available: false,
|
|
22599
|
+
supported: hasNativeSession,
|
|
22600
|
+
has_native_session: hasNativeSession,
|
|
22601
|
+
reason: 'missing_merchant_id'
|
|
22602
|
+
});
|
|
22603
|
+
case 7:
|
|
22604
|
+
_context.p = 7;
|
|
22605
|
+
_context.n = 8;
|
|
22606
|
+
return loadApplePaySdk();
|
|
22607
|
+
case 8:
|
|
22608
|
+
_context.n = 10;
|
|
22609
|
+
break;
|
|
22610
|
+
case 9:
|
|
22611
|
+
_context.p = 9;
|
|
22612
|
+
_context.v;
|
|
22613
|
+
return _context.a(2, {
|
|
22614
|
+
available: false,
|
|
22615
|
+
supported: hasNativeSession,
|
|
22616
|
+
has_native_session: hasNativeSession,
|
|
22617
|
+
reason: 'sdk_load_failed'
|
|
22618
|
+
});
|
|
22619
|
+
case 10:
|
|
22620
|
+
return _context.a(2, evaluateApplePayAvailability({
|
|
22621
|
+
merchantId: merchantId,
|
|
22622
|
+
apple_pay_capabilities: options.apple_pay_capabilities
|
|
22623
|
+
}));
|
|
22624
|
+
}
|
|
22625
|
+
}, _callee, null, [[7, 9], [2, 5]]);
|
|
22626
|
+
})();
|
|
22627
|
+
});
|
|
22628
|
+
}
|
|
20794
22629
|
}]);
|
|
20795
22630
|
}(OpenWalletButtons);
|
|
20796
22631
|
|
|
@@ -20827,6 +22662,137 @@ var GOOGLE_PAY_CARD_NETWORKS = ['AMEX', 'DISCOVER', 'INTERAC', 'JCB', 'MASTERCAR
|
|
|
20827
22662
|
SHIPPING_OPTION: EVENT$1.ON_SHIPPING_OPTIONS_CHANGE
|
|
20828
22663
|
});
|
|
20829
22664
|
|
|
22665
|
+
/**
|
|
22666
|
+
* Synchronous, network-free check for whether the Google Pay JS API is already present
|
|
22667
|
+
* in the page (`window.google.payments.api.PaymentsClient`). Safe to call in any
|
|
22668
|
+
* environment (never throws).
|
|
22669
|
+
*
|
|
22670
|
+
* Note: Google Pay has no synchronous device/card capability API — this only reports
|
|
22671
|
+
* whether the SDK script has loaded. The authoritative readiness signal comes from
|
|
22672
|
+
* {@link evaluateGooglePayAvailability} (which runs `isReadyToPay`).
|
|
22673
|
+
*/
|
|
22674
|
+
function isGooglePayApiPresent() {
|
|
22675
|
+
var _a, _b, _c;
|
|
22676
|
+
try {
|
|
22677
|
+
return typeof window !== 'undefined' && !!((_c = (_b = (_a = window.google) === null || _a === void 0 ? void 0 : _a.payments) === null || _b === void 0 ? void 0 : _b.api) === null || _c === void 0 ? void 0 : _c.PaymentsClient);
|
|
22678
|
+
} catch (_d) {
|
|
22679
|
+
return false;
|
|
22680
|
+
}
|
|
22681
|
+
}
|
|
22682
|
+
var scriptLoadPromise = null;
|
|
22683
|
+
/**
|
|
22684
|
+
* Idempotently loads the Google Pay JS SDK from the CDN.
|
|
22685
|
+
* Resolves immediately if the API is already present, de-duplicates concurrent loads,
|
|
22686
|
+
* and rejects if the script fails to load.
|
|
22687
|
+
*/
|
|
22688
|
+
function loadGooglePayScript() {
|
|
22689
|
+
if (isGooglePayApiPresent()) {
|
|
22690
|
+
return Promise.resolve();
|
|
22691
|
+
}
|
|
22692
|
+
if (scriptLoadPromise) {
|
|
22693
|
+
return scriptLoadPromise;
|
|
22694
|
+
}
|
|
22695
|
+
scriptLoadPromise = new Promise(function (resolve, reject) {
|
|
22696
|
+
var script = document.createElement('script');
|
|
22697
|
+
script.type = 'text/javascript';
|
|
22698
|
+
script.src = GOOGLE_PAY_SCRIPT_URL;
|
|
22699
|
+
script.async = true;
|
|
22700
|
+
script.onload = function () {
|
|
22701
|
+
if (!isGooglePayApiPresent()) {
|
|
22702
|
+
scriptLoadPromise = null;
|
|
22703
|
+
reject(new Error('Google Pay not available'));
|
|
22704
|
+
return;
|
|
22705
|
+
}
|
|
22706
|
+
resolve();
|
|
22707
|
+
};
|
|
22708
|
+
script.onerror = function () {
|
|
22709
|
+
// Allow a future retry after a transient failure.
|
|
22710
|
+
scriptLoadPromise = null;
|
|
22711
|
+
reject(new Error('Failed to load Google Pay script'));
|
|
22712
|
+
};
|
|
22713
|
+
document.head.appendChild(script);
|
|
22714
|
+
});
|
|
22715
|
+
return scriptLoadPromise;
|
|
22716
|
+
}
|
|
22717
|
+
/**
|
|
22718
|
+
* Builds the base `isReadyToPay` request using the default card configuration
|
|
22719
|
+
* (standard auth methods + card networks). Mirrors the instance service's request:
|
|
22720
|
+
* `existingPaymentMethodRequired` is `false` on Safari (where Google Pay surfaces a
|
|
22721
|
+
* different flow) and `true` elsewhere.
|
|
22722
|
+
*/
|
|
22723
|
+
function buildIsReadyToPayRequest() {
|
|
22724
|
+
var isSafariBrowser = Browser.getBrowserName() === 'Apple Safari';
|
|
22725
|
+
return _extends(_extends({}, GOOGLE_PAY_API_CONFIG), {
|
|
22726
|
+
allowedPaymentMethods: [{
|
|
22727
|
+
type: 'CARD',
|
|
22728
|
+
parameters: {
|
|
22729
|
+
allowedAuthMethods: GOOGLE_PAY_AUTH_METHODS,
|
|
22730
|
+
allowedCardNetworks: GOOGLE_PAY_CARD_NETWORKS
|
|
22731
|
+
}
|
|
22732
|
+
}],
|
|
22733
|
+
existingPaymentMethodRequired: !isSafariBrowser
|
|
22734
|
+
});
|
|
22735
|
+
}
|
|
22736
|
+
/**
|
|
22737
|
+
* Core Google Pay availability evaluation, shared by the instance flow
|
|
22738
|
+
* (`GooglePayOpenWalletService`) and the static `GooglePayOpenWalletButton.checkAvailability`.
|
|
22739
|
+
*
|
|
22740
|
+
* The static check passes no `request`, so it uses the **default** card configuration
|
|
22741
|
+
* (a button instance with a custom `card_config` may produce a different result). The
|
|
22742
|
+
* instance flow passes its own `paymentsClient` and `request` so the readiness check
|
|
22743
|
+
* honours the merchant's `card_config` and runs against the same client it pays with.
|
|
22744
|
+
*
|
|
22745
|
+
* Assumes the Google Pay SDK is already loaded (call {@link loadGooglePayScript} first).
|
|
22746
|
+
*/
|
|
22747
|
+
function evaluateGooglePayAvailability() {
|
|
22748
|
+
return __awaiter(this, arguments, void 0, function () {
|
|
22749
|
+
var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
22750
|
+
return /*#__PURE__*/_regenerator().m(function _callee() {
|
|
22751
|
+
var _a, _b, paymentsClient, response, available;
|
|
22752
|
+
return _regenerator().w(function (_context) {
|
|
22753
|
+
while (1) switch (_context.p = _context.n) {
|
|
22754
|
+
case 0:
|
|
22755
|
+
if (isGooglePayApiPresent()) {
|
|
22756
|
+
_context.n = 1;
|
|
22757
|
+
break;
|
|
22758
|
+
}
|
|
22759
|
+
return _context.a(2, {
|
|
22760
|
+
available: false,
|
|
22761
|
+
supported: false,
|
|
22762
|
+
reason: 'unsupported_browser'
|
|
22763
|
+
});
|
|
22764
|
+
case 1:
|
|
22765
|
+
_context.p = 1;
|
|
22766
|
+
paymentsClient = (_a = params.paymentsClient) !== null && _a !== void 0 ? _a : new google.payments.api.PaymentsClient({
|
|
22767
|
+
merchantInfo: {
|
|
22768
|
+
merchantId: params.merchantId || ''
|
|
22769
|
+
}
|
|
22770
|
+
});
|
|
22771
|
+
_context.n = 2;
|
|
22772
|
+
return paymentsClient.isReadyToPay((_b = params.request) !== null && _b !== void 0 ? _b : buildIsReadyToPayRequest());
|
|
22773
|
+
case 2:
|
|
22774
|
+
response = _context.v;
|
|
22775
|
+
available = !!response.result;
|
|
22776
|
+
return _context.a(2, {
|
|
22777
|
+
available: available,
|
|
22778
|
+
supported: true,
|
|
22779
|
+
payment_method_present: response.paymentMethodPresent,
|
|
22780
|
+
reason: available ? undefined : 'no_payment_method'
|
|
22781
|
+
});
|
|
22782
|
+
case 3:
|
|
22783
|
+
_context.p = 3;
|
|
22784
|
+
_context.v;
|
|
22785
|
+
return _context.a(2, {
|
|
22786
|
+
available: false,
|
|
22787
|
+
supported: isGooglePayApiPresent(),
|
|
22788
|
+
reason: 'query_failed'
|
|
22789
|
+
});
|
|
22790
|
+
}
|
|
22791
|
+
}, _callee, null, [[1, 3]]);
|
|
22792
|
+
})();
|
|
22793
|
+
});
|
|
22794
|
+
}
|
|
22795
|
+
|
|
20830
22796
|
/**
|
|
20831
22797
|
* Parses intermediate payment data from Google Pay into a normalized format
|
|
20832
22798
|
* for use in shipping address/option change events.
|
|
@@ -21162,14 +23128,18 @@ var GooglePayOpenWalletService = /*#__PURE__*/function (_OpenWalletService) {
|
|
|
21162
23128
|
key: "checkAvailability",
|
|
21163
23129
|
value: function checkAvailability() {
|
|
21164
23130
|
return __awaiter(this, void 0, void 0, /*#__PURE__*/_regenerator().m(function _callee2() {
|
|
23131
|
+
var result;
|
|
21165
23132
|
return _regenerator().w(function (_context2) {
|
|
21166
23133
|
while (1) switch (_context2.n) {
|
|
21167
23134
|
case 0:
|
|
21168
|
-
|
|
21169
|
-
|
|
21170
|
-
|
|
21171
|
-
|
|
21172
|
-
})
|
|
23135
|
+
_context2.n = 1;
|
|
23136
|
+
return evaluateGooglePayAvailability({
|
|
23137
|
+
paymentsClient: this.paymentsClient,
|
|
23138
|
+
request: this.createRequest()
|
|
23139
|
+
});
|
|
23140
|
+
case 1:
|
|
23141
|
+
result = _context2.v;
|
|
23142
|
+
return _context2.a(2, result.available);
|
|
21173
23143
|
}
|
|
21174
23144
|
}, _callee2, this);
|
|
21175
23145
|
}));
|
|
@@ -21627,14 +23597,46 @@ var GooglePayOpenWalletButton = /*#__PURE__*/function (_OpenWalletButtons) {
|
|
|
21627
23597
|
return _this;
|
|
21628
23598
|
}
|
|
21629
23599
|
/**
|
|
21630
|
-
*
|
|
21631
|
-
*
|
|
21632
|
-
*
|
|
23600
|
+
* Checks Google Pay availability **without instantiating the button** — the static
|
|
23601
|
+
* availability entry point.
|
|
23602
|
+
*
|
|
23603
|
+
* Loads the Google Pay JS SDK if needed, then runs `PaymentsClient.isReadyToPay()` using
|
|
23604
|
+
* the **default** card configuration (a button instance with a custom `card_config` may
|
|
23605
|
+
* produce a different result). All options are optional — unlike Apple Pay, Google Pay's
|
|
23606
|
+
* readiness check does not require a merchant identifier; supplying `service_id` +
|
|
23607
|
+
* `public_key_or_access_token` additionally validates that the service is a Google Pay service.
|
|
23608
|
+
*
|
|
23609
|
+
* Pass `{ sync: true }` for a cheap, network-free check that only reports whether the
|
|
23610
|
+
* Google Pay JS API is already present (see {@link GooglePayAvailabilityOptions.sync}).
|
|
23611
|
+
*
|
|
23612
|
+
* @param {GooglePayAvailabilityOptions} [options] - Optional merchant context and mode.
|
|
23613
|
+
* @returns {Promise<GooglePayAvailabilityResult>} The availability result.
|
|
23614
|
+
*
|
|
23615
|
+
* @example
|
|
23616
|
+
* // Simplest form — no merchant context needed:
|
|
23617
|
+
* const result = await GooglePayOpenWalletButton.checkAvailability();
|
|
23618
|
+
* if (result.available) {
|
|
23619
|
+
* // Render the Google Pay button.
|
|
23620
|
+
* }
|
|
23621
|
+
*
|
|
23622
|
+
* @example
|
|
23623
|
+
* // Validate the service is a Google Pay service while checking:
|
|
23624
|
+
* const result = await GooglePayOpenWalletButton.checkAvailability({
|
|
23625
|
+
* public_key_or_access_token,
|
|
23626
|
+
* service_id,
|
|
23627
|
+
* env: 'sandbox',
|
|
23628
|
+
* });
|
|
21633
23629
|
*/
|
|
21634
23630
|
_inherits(GooglePayOpenWalletButton, _OpenWalletButtons);
|
|
21635
23631
|
return _createClass(GooglePayOpenWalletButton, [{
|
|
21636
23632
|
key: "validateWalletMeta",
|
|
21637
|
-
value:
|
|
23633
|
+
value:
|
|
23634
|
+
/**
|
|
23635
|
+
* Validates Google Pay-specific required metadata fields.
|
|
23636
|
+
* Google Pay has no additional required fields beyond the base (amount, currency, country).
|
|
23637
|
+
* @private
|
|
23638
|
+
*/
|
|
23639
|
+
function validateWalletMeta() {
|
|
21638
23640
|
// Google Pay has no additional required meta fields beyond the base.
|
|
21639
23641
|
// Optional fields (merchant_name, card_config, etc.) are validated
|
|
21640
23642
|
// at runtime by the Google Pay SDK.
|
|
@@ -21665,6 +23667,87 @@ var GooglePayOpenWalletButton = /*#__PURE__*/function (_OpenWalletButtons) {
|
|
|
21665
23667
|
value: function createWalletService(serviceConfig) {
|
|
21666
23668
|
return new GooglePayOpenWalletService(this.api, this.eventEmitter, this.serviceId, this.container, this.meta, serviceConfig);
|
|
21667
23669
|
}
|
|
23670
|
+
}], [{
|
|
23671
|
+
key: "checkAvailability",
|
|
23672
|
+
value: function checkAvailability() {
|
|
23673
|
+
return __awaiter(this, arguments, void 0, function () {
|
|
23674
|
+
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
23675
|
+
return /*#__PURE__*/_regenerator().m(function _callee() {
|
|
23676
|
+
var present, merchantId, authType, api, serviceConfig;
|
|
23677
|
+
return _regenerator().w(function (_context) {
|
|
23678
|
+
while (1) switch (_context.p = _context.n) {
|
|
23679
|
+
case 0:
|
|
23680
|
+
if (!options.sync) {
|
|
23681
|
+
_context.n = 1;
|
|
23682
|
+
break;
|
|
23683
|
+
}
|
|
23684
|
+
present = isGooglePayApiPresent();
|
|
23685
|
+
return _context.a(2, {
|
|
23686
|
+
available: present,
|
|
23687
|
+
supported: present,
|
|
23688
|
+
reason: present ? undefined : 'api_not_loaded'
|
|
23689
|
+
});
|
|
23690
|
+
case 1:
|
|
23691
|
+
merchantId = options.merchant_id;
|
|
23692
|
+
if (!(!merchantId && options.public_key_or_access_token && options.service_id)) {
|
|
23693
|
+
_context.n = 6;
|
|
23694
|
+
break;
|
|
23695
|
+
}
|
|
23696
|
+
_context.p = 2;
|
|
23697
|
+
authType = AccessToken.validateJWT(options.public_key_or_access_token) ? API_AUTH_TYPE.TOKEN : API_AUTH_TYPE.PUBLIC_KEY;
|
|
23698
|
+
api = new ApiInternal(options.public_key_or_access_token, authType);
|
|
23699
|
+
if (options.env) {
|
|
23700
|
+
api.setEnv(options.env);
|
|
23701
|
+
}
|
|
23702
|
+
_context.n = 3;
|
|
23703
|
+
return getServiceConfigCached(api, options.service_id);
|
|
23704
|
+
case 3:
|
|
23705
|
+
serviceConfig = _context.v;
|
|
23706
|
+
if (!(serviceConfig.type !== SERVICE_TYPES.GOOGLE_PAY)) {
|
|
23707
|
+
_context.n = 4;
|
|
23708
|
+
break;
|
|
23709
|
+
}
|
|
23710
|
+
return _context.a(2, {
|
|
23711
|
+
available: false,
|
|
23712
|
+
supported: isGooglePayApiPresent(),
|
|
23713
|
+
reason: 'service_type_mismatch'
|
|
23714
|
+
});
|
|
23715
|
+
case 4:
|
|
23716
|
+
merchantId = serviceConfig.merchant;
|
|
23717
|
+
_context.n = 6;
|
|
23718
|
+
break;
|
|
23719
|
+
case 5:
|
|
23720
|
+
_context.p = 5;
|
|
23721
|
+
_context.v;
|
|
23722
|
+
return _context.a(2, {
|
|
23723
|
+
available: false,
|
|
23724
|
+
supported: isGooglePayApiPresent(),
|
|
23725
|
+
reason: 'config_fetch_failed'
|
|
23726
|
+
});
|
|
23727
|
+
case 6:
|
|
23728
|
+
_context.p = 6;
|
|
23729
|
+
_context.n = 7;
|
|
23730
|
+
return loadGooglePayScript();
|
|
23731
|
+
case 7:
|
|
23732
|
+
_context.n = 9;
|
|
23733
|
+
break;
|
|
23734
|
+
case 8:
|
|
23735
|
+
_context.p = 8;
|
|
23736
|
+
_context.v;
|
|
23737
|
+
return _context.a(2, {
|
|
23738
|
+
available: false,
|
|
23739
|
+
supported: isGooglePayApiPresent(),
|
|
23740
|
+
reason: 'sdk_load_failed'
|
|
23741
|
+
});
|
|
23742
|
+
case 9:
|
|
23743
|
+
return _context.a(2, evaluateGooglePayAvailability({
|
|
23744
|
+
merchantId: merchantId
|
|
23745
|
+
}));
|
|
23746
|
+
}
|
|
23747
|
+
}, _callee, null, [[6, 8], [2, 5]]);
|
|
23748
|
+
})();
|
|
23749
|
+
});
|
|
23750
|
+
}
|
|
21668
23751
|
}]);
|
|
21669
23752
|
}(OpenWalletButtons);
|
|
21670
23753
|
|
|
@@ -44735,6 +46818,7 @@ exports.Api = Api;
|
|
|
44735
46818
|
exports.ApplePayOpenWalletButton = ApplePayOpenWalletButton;
|
|
44736
46819
|
exports.ApplePayWalletButtonExpress = ApplePayWalletButtonExpress;
|
|
44737
46820
|
exports.CANVAS_3DS_EVENT = CANVAS_3DS_EVENT;
|
|
46821
|
+
exports.CARD_SCHEME = CARD_SCHEME;
|
|
44738
46822
|
exports.CHECKOUT_BUTTON_EVENT = CHECKOUT_BUTTON_EVENT;
|
|
44739
46823
|
exports.CHECKOUT_EVENT = EVENT$2;
|
|
44740
46824
|
exports.Canvas3ds = Canvas3ds;
|