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