oauth4webapi 1.2.1 → 1.2.2
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 +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.js +46 -40
- package/package.json +8 -10
package/README.md
CHANGED
|
@@ -67,7 +67,7 @@ The supported JavaScript runtimes include ones that
|
|
|
67
67
|
- These are (not an exhaustive list):
|
|
68
68
|
- Browsers
|
|
69
69
|
- Cloudflare Workers
|
|
70
|
-
- Deno
|
|
70
|
+
- Deno
|
|
71
71
|
- Electron
|
|
72
72
|
- Next.js Middlewares
|
|
73
73
|
- Node.js ([runtime flags may be needed](https://github.com/panva/oauth4webapi/issues/8))
|
package/build/index.d.ts
CHANGED
|
@@ -537,7 +537,7 @@ export interface DPoPOptions extends CryptoKeyPair {
|
|
|
537
537
|
* Its algorithm must be compatible with a supported {@link JWSAlgorithm JWS `alg` Algorithm}.
|
|
538
538
|
*/
|
|
539
539
|
privateKey: CryptoKey;
|
|
540
|
-
/** The public key corresponding to {@link DPoPOptions.privateKey} */
|
|
540
|
+
/** The public key corresponding to {@link DPoPOptions.privateKey}. */
|
|
541
541
|
publicKey: CryptoKey;
|
|
542
542
|
/**
|
|
543
543
|
* Server-Provided Nonce to use in the request. This option serves as an override in case the
|
package/build/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
let USER_AGENT;
|
|
2
2
|
if (typeof navigator === 'undefined' || !navigator.userAgent?.startsWith?.('Mozilla/5.0 ')) {
|
|
3
3
|
const NAME = 'oauth4webapi';
|
|
4
|
-
const VERSION = 'v1.2.
|
|
4
|
+
const VERSION = 'v1.2.2';
|
|
5
5
|
USER_AGENT = `${NAME}/${VERSION}`;
|
|
6
6
|
}
|
|
7
7
|
const encoder = new TextEncoder();
|
|
@@ -151,11 +151,17 @@ function prepareHeaders(input) {
|
|
|
151
151
|
return headers;
|
|
152
152
|
}
|
|
153
153
|
function signal(value) {
|
|
154
|
-
|
|
154
|
+
if (typeof value === 'function') {
|
|
155
|
+
value = value();
|
|
156
|
+
}
|
|
157
|
+
if (!(value instanceof AbortSignal)) {
|
|
158
|
+
throw new TypeError('"options.signal" must return or be an instance of AbortSignal');
|
|
159
|
+
}
|
|
160
|
+
return value;
|
|
155
161
|
}
|
|
156
162
|
export async function discoveryRequest(issuerIdentifier, options) {
|
|
157
163
|
if (!(issuerIdentifier instanceof URL)) {
|
|
158
|
-
throw new TypeError('"
|
|
164
|
+
throw new TypeError('"issuerIdentifier" must be an instance of URL');
|
|
159
165
|
}
|
|
160
166
|
if (issuerIdentifier.protocol !== 'https:' && issuerIdentifier.protocol !== 'http:') {
|
|
161
167
|
throw new TypeError('"issuer.protocol" must be "https:" or "http:"');
|
|
@@ -315,20 +321,20 @@ async function privateKeyJwt(as, client, key, kid) {
|
|
|
315
321
|
kid,
|
|
316
322
|
}, clientAssertion(as, client), key);
|
|
317
323
|
}
|
|
318
|
-
function
|
|
319
|
-
if (typeof
|
|
320
|
-
throw new TypeError('"
|
|
324
|
+
function assertAs(as) {
|
|
325
|
+
if (typeof as !== 'object' || as === null) {
|
|
326
|
+
throw new TypeError('"as" must be an object');
|
|
321
327
|
}
|
|
322
|
-
if (!validateString(
|
|
323
|
-
throw new TypeError('"
|
|
328
|
+
if (!validateString(as.issuer)) {
|
|
329
|
+
throw new TypeError('"as.issuer" property must be a non-empty string');
|
|
324
330
|
}
|
|
325
331
|
return true;
|
|
326
332
|
}
|
|
327
|
-
function assertClient(
|
|
328
|
-
if (typeof
|
|
333
|
+
function assertClient(client) {
|
|
334
|
+
if (typeof client !== 'object' || client === null) {
|
|
329
335
|
throw new TypeError('"client" must be an object');
|
|
330
336
|
}
|
|
331
|
-
if (!validateString(
|
|
337
|
+
if (!validateString(client.client_id)) {
|
|
332
338
|
throw new TypeError('"client.client_id" property must be a non-empty string');
|
|
333
339
|
}
|
|
334
340
|
return true;
|
|
@@ -399,7 +405,7 @@ async function jwt(header, claimsSet, key) {
|
|
|
399
405
|
return `${input}.${signature}`;
|
|
400
406
|
}
|
|
401
407
|
export async function issueRequestObject(as, client, parameters, privateKey) {
|
|
402
|
-
|
|
408
|
+
assertAs(as);
|
|
403
409
|
assertClient(client);
|
|
404
410
|
if (!(parameters instanceof URLSearchParams)) {
|
|
405
411
|
throw new TypeError('"parameters" must be an instance of URLSearchParams');
|
|
@@ -468,13 +474,13 @@ async function publicJwk(key) {
|
|
|
468
474
|
return { kty, crv, e, n, x, y };
|
|
469
475
|
}
|
|
470
476
|
export async function pushedAuthorizationRequest(as, client, parameters, options) {
|
|
471
|
-
|
|
477
|
+
assertAs(as);
|
|
472
478
|
assertClient(client);
|
|
473
479
|
if (!(parameters instanceof URLSearchParams)) {
|
|
474
480
|
throw new TypeError('"parameters" must be an instance of URLSearchParams');
|
|
475
481
|
}
|
|
476
482
|
if (typeof as.pushed_authorization_request_endpoint !== 'string') {
|
|
477
|
-
throw new TypeError('"
|
|
483
|
+
throw new TypeError('"as.pushed_authorization_request_endpoint" must be a string');
|
|
478
484
|
}
|
|
479
485
|
const url = new URL(as.pushed_authorization_request_endpoint);
|
|
480
486
|
const body = new URLSearchParams(parameters);
|
|
@@ -555,7 +561,7 @@ export function parseWwwAuthenticateChallenges(response) {
|
|
|
555
561
|
return challenges;
|
|
556
562
|
}
|
|
557
563
|
export async function processPushedAuthorizationResponse(as, client, response) {
|
|
558
|
-
|
|
564
|
+
assertAs(as);
|
|
559
565
|
assertClient(client);
|
|
560
566
|
if (!(response instanceof Response)) {
|
|
561
567
|
throw new TypeError('"response" must be an instance of Response');
|
|
@@ -609,10 +615,10 @@ export async function protectedResourceRequest(accessToken, method, url, headers
|
|
|
609
615
|
}).then(processDpopNonce);
|
|
610
616
|
}
|
|
611
617
|
export async function userInfoRequest(as, client, accessToken, options) {
|
|
612
|
-
|
|
618
|
+
assertAs(as);
|
|
613
619
|
assertClient(client);
|
|
614
620
|
if (typeof as.userinfo_endpoint !== 'string') {
|
|
615
|
-
throw new TypeError('"
|
|
621
|
+
throw new TypeError('"as.userinfo_endpoint" must be a string');
|
|
616
622
|
}
|
|
617
623
|
const url = new URL(as.userinfo_endpoint);
|
|
618
624
|
const headers = prepareHeaders(options?.headers);
|
|
@@ -716,7 +722,7 @@ function getContentType(response) {
|
|
|
716
722
|
return response.headers.get('content-type')?.split(';')[0];
|
|
717
723
|
}
|
|
718
724
|
export async function processUserInfoResponse(as, client, expectedSubject, response, options) {
|
|
719
|
-
|
|
725
|
+
assertAs(as);
|
|
720
726
|
assertClient(client);
|
|
721
727
|
if (!(response instanceof Response)) {
|
|
722
728
|
throw new TypeError('"response" must be an instance of Response');
|
|
@@ -727,7 +733,7 @@ export async function processUserInfoResponse(as, client, expectedSubject, respo
|
|
|
727
733
|
let json;
|
|
728
734
|
if (getContentType(response) === 'application/jwt') {
|
|
729
735
|
if (typeof as.jwks_uri !== 'string') {
|
|
730
|
-
throw new TypeError('"
|
|
736
|
+
throw new TypeError('"as.jwks_uri" must be a string');
|
|
731
737
|
}
|
|
732
738
|
const { claims } = await validateJwt(await preserveBodyStream(response).text(), checkSigningAlgorithm.bind(undefined, client.userinfo_signed_response_alg, as.userinfo_signing_alg_values_supported), getPublicSigKeyFromIssuerJwksUri.bind(undefined, as, options))
|
|
733
739
|
.then(validateOptionalAudience.bind(undefined, client.client_id))
|
|
@@ -813,7 +819,7 @@ async function authenticatedRequest(as, client, method, url, body, headers, opti
|
|
|
813
819
|
}
|
|
814
820
|
async function tokenEndpointRequest(as, client, grantType, parameters, options) {
|
|
815
821
|
if (typeof as.token_endpoint !== 'string') {
|
|
816
|
-
throw new TypeError('"
|
|
822
|
+
throw new TypeError('"as.token_endpoint" must be a string');
|
|
817
823
|
}
|
|
818
824
|
const url = new URL(as.token_endpoint);
|
|
819
825
|
parameters.set('grant_type', grantType);
|
|
@@ -825,7 +831,7 @@ async function tokenEndpointRequest(as, client, grantType, parameters, options)
|
|
|
825
831
|
return authenticatedRequest(as, client, 'POST', url, parameters, headers, options);
|
|
826
832
|
}
|
|
827
833
|
export async function refreshTokenGrantRequest(as, client, refreshToken, options) {
|
|
828
|
-
|
|
834
|
+
assertAs(as);
|
|
829
835
|
assertClient(client);
|
|
830
836
|
if (!validateString(refreshToken)) {
|
|
831
837
|
throw new TypeError('"refreshToken" must be a non-empty string');
|
|
@@ -842,7 +848,7 @@ export function getValidatedIdTokenClaims(ref) {
|
|
|
842
848
|
return idTokenClaims.get(ref);
|
|
843
849
|
}
|
|
844
850
|
async function processGenericAccessTokenResponse(as, client, response, options, ignoreIdToken = false, ignoreRefreshToken = false) {
|
|
845
|
-
|
|
851
|
+
assertAs(as);
|
|
846
852
|
assertClient(client);
|
|
847
853
|
if (!(response instanceof Response)) {
|
|
848
854
|
throw new TypeError('"response" must be an instance of Response');
|
|
@@ -892,7 +898,7 @@ async function processGenericAccessTokenResponse(as, client, response, options,
|
|
|
892
898
|
}
|
|
893
899
|
if (json.id_token) {
|
|
894
900
|
if (typeof as.jwks_uri !== 'string') {
|
|
895
|
-
throw new TypeError('"
|
|
901
|
+
throw new TypeError('"as.jwks_uri" must be a string');
|
|
896
902
|
}
|
|
897
903
|
const { header, claims } = await validateJwt(json.id_token, checkSigningAlgorithm.bind(undefined, client.id_token_signed_response_alg, as.id_token_signing_alg_values_supported), getPublicSigKeyFromIssuerJwksUri.bind(undefined, as, options))
|
|
898
904
|
.then(validatePresence.bind(undefined, ['aud', 'exp', 'iat', 'iss', 'sub']))
|
|
@@ -948,7 +954,7 @@ function validateIssuer(expected, result) {
|
|
|
948
954
|
return result;
|
|
949
955
|
}
|
|
950
956
|
export async function authorizationCodeGrantRequest(as, client, callbackParameters, redirectUri, codeVerifier, options) {
|
|
951
|
-
|
|
957
|
+
assertAs(as);
|
|
952
958
|
assertClient(client);
|
|
953
959
|
if (!(callbackParameters instanceof CallbackParameters)) {
|
|
954
960
|
throw new TypeError('"callbackParameters" must be an instance of CallbackParameters obtained from "validateAuthResponse()", or "validateJwtAuthResponse()');
|
|
@@ -1050,7 +1056,7 @@ function checkJwtType(expected, result) {
|
|
|
1050
1056
|
return result;
|
|
1051
1057
|
}
|
|
1052
1058
|
export async function clientCredentialsGrantRequest(as, client, parameters, options) {
|
|
1053
|
-
|
|
1059
|
+
assertAs(as);
|
|
1054
1060
|
assertClient(client);
|
|
1055
1061
|
return tokenEndpointRequest(as, client, 'client_credentials', new URLSearchParams(parameters), options);
|
|
1056
1062
|
}
|
|
@@ -1062,13 +1068,13 @@ export async function processClientCredentialsResponse(as, client, response) {
|
|
|
1062
1068
|
return result;
|
|
1063
1069
|
}
|
|
1064
1070
|
export async function revocationRequest(as, client, token, options) {
|
|
1065
|
-
|
|
1071
|
+
assertAs(as);
|
|
1066
1072
|
assertClient(client);
|
|
1067
1073
|
if (!validateString(token)) {
|
|
1068
1074
|
throw new TypeError('"token" must be a non-empty string');
|
|
1069
1075
|
}
|
|
1070
1076
|
if (typeof as.revocation_endpoint !== 'string') {
|
|
1071
|
-
throw new TypeError('"
|
|
1077
|
+
throw new TypeError('"as.revocation_endpoint" must be a string');
|
|
1072
1078
|
}
|
|
1073
1079
|
const url = new URL(as.revocation_endpoint);
|
|
1074
1080
|
const body = new URLSearchParams(options?.additionalParameters);
|
|
@@ -1096,13 +1102,13 @@ function assertReadableResponse(response) {
|
|
|
1096
1102
|
}
|
|
1097
1103
|
}
|
|
1098
1104
|
export async function introspectionRequest(as, client, token, options) {
|
|
1099
|
-
|
|
1105
|
+
assertAs(as);
|
|
1100
1106
|
assertClient(client);
|
|
1101
1107
|
if (!validateString(token)) {
|
|
1102
1108
|
throw new TypeError('"token" must be a non-empty string');
|
|
1103
1109
|
}
|
|
1104
1110
|
if (typeof as.introspection_endpoint !== 'string') {
|
|
1105
|
-
throw new TypeError('"
|
|
1111
|
+
throw new TypeError('"as.introspection_endpoint" must be a string');
|
|
1106
1112
|
}
|
|
1107
1113
|
const url = new URL(as.introspection_endpoint);
|
|
1108
1114
|
const body = new URLSearchParams(options?.additionalParameters);
|
|
@@ -1117,7 +1123,7 @@ export async function introspectionRequest(as, client, token, options) {
|
|
|
1117
1123
|
return authenticatedRequest(as, client, 'POST', url, body, headers, options);
|
|
1118
1124
|
}
|
|
1119
1125
|
export async function processIntrospectionResponse(as, client, response, options) {
|
|
1120
|
-
|
|
1126
|
+
assertAs(as);
|
|
1121
1127
|
assertClient(client);
|
|
1122
1128
|
if (!(response instanceof Response)) {
|
|
1123
1129
|
throw new TypeError('"response" must be an instance of Response');
|
|
@@ -1132,7 +1138,7 @@ export async function processIntrospectionResponse(as, client, response, options
|
|
|
1132
1138
|
let json;
|
|
1133
1139
|
if (getContentType(response) === 'application/token-introspection+jwt') {
|
|
1134
1140
|
if (typeof as.jwks_uri !== 'string') {
|
|
1135
|
-
throw new TypeError('"
|
|
1141
|
+
throw new TypeError('"as.jwks_uri" must be a string');
|
|
1136
1142
|
}
|
|
1137
1143
|
const { claims } = await validateJwt(await preserveBodyStream(response).text(), checkSigningAlgorithm.bind(undefined, client.introspection_signed_response_alg, as.introspection_signing_alg_values_supported), getPublicSigKeyFromIssuerJwksUri.bind(undefined, as, options))
|
|
1138
1144
|
.then(checkJwtType.bind(undefined, 'token-introspection+jwt'))
|
|
@@ -1161,9 +1167,9 @@ export async function processIntrospectionResponse(as, client, response, options
|
|
|
1161
1167
|
return json;
|
|
1162
1168
|
}
|
|
1163
1169
|
export async function jwksRequest(as, options) {
|
|
1164
|
-
|
|
1170
|
+
assertAs(as);
|
|
1165
1171
|
if (typeof as.jwks_uri !== 'string') {
|
|
1166
|
-
throw new TypeError('"
|
|
1172
|
+
throw new TypeError('"as.jwks_uri" must be a string');
|
|
1167
1173
|
}
|
|
1168
1174
|
const url = new URL(as.jwks_uri);
|
|
1169
1175
|
const headers = prepareHeaders(options?.headers);
|
|
@@ -1328,7 +1334,7 @@ async function validateJwt(jws, checkAlg, getKey) {
|
|
|
1328
1334
|
return { header, claims };
|
|
1329
1335
|
}
|
|
1330
1336
|
export async function validateJwtAuthResponse(as, client, parameters, expectedState, options) {
|
|
1331
|
-
|
|
1337
|
+
assertAs(as);
|
|
1332
1338
|
assertClient(client);
|
|
1333
1339
|
if (parameters instanceof URL) {
|
|
1334
1340
|
parameters = parameters.searchParams;
|
|
@@ -1341,7 +1347,7 @@ export async function validateJwtAuthResponse(as, client, parameters, expectedSt
|
|
|
1341
1347
|
throw new OPE('"parameters" does not contain a JARM response');
|
|
1342
1348
|
}
|
|
1343
1349
|
if (typeof as.jwks_uri !== 'string') {
|
|
1344
|
-
throw new TypeError('"
|
|
1350
|
+
throw new TypeError('"as.jwks_uri" must be a string');
|
|
1345
1351
|
}
|
|
1346
1352
|
const { claims } = await validateJwt(response, checkSigningAlgorithm.bind(undefined, client.authorization_signed_response_alg, as.authorization_signing_alg_values_supported), getPublicSigKeyFromIssuerJwksUri.bind(undefined, as, options))
|
|
1347
1353
|
.then(validatePresence.bind(undefined, ['aud', 'exp', 'iss']))
|
|
@@ -1384,7 +1390,7 @@ export const expectNoState = Symbol();
|
|
|
1384
1390
|
class CallbackParameters extends URLSearchParams {
|
|
1385
1391
|
}
|
|
1386
1392
|
export function validateAuthResponse(as, client, parameters, expectedState) {
|
|
1387
|
-
|
|
1393
|
+
assertAs(as);
|
|
1388
1394
|
assertClient(client);
|
|
1389
1395
|
if (parameters instanceof URL) {
|
|
1390
1396
|
parameters = parameters.searchParams;
|
|
@@ -1460,13 +1466,13 @@ async function importJwk(jwk) {
|
|
|
1460
1466
|
return crypto.subtle.importKey('jwk', key, algorithm, true, ['verify']);
|
|
1461
1467
|
}
|
|
1462
1468
|
export async function deviceAuthorizationRequest(as, client, parameters, options) {
|
|
1463
|
-
|
|
1469
|
+
assertAs(as);
|
|
1464
1470
|
assertClient(client);
|
|
1465
1471
|
if (!(parameters instanceof URLSearchParams)) {
|
|
1466
1472
|
throw new TypeError('"parameters" must be an instance of URLSearchParams');
|
|
1467
1473
|
}
|
|
1468
1474
|
if (typeof as.device_authorization_endpoint !== 'string') {
|
|
1469
|
-
throw new TypeError('"
|
|
1475
|
+
throw new TypeError('"as.device_authorization_endpoint" must be a string');
|
|
1470
1476
|
}
|
|
1471
1477
|
const url = new URL(as.device_authorization_endpoint);
|
|
1472
1478
|
const body = new URLSearchParams(parameters);
|
|
@@ -1476,7 +1482,7 @@ export async function deviceAuthorizationRequest(as, client, parameters, options
|
|
|
1476
1482
|
return authenticatedRequest(as, client, 'POST', url, body, headers, options);
|
|
1477
1483
|
}
|
|
1478
1484
|
export async function processDeviceAuthorizationResponse(as, client, response) {
|
|
1479
|
-
|
|
1485
|
+
assertAs(as);
|
|
1480
1486
|
assertClient(client);
|
|
1481
1487
|
if (!(response instanceof Response)) {
|
|
1482
1488
|
throw new TypeError('"response" must be an instance of Response');
|
|
@@ -1520,7 +1526,7 @@ export async function processDeviceAuthorizationResponse(as, client, response) {
|
|
|
1520
1526
|
return json;
|
|
1521
1527
|
}
|
|
1522
1528
|
export async function deviceCodeGrantRequest(as, client, deviceCode, options) {
|
|
1523
|
-
|
|
1529
|
+
assertAs(as);
|
|
1524
1530
|
assertClient(client);
|
|
1525
1531
|
if (!validateString(deviceCode)) {
|
|
1526
1532
|
throw new TypeError('"deviceCode" must be a non-empty string');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oauth4webapi",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "OAuth 2 / OpenID Connect for Web Platform API JavaScript runtimes",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"auth",
|
|
@@ -43,35 +43,33 @@
|
|
|
43
43
|
"scripts": {
|
|
44
44
|
"_format": "find src test tap examples conformance -type f -name '*.ts' -o -name '*.mjs' | xargs prettier",
|
|
45
45
|
"build": "rm -rf build && tsc && tsc --declaration true --emitDeclarationOnly true --removeComments false && tsc -p test && tsc -p examples && tsc -p conformance && tsc -p tap",
|
|
46
|
-
"conformance": "patch-package &&
|
|
47
|
-
"coverage": "patch-package && c8 ava",
|
|
46
|
+
"conformance": "bash -c 'patch-package && source .node_flags.sh && ava --config conformance/ava.config.ts'",
|
|
48
47
|
"docs": "patch-package && typedoc",
|
|
49
48
|
"format": "npm run _format -- --write",
|
|
50
49
|
"format-check": "npm run _format -- --check",
|
|
51
50
|
"prepack": "npm run format && npm run docs && ./examples/.update-diffs.sh && git diff --quiet && npm run test && npm run build",
|
|
52
51
|
"tap:deno": "./tap/.deno.sh",
|
|
53
52
|
"tap:edge-runtime": "./tap/.edge-runtime.sh",
|
|
54
|
-
"tap:node": "./tap/.node.sh",
|
|
53
|
+
"tap:node": "bash -c './tap/.node.sh'",
|
|
55
54
|
"tap:workers": "./tap/.workers.sh",
|
|
56
55
|
"tap:browsers": "./tap/.browsers.sh",
|
|
57
|
-
"test": "ava"
|
|
56
|
+
"test": "bash -c 'source .node_flags.sh && ava'"
|
|
58
57
|
},
|
|
59
58
|
"devDependencies": {
|
|
60
59
|
"@esbuild-kit/esm-loader": "^2.5.0",
|
|
61
|
-
"@types/node": "^18.
|
|
60
|
+
"@types/node": "^18.11.2",
|
|
62
61
|
"@types/qunit": "^2.19.3",
|
|
63
62
|
"ava": "^4.3.3",
|
|
64
|
-
"
|
|
65
|
-
"edge-runtime": "^1.1.0-beta.31",
|
|
63
|
+
"edge-runtime": "^1.1.0-beta.40",
|
|
66
64
|
"jose": "^4.10.0",
|
|
67
65
|
"patch-package": "^6.4.7",
|
|
68
66
|
"prettier": "^2.7.1",
|
|
69
67
|
"prettier-plugin-jsdoc": "^0.4.2",
|
|
70
|
-
"qunit": "^2.19.
|
|
68
|
+
"qunit": "^2.19.2",
|
|
71
69
|
"testcafe": "^2.0.1",
|
|
72
70
|
"testcafe-browser-provider-browserstack": "^1.14.0",
|
|
73
71
|
"timekeeper": "^2.2.0",
|
|
74
|
-
"typedoc": "^0.23.
|
|
72
|
+
"typedoc": "^0.23.17",
|
|
75
73
|
"typedoc-plugin-markdown": "^3.13.6",
|
|
76
74
|
"typescript": "^4.8.4",
|
|
77
75
|
"undici": "^5.11.0",
|