From: Justin Wind Date: Sun, 4 Jun 2023 19:06:11 +0000 (-0700) Subject: switch from axios to got package for client requests X-Git-Tag: v1.3.0~4 X-Git-Url: https://git.squeep.com/?a=commitdiff_plain;h=cc52f66ba8522b6bc7002dfba79c1162a51aef0e;p=squeep-indieauth-helper switch from axios to got package for client requests --- diff --git a/lib/common.js b/lib/common.js index 3bf7e55..c1b8703 100644 --- a/lib/common.js +++ b/lib/common.js @@ -17,20 +17,28 @@ const fileScope = (filename) => { /** - * Pick out useful axios response fields. - * @param {AxiosResponse} res + * Pick out useful got response fields. + * @param {GotResponse} res * @returns {Object} */ -const axiosResponseLogData = (res) => { +const gotResponseLogData = (res) => { const data = pick(res, [ - 'status', - 'statusText', + 'statusCode', + 'statusMessage', 'headers', - 'elapsedTimeMs', - 'data', + 'body', ]); - if (data.data) { - data.data = logTruncate(data.data, 100); + if (typeof res.body === 'string') { + data.body = logTruncate(data.body, 100); + } else if (res.body instanceof Buffer) { + data.body = ``; + } + data.elapsedTimeMs = res?.timings?.phases?.total; + if (res?.redirectUrls?.length) { + data.redirectUrls = res.redirectUrls; + } + if (res?.retryCount) { + data.retryCount = res.retryCount; } return data; }; @@ -98,23 +106,11 @@ const properURLComponentName = (component) => { } -/** - * Encodes single-level object as form data string. - * @param {Object} data - */ -const formData = (data) => { - const formData = new URLSearchParams(); - Object.entries(data).forEach(([name, value]) => formData.set(name, value)); - return formData.toString(); -}; - - module.exports = { fileScope, - axiosResponseLogData, + gotResponseLogData, logTruncate, pick, setSymmetricDifference, properURLComponentName, - formData, }; \ No newline at end of file diff --git a/lib/communication.js b/lib/communication.js index ce2bc4e..18a6e58 100644 --- a/lib/communication.js +++ b/lib/communication.js @@ -1,11 +1,9 @@ 'use strict'; -const axios = require('axios'); const { mf2 } = require('microformats-parser'); const { parse: parseLinkHeader } = require('@squeep/web-linking'); const { Iconv } = require('iconv'); const { version: packageVersion, name: packageName } = require('../package.json'); -const { performance } = require('perf_hooks'); const { randomBytes, createHash } = require('crypto'); const { promisify } = require('util'); const randomBytesAsync = promisify(randomBytes); @@ -26,6 +24,7 @@ class Communication { /** * @param {Console} logger * @param {Object} options + * @param {Number=} options.timeout * @param {Object=} options.userAgent * @param {String=} options.userAgent.product * @param {String=} options.userAgent.version @@ -34,20 +33,52 @@ class Communication { constructor(logger, options = {}) { this.logger = logger; this.options = options; - this.axios = axios.create({ - headers: { - [Enum.Header.UserAgent]: Communication._userAgentString(options.userAgent), - [Enum.Header.Accept]: 'text/html, text/*;q=0.9, application/xhtml+xml;q=0.8, application/xml;q=0.8, */*;q=0.1', - }, - }); - this.axios.interceptors.request.use((request) => { - request.startTimestampMs = performance.now(); - return request; - }); - this.axios.interceptors.response.use((response) => { - response.elapsedTimeMs = performance.now() - response.config.startTimestampMs; - return response; - }); + + this._defaultAccept = options?.defaultAccept || 'text/html, text/*;q=0.9, application/xhtml+xml;q=0.8, application/xml;q=0.8, */*;q=0.1'; + this._jsonAccept = options?.jsonAccept || [Enum.ContentType.ApplicationJson, Enum.ContentType.Any + ';q=0.1'].join(', '); + + this.Got = undefined; + this.got = this._init; // Do the dynamic import on first attempt to use client. + } + + + /** + * Do a little dance to support this ESM client. + */ + async _init(...args) { + if (!this.Got) { + // For some reason eslint is confused about import being supported here. + // eslint-disable-next-line + this.Got = await import('got'); + this.got = this.Got.got.extend({ + headers: { + [Enum.Header.UserAgent]: Communication._userAgentString(this.options.userAgent), + [Enum.Header.Accept]: this._defaultAccept, + }, + timeout: { + request: this.options.timeout || 120000, + }, + hooks: { + beforeRetry: [ + this._onRetry, + ], + }, + }); + } + if (args.length) { + return this.got(...args); + } + } + + + /** + * Take notes on transient retries. + * @param {*} error + * @param {*} retryCount + */ + _onRetry(error, retryCount) { + const _scope = _fileScope('_onRetry'); + this.logger.debug(_scope, 'retry', { retryCount, error }); } @@ -135,46 +166,6 @@ class Communication { } - /** - * Valid response statuses. - * Allow 401 as a workaround for one specific client which return such on - * its client identifier endpoint when not yet authenticated. - * @param {Number} status - * @returns {Boolean} - */ - static _validateStatus(status) { - return (status >= 200 && status < 300) || status == 401; - } - - - /** - * A request config skeleton. - * @param {String} method - * @param {URL} urlObj - * @param {String=} body - * @param {Object=} params - * @param {Object=} headers - * @returns {Object} - */ - static _axiosConfig(method, urlObj, body, params = {}, headers = {}) { - const config = { - method, - url: `${urlObj.origin}${urlObj.pathname}`, - params: urlObj.searchParams, - headers, - ...(body && { data: body }), - // Setting this does not appear to be enough to keep axios from parsing JSON response into object - responseType: 'text', - // So force the matter by eliding all response transformations - transformResponse: [ (res) => res ], - - validateStatus: Communication._validateStatus, - }; - Object.entries(params).map(([k, v]) => config.params.set(k, v)); - return config; - } - - /** * Isolate the base of a url. * mf2 parser needs this so that relative links can be made absolute. @@ -208,7 +199,7 @@ class Communication { mediaType: mediaType.toLowerCase() || defaultContentType, params: params.reduce((obj, param) => { const [field, value] = param.split('='); - const isQuoted = value && value.charAt(0) === '"' && value.charAt(value.length - 1) === '"'; + const isQuoted = value?.startsWith('"') && value?.endsWith('"'); obj[field.toLowerCase()] = isQuoted ? value.slice(1, value.length - 1) : value; return obj; }, {}), @@ -284,27 +275,34 @@ class Communication { }; let response; try { - const fetchMicroformatConfig = Communication._axiosConfig('GET', urlObj); - response = await this.axios(fetchMicroformatConfig); + const fetchMicroformatConfig = { + method: 'GET', + url: urlObj, + responseType: 'buffer', + }; + response = await this.got(fetchMicroformatConfig); } catch (e) { this.logger.error(_scope, 'microformat request failed', { error: e, ...logInfoData }); return; } - logInfoData.response = common.axiosResponseLogData(response); + logInfoData.response = common.gotResponseLogData(response); // Normalize to utf8. - let body = response.data; + let body; const contentType = Communication._parseContentType(response.headers[Enum.Header.ContentType.toLowerCase()]); + // If a charset was specified, and it's not utf8ish, attempt to transliterate it to utf8. const nonUTF8Charset = !/utf-*8/i.test(contentType.params.charset) && contentType.params.charset; if (nonUTF8Charset) { try { const iconv = new Iconv(nonUTF8Charset, 'utf-8//translit//ignore'); - body = iconv.convert(body).toString('utf8'); + body = iconv.convert(response.body).toString('utf8'); } catch (e) { // istanbul ignore next this.logger.error(_scope, 'iconv conversion error', { error: e, ...logInfoData }); // Try to carry on, maybe the encoding will work anyhow... } + } else { + body = response.body.toString('utf8'); } let microformat = {}; @@ -340,24 +338,22 @@ class Communication { }; let response; try { - const fetchJSONConfig = Communication._axiosConfig('GET', urlObj, undefined, undefined, { - [Enum.Header.Accept]: [Enum.ContentType.ApplicationJson, Enum.ContentType.Any + ';q=0.1'].join(', '), - }); - response = await this.axios(fetchJSONConfig); + const fetchJSONConfig = { + method: 'GET', + url: urlObj, + headers: { + [Enum.Header.Accept]: this._jsonAccept, + }, + responseType: 'json', + }; + response = await this.got(fetchJSONConfig); } catch (e) { this.logger.error(_scope, 'json request failed', { error: e, ...logInfoData }); return; } - logInfoData.response = common.axiosResponseLogData(response); + logInfoData.response = common.gotResponseLogData(response); - let data; - try { - data = JSON.parse(response.data); - } catch (e) { - this.logger.error(_scope, 'json parsing failed', { error: e, ...logInfoData }); - } - - return data; + return response.body; } @@ -760,27 +756,25 @@ class Communication { async redeemCode(urlObj, code, codeVerifier, clientId, redirectURI) { const _scope = _fileScope('redeemCode'); - const formData = common.formData({ - 'grant_type': 'authorization_code', - code, - 'client_id': clientId, - 'redirect_uri': redirectURI, - 'code_verifier': codeVerifier, - }); - - const postRedeemCodeConfig = Communication._axiosConfig('POST', urlObj, formData, {}, { - [Enum.Header.ContentType]: Enum.ContentType.ApplicationForm, - [Enum.Header.Accept]: `${Enum.ContentType.ApplicationJson}, ${Enum.ContentType.Any};q=0.1`, - }); + const postRedeemCodeConfig = { + url: urlObj, + method: 'POST', + headers: { + [Enum.Header.Accept]: this._jsonAccept, + }, + form: { + 'grant_type': 'authorization_code', + code, + 'client_id': clientId, + 'redirect_uri': redirectURI, + 'code_verifier': codeVerifier, + }, + responseType: 'json', + }; try { - const response = await this.axios(postRedeemCodeConfig); - try { - return JSON.parse(response.data); - } catch (e) { - this.logger.error(_scope, 'failed to parse json', { error: e, response }); - throw e; - } + const response = await this.got(postRedeemCodeConfig); + return response.body; } catch (e) { this.logger.error(_scope, 'redeem code request failed', { error: e, url: urlObj.href }); return; @@ -789,7 +783,7 @@ class Communication { /** - * Deprecated method name. + * Deprecated method name alias. * @see redeemCode * @param {URL} urlObj * @param {String} code @@ -812,21 +806,24 @@ class Communication { async introspectToken(introspectionUrlObj, authorizationHeader, token) { const _scope = _fileScope('introspectToken'); - const formData = common.formData({ token }); - const postIntrospectConfig = Communication._axiosConfig('POST', introspectionUrlObj, formData, {}, { - [Enum.Header.Authorization]: authorizationHeader, - [Enum.Header.ContentType]: Enum.ContentType.ApplicationForm, - [Enum.Header.Accept]: `${Enum.ContentType.ApplicationJson}, ${Enum.ContentType.Any};q=0.1`, - }); - delete postIntrospectConfig.validateStatus; // only accept success + const postIntrospectConfig = { + url: introspectionUrlObj, + method: 'POST', + headers: { + [Enum.Header.Authorization]: authorizationHeader, + [Enum.Header.Accept]: this._jsonAccept, + }, + form: { + token, + }, + responseType: 'json', + }; - let tokenInfo; try { - const response = await this.axios(postIntrospectConfig); + const response = await this.got(postIntrospectConfig); this.logger.debug(_scope, 'response', { response }); // check status try { - tokenInfo = JSON.parse(response.data); const { active, me, @@ -834,7 +831,11 @@ class Communication { scope, exp, iat, - } = tokenInfo; + } = response.body; + + if (![true, false].includes(active)) { + throw new RangeError('missing required response field "active"'); + } return { active, @@ -845,7 +846,7 @@ class Communication { ...(iat && { iat: Number(iat) }), }; } catch (e) { - this.logger.error(_scope, 'failed to parse json', { error: e, response }); + this.logger.error(_scope, 'failed to parse json', { error: e, response: common.gotResponseLogData(response) }); throw e; } } catch (e) { @@ -868,15 +869,16 @@ class Communication { const _scope = _fileScope('deliverTicket'); try { - const ticketPayload = { - ticket, - resource: resourceUrlObj.href, - subject: subjectUrlObj.href, + const ticketConfig = { + method: 'POST', + url: ticketEndpointUrlObj, + form: { + ticket, + resource: resourceUrlObj.href, + subject: subjectUrlObj.href, + }, }; - const ticketConfig = Communication._axiosConfig('POST', ticketEndpointUrlObj, ticketPayload, {}, { - [Enum.Header.ContentType]: Enum.ContentType.ApplicationForm, - }); - return await this.axios(ticketConfig); + return await this.got(ticketConfig); } catch (e) { this.logger.error(_scope, 'ticket delivery request failed', { error: e, url: ticketEndpointUrlObj.href }); throw e; diff --git a/package-lock.json b/package-lock.json index 8391384..9597664 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "ISC", "dependencies": { "@squeep/web-linking": "^1.0.7", - "axios": "^1.4.0", + "got": "^13.0.0", "iconv": "^3.0.1", "ip-address": "^8.1.0", "microformats-parser": "^1.4.1" @@ -705,6 +705,17 @@ "node": ">= 8" } }, + "node_modules/@sindresorhus/is": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.4.0.tgz", + "integrity": "sha512-Ggh6E9AnMpiNXlbXfFUcWE9qm408rL8jDi7+PMBBx7TMbwEmiqAiSmZ+zydYwxcJLqPGNDoLc9mXDuMDBZg0sA==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, "node_modules/@sinonjs/commons": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", @@ -757,6 +768,22 @@ "node": ">=14.0" } }, + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" + }, "node_modules/acorn": { "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", @@ -877,21 +904,6 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", - "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", - "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -973,6 +985,31 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "10.2.10", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.10.tgz", + "integrity": "sha512-v6WB+Epm/qO4Hdlio/sfUn69r5Shgh39SsE9DSd4bIezP0mblOlObI+I0kUEM7J0JFc+I7pSeMeYaOYtX1N/VQ==", + "dependencies": { + "@types/http-cache-semantics": "^4.0.1", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.2", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + } + }, "node_modules/caching-transform": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", @@ -1119,17 +1156,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -1209,6 +1235,31 @@ "node": ">=0.10.0" } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -1230,12 +1281,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", "engines": { - "node": ">=0.4.0" + "node": ">=10" } }, "node_modules/diff": { @@ -1662,25 +1713,6 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, - "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/foreground-child": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", @@ -1694,17 +1726,12 @@ "node": ">=8.0.0" } }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, + "node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", "engines": { - "node": ">= 6" + "node": ">= 14.17" } }, "node_modules/fromentries": { @@ -1780,6 +1807,17 @@ "node": ">=8.0.0" } }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -1827,6 +1865,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/got": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz", + "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==", + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -1900,6 +1962,23 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + }, + "node_modules/http2-wrapper": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.0.tgz", + "integrity": "sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, "node_modules/iconv": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/iconv/-/iconv-3.0.1.tgz", @@ -2241,6 +2320,11 @@ "node": ">=4" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2271,6 +2355,14 @@ "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", "dev": true }, + "node_modules/keyv": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", + "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2333,6 +2425,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -2368,23 +2471,15 @@ "node": ">=10" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "engines": { - "node": ">= 0.6" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/minimatch": { @@ -2554,6 +2649,17 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", + "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", @@ -2757,6 +2863,14 @@ "node": ">= 0.4.0" } }, + "node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "engines": { + "node": ">=12.20" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -3063,11 +3177,6 @@ "node": ">=8" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "node_modules/pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -3103,6 +3212,17 @@ } ] }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -3216,6 +3336,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -3225,6 +3350,20 @@ "node": ">=4" } }, + "node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -4333,6 +4472,11 @@ "fastq": "^1.6.0" } }, + "@sindresorhus/is": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.4.0.tgz", + "integrity": "sha512-Ggh6E9AnMpiNXlbXfFUcWE9qm408rL8jDi7+PMBBx7TMbwEmiqAiSmZ+zydYwxcJLqPGNDoLc9mXDuMDBZg0sA==" + }, "@sinonjs/commons": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", @@ -4384,6 +4528,19 @@ "resolved": "https://registry.npmjs.org/@squeep/web-linking/-/web-linking-1.0.7.tgz", "integrity": "sha512-9d3QijrWc/WNE7p/K7NLUHbmPf92CURRqfzDLV0cGqYNA4QWAPfzwC8hxWpdUkUnep3KakvLKK60l0kEBMM3ag==" }, + "@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "requires": { + "defer-to-connect": "^2.0.1" + } + }, + "@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" + }, "acorn": { "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", @@ -4471,21 +4628,6 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "axios": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", - "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", - "requires": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -4541,6 +4683,25 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==" + }, + "cacheable-request": { + "version": "10.2.10", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.10.tgz", + "integrity": "sha512-v6WB+Epm/qO4Hdlio/sfUn69r5Shgh39SsE9DSd4bIezP0mblOlObI+I0kUEM7J0JFc+I7pSeMeYaOYtX1N/VQ==", + "requires": { + "@types/http-cache-semantics": "^4.0.1", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.2", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + } + }, "caching-transform": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", @@ -4640,14 +4801,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -4710,6 +4863,21 @@ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + } + } + }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -4725,10 +4893,10 @@ "strip-bom": "^4.0.0" } }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" }, "diff": { "version": "5.0.0", @@ -5037,11 +5205,6 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, - "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" - }, "foreground-child": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", @@ -5052,15 +5215,10 @@ "signal-exit": "^3.0.2" } }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } + "form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==" }, "fromentries": { "version": "1.3.2", @@ -5105,6 +5263,11 @@ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" + }, "glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -5137,6 +5300,24 @@ "type-fest": "^0.20.2" } }, + "got": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz", + "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==", + "requires": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + } + }, "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -5194,6 +5375,20 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + }, + "http2-wrapper": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.0.tgz", + "integrity": "sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==", + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + } + }, "iconv": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/iconv/-/iconv-3.0.1.tgz", @@ -5444,6 +5639,11 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -5468,6 +5668,14 @@ "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", "dev": true }, + "keyv": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", + "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", + "requires": { + "json-buffer": "3.0.1" + } + }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -5515,6 +5723,11 @@ "is-unicode-supported": "^0.1.0" } }, + "lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==" + }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -5541,18 +5754,10 @@ "parse5": "^6.0.0" } }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } + "mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==" }, "minimatch": { "version": "3.1.2", @@ -5690,6 +5895,11 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "normalize-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", + "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==" + }, "nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", @@ -5856,6 +6066,11 @@ "integrity": "sha512-jd0cvB8qQ5uVt0lvCIexBaROw1KyKm5sbulg2fWOHjETisuCzWyt+eTZKEMs8v6HwzoGs8xik26jg7eCM6pS+A==", "dev": true }, + "p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==" + }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -6093,11 +6308,6 @@ "fromentries": "^1.2.0" } }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -6116,6 +6326,11 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -6207,12 +6422,25 @@ "supports-preserve-symlinks-flag": "^1.0.0" } }, + "resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, + "responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "requires": { + "lowercase-keys": "^3.0.0" + } + }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", diff --git a/package.json b/package.json index 2a41497..e0df23c 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "license": "ISC", "dependencies": { "@squeep/web-linking": "^1.0.7", - "axios": "^1.4.0", + "got": "^13.0.0", "iconv": "^3.0.1", "ip-address": "^8.1.0", "microformats-parser": "^1.4.1" diff --git a/test/lib/common.js b/test/lib/common.js index 20911c1..ff250a4 100644 --- a/test/lib/common.js +++ b/test/lib/common.js @@ -46,47 +46,121 @@ describe('common', function () { }); }); // logTruncate - describe('axiosResponseLogData', function () { + describe('gotResponseLogData', function () { it('covers', function () { const response = { - status: 200, - statusText: 'OK', + statusCode: 200, + statusMessage: 'OK', headers: { 'Content-Type': 'text/plain', }, otherData: 'blah', - data: 'Old Mother West Wind had stopped to talk with the Slender Fir Tree. "I\'ve just come across the Green Meadows," said Old Mother West Wind, “and there I saw the Best Thing in the World.”', + timings: { + phases: { + total: 89, + }, + }, + retryCount: 0, + redirectUrls: [], + body: 'Old Mother West Wind had stopped to talk with the Slender Fir Tree. "I\'ve just come across the Green Meadows," said Old Mother West Wind, “and there I saw the Best Thing in the World.”', + }; + const expected = { + statusCode: 200, + statusMessage: 'OK', + elapsedTimeMs: 89, + headers: { + 'Content-Type': 'text/plain', + }, + body: 'Old Mother West Wind had stopped to talk with the Slender Fir Tree. "I\'ve just come across the Green... (184 bytes)', + }; + const result = common.gotResponseLogData(response); + assert.deepStrictEqual(result, expected); + }); + it('covers no body', function () { + const response = { + statusCode: 200, + statusMessage: 'OK', + headers: { + 'Content-Type': 'text/plain', + }, + timings: { + phases: { + total: 89, + }, + }, + retryCount: 1, }; const expected = { - status: 200, - statusText: 'OK', + statusCode: 200, + statusMessage: 'OK', headers: { 'Content-Type': 'text/plain', }, - data: 'Old Mother West Wind had stopped to talk with the Slender Fir Tree. "I\'ve just come across the Green... (184 bytes)', + elapsedTimeMs: 89, + retryCount: 1, + }; + const result = common.gotResponseLogData(response); + assert.deepStrictEqual(result, expected); + }); + it('covers json', function () { + const response = { + statusCode: 200, + statusMessage: 'OK', + headers: { + 'Content-Type': 'application/json', + }, + timings: { + phases: { + total: 89, + }, + }, + body: { + foo: 'bar', + }, + redirectUrls: ['https://redirect.example.com/'], + }; + const expected = { + statusCode: 200, + statusMessage: 'OK', + headers: { + 'Content-Type': 'application/json', + }, + elapsedTimeMs: 89, + body: { + foo: 'bar', + }, + redirectUrls: ['https://redirect.example.com/'], }; - const result = common.axiosResponseLogData(response); + const result = common.gotResponseLogData(response); assert.deepStrictEqual(result, expected); }); - it('covers no data', function () { + it('covers buffer', function () { const response = { - status: 200, - statusText: 'OK', + statusCode: 200, + statusMessage: 'OK', headers: { 'Content-Type': 'text/plain', }, + timings: { + phases: { + total: 89, + }, + }, + body: Buffer.from('ᘛ⁐̤ᕐᐷ'), }; const expected = { - status: 200, - statusText: 'OK', + statusCode: 200, + statusMessage: 'OK', headers: { 'Content-Type': 'text/plain', }, + elapsedTimeMs: 89, + body: '', }; - const result = common.axiosResponseLogData(response); + const result = common.gotResponseLogData(response); assert.deepStrictEqual(result, expected); }); - }); // axiosResponseLogData + }); // gotResponseLogData describe('setSymmetricDifference', function () { it('covers difference', function () { @@ -120,14 +194,4 @@ describe('common', function () { }); }); // properURLComponentName - describe('formData', function () { - it('covers', function () { - const result = common.formData({ - key: 'value', - foo: 'bar', - }); - assert.strictEqual(result, 'key=value&foo=bar'); - }); - }); // formData - }); // common \ No newline at end of file diff --git a/test/lib/communication.js b/test/lib/communication.js index 7ecad8e..46ef89e 100644 --- a/test/lib/communication.js +++ b/test/lib/communication.js @@ -16,11 +16,12 @@ const testData = require('../test-data/communication'); describe('Communication', function () { let communication, options; - beforeEach(function () { + beforeEach(async function () { options = {}; communication = new Communication(stubLogger, options); + await communication._init(); stubLogger._reset(); - sinon.stub(communication, 'axios'); + sinon.stub(communication, 'got'); }); afterEach(function () { sinon.restore(); @@ -34,20 +35,19 @@ describe('Communication', function () { communication = new Communication(stubLogger); }); - describe('Axios timing coverage', function () { - const request = {}; - const response = { - config: request, - }; - it('tags request', function () { - communication.axios.interceptors.request.handlers[0].fulfilled(request); - assert(request.startTimestampMs); + describe('_init', function () { + it('covers first use', async function () { + await communication._init({}); + assert(communication.got.called); }); - it('tags response', function () { - communication.axios.interceptors.response.handlers[0].fulfilled(response); - assert(response.elapsedTimeMs); + }); // _init + + describe('_onRetry', function () { + it('covers', function () { + communication._onRetry(new Error('oh no'), 1); + assert(communication.logger.debug.called); }); - }); // Axios timing coverage + }); // _onRetry describe('_challengeFromVerifier', function () { it('covers', function () { @@ -127,93 +127,6 @@ describe('Communication', function () { }); }); // userAgentString - describe('Axios Configurations', function () { - let requestUrl, expectedUrl; - beforeEach(function () { - requestUrl = 'https://example.com/client_id'; - expectedUrl = 'https://example.com/client_id'; - }); - it('_axiosConfig', function () { - const method = 'GET'; - const contentType = 'text/plain'; - const body = undefined; - const params = { - 'extra_parameter': 'foobar', - }; - const urlObj = new URL(requestUrl); - const expectedUrlObj = new URL(`${requestUrl}?extra_parameter=foobar`); - const expected = { - method, - url: 'https://example.com/client_id', - headers: { - 'Content-Type': 'text/plain', - }, - params: expectedUrlObj.searchParams, - responseType: 'text', - validateStatus: Communication._validateStatus, - }; - const result = Communication._axiosConfig(method, urlObj, body, params, { - 'Content-Type': contentType, - }); - delete result.transformResponse; - assert.deepStrictEqual(result, expected); - }); - it('_axiosConfig covers defaults', function () { - const method = 'OPTIONS'; - const urlObj = new URL(requestUrl); - const expectedUrlObj = new URL(requestUrl); - const expected = { - method, - url: expectedUrl, - headers: {}, - params: expectedUrlObj.searchParams, - responseType: 'text', - validateStatus: Communication._validateStatus, - }; - const result = Communication._axiosConfig(method, urlObj); - delete result.transformResponse; - assert.deepStrictEqual(result, expected); - }); - it('covers data', function () { - const method = 'POST'; - const body = Buffer.from('some data'); - const params = {}; - const urlObj = new URL(requestUrl); - const expected = { - method, - url: 'https://example.com/client_id', - data: body, - headers: {}, - params: urlObj.searchParams, - responseType: 'text', - validateStatus: Communication._validateStatus, - }; - const result = Communication._axiosConfig(method, urlObj, body, params, {}); - delete result.transformResponse; - assert.deepStrictEqual(result, expected); - }); - it('covers null response transform', function () { - const urlObj = new URL(requestUrl); - const result = Communication._axiosConfig('GET', urlObj, undefined, {}, {}); - result.transformResponse[0](); - }); - - describe('_validateStatus', function () { - it('allows normal valid', function () { - const result = Communication._validateStatus(200); - assert.strictEqual(result, true); - }); - it('allows unauthorized', function () { - const result = Communication._validateStatus(401); - assert.strictEqual(result, true); - }); - it('rejects invalid', function () { - const result = Communication._validateStatus(400); - assert.strictEqual(result, false); - }); - }); // _validateStatus - }); // Axios Configurations - describe('_baseUrlString', function () { it('covers no path', function () { const urlObj = new URL('https://example.com'); @@ -271,7 +184,7 @@ describe('Communication', function () { headers: { link: '; rel="self", ;rel="hub"', }, - data: {}, + body: {}, } }); it('covers', function () { @@ -358,12 +271,12 @@ describe('Communication', function () { urlObj = new URL('https://thuza.ratfeathers.com/'); response = { headers: Object.assign({}, testData.linkHeaders), - data: testData.hCardHtml, + body: Buffer.from(testData.hCardHtml), }; }); it('covers', async function () { - response.data = testData.hCardHtml; - communication.axios.resolves(response); + response.body = testData.hCardHtml; + communication.got.resolves(response); expected = { rels: { 'authorization_endpoint': ['https://ia.squeep.com/auth'], @@ -410,8 +323,8 @@ describe('Communication', function () { result = await communication.fetchMicroformat(urlObj); assert.deepStrictEqual(result, expected); }); - it('covers axios error', async function () { - communication.axios.rejects(new Error('blah')); + it('covers got error', async function () { + communication.got.rejects(new Error('blah')); expected = undefined; result = await communication.fetchMicroformat(urlObj); @@ -419,9 +332,9 @@ describe('Communication', function () { assert.deepStrictEqual(result, expected); }); it('covers non-parsable content', async function () { - response.data = 'some bare text'; + response.body = 'some bare text'; response.headers = {}; - communication.axios.resolves(response); + communication.got.resolves(response); expected = { items: [], rels: {}, @@ -434,7 +347,7 @@ describe('Communication', function () { }); it('covers non-utf8 content', async function () { response.headers['content-type'] = 'text/html; charset=ASCII'; - communication.axios.resolves(response); + communication.got.resolves(response); expected = { rels: { 'authorization_endpoint': ['https://ia.squeep.com/auth'], @@ -492,19 +405,19 @@ describe('Communication', function () { urlObj = new URL('https://thuza.ratfeathers.com/'); response = { headers: Object.assign({}, testData.linkHeaders), - data: testData.hCardHtml, + body: testData.hCardHtml, }; }); it('covers', async function () { - communication.axios.resolves(response); + communication.got.resolves(response); expected = { foo: 'bar', baz: 123 }; - response.data = JSON.stringify(expected); + response.body = expected; result = await communication.fetchJSON(urlObj); assert.deepStrictEqual(result, expected); }); - it('covers axios error', async function () { - communication.axios.rejects(new Error('blah')); + it('covers got error', async function () { + communication.got.rejects(new Error('blah')); expected = undefined; result = await communication.fetchJSON(urlObj); @@ -512,9 +425,11 @@ describe('Communication', function () { assert.deepStrictEqual(result, expected); }); it('covers non-parsable content', async function () { - response.data = 'some bare text'; + response.body = 'some bare text'; response.headers = {}; - communication.axios.resolves(response); + const error = new Error('oh no'); + response.request = { options: { url: new URL('https://example.com/') } }; + communication.got.rejects(new communication.Got.ParseError(error, response)); expected = undefined; result = await communication.fetchJSON(urlObj); @@ -640,11 +555,11 @@ describe('Communication', function () { urlObj = new URL('https://thuza.ratfeathers.com/'); response = { headers: {}, - data: testData.multiMF2Html, + body: testData.multiMF2Html, }; }); it('covers', async function () { - communication.axios.resolves(response); + communication.got.resolves(response); expected = { items: [{ properties: { @@ -665,14 +580,14 @@ describe('Communication', function () { assert.deepStrictEqual(result, expected); }); it('covers failed fetch', async function () { - communication.axios.rejects(); + communication.got.rejects(); expected = undefined; result = await communication.fetchClientIdentifier(urlObj); assert.deepStrictEqual(result, expected); }); it('covers no h-app data', async function () { - response.data = testData.noneMF2Html; - communication.axios.resolves(response); + response.body = testData.noneMF2Html; + communication.got.resolves(response); expected = { items: [], rels: {}, @@ -729,13 +644,13 @@ describe('Communication', function () { urlObj = new URL('https://thuza.ratfeathers.com/'); response = { headers: {}, - data: testData.hCardHtml, + body: testData.hCardHtml, }; sinon.stub(communication, 'fetchJSON'); }); describe('legacy without indieauth-metadata', function () { it('covers', async function () { - communication.axios.resolves(response); + communication.got.resolves(response); expected = { name: 'Thuza', photo: 'https://thuza.ratfeathers.com/image.png', @@ -752,8 +667,8 @@ describe('Communication', function () { assert.deepStrictEqual(result, expected); }); it('covers multiple hCards', async function () { - response.data = testData.multiMF2Html; - communication.axios.resolves(response); + response.body = testData.multiMF2Html; + communication.got.resolves(response); expected = { email: undefined, name: 'Thuza', @@ -770,7 +685,7 @@ describe('Communication', function () { assert.deepStrictEqual(result, expected); }); it('covers failed fetch', async function () { - communication.axios.rejects(); + communication.got.rejects(); expected = { email: undefined, name: undefined, @@ -783,8 +698,8 @@ describe('Communication', function () { }); }); it('covers', async function () { - response.data = testData.hCardMetadataHtml; - communication.axios.resolves(response); + response.body = testData.hCardMetadataHtml; + communication.got.resolves(response); communication.fetchJSON.resolves({ 'issuer': 'https://ia.squeep.com/', 'authorization_endpoint': 'https://ia.squeep.com/auth', @@ -828,8 +743,8 @@ describe('Communication', function () { assert.deepStrictEqual(result, expected); }); it('covers metadata missing fields', async function () { - response.data = testData.hCardMetadataHtml; - communication.axios.resolves(response); + response.body = testData.hCardMetadataHtml; + communication.got.resolves(response); communication.fetchJSON.resolves({ 'issuer': 'https://ia.squeep.com/', }); @@ -850,8 +765,8 @@ describe('Communication', function () { }); it('covers metadata response failure', async function () { const jsonError = new Error('oh no'); - response.data = testData.hCardMetadataHtml; - communication.axios + response.body = testData.hCardMetadataHtml; + communication.got .onCall(0).resolves(response) .onCall(1).rejects(jsonError); communication.fetchJSON.restore(); @@ -874,14 +789,16 @@ describe('Communication', function () { let expected, urlObj, code, codeVerifier, clientId, redirectURI; beforeEach(function () { urlObj = new URL('https://example.com/auth'); - code = Buffer.allocUnsafe(42).toString('base64').replace('/', '_').replace('+', '-'); - codeVerifier = Buffer.allocUnsafe(42).toString('base64').replace('/', '_').replace('+', '-'); + code = Buffer.allocUnsafe(42).toString('base64url'); + codeVerifier = Buffer.allocUnsafe(42).toString('base64url'); clientId = 'https://example.com/'; redirectURI = 'https://example.com/_ia'; }); it('covers', async function () { - communication.axios.resolves({ - data: '{"me":"https://profile.example.com/"}', + communication.got.resolves({ + body: { + me: 'https://profile.example.com/', + }, }); expected = { me: 'https://profile.example.com/', @@ -892,8 +809,10 @@ describe('Communication', function () { assert.deepStrictEqual(result, expected); }); it('covers deprecated method name', async function () { - communication.axios.resolves({ - data: '{"me":"https://profile.example.com/"}', + communication.got.resolves({ + body: { + me: 'https://profile.example.com/', + }, }); expected = { me: 'https://profile.example.com/', @@ -904,7 +823,16 @@ describe('Communication', function () { assert.deepStrictEqual(result, expected); }); it('covers failure', async function () { - communication.axios.resolves('Not a JSON payload.'); + const error = new Error('oh no'); + const response = { + request: { + options: { + url: new URL('https://example.com'), + }, + }, + }; + const parseError = new communication.Got.ParseError(error, response); + communication.got.rejects(parseError); const result = await communication.redeemCode(urlObj, code, codeVerifier, clientId, redirectURI); @@ -921,30 +849,30 @@ describe('Communication', function () { }); it('covers success active', async function () { const nowEpoch = Math.ceil(Date.now() / 1000); - communication.axios.resolves({ - data: JSON.stringify({ + communication.got.resolves({ + body: { active: true, me: 'https://profile.example.com/', 'client_id': 'https://app.example.com/', scope: 'create profile email', exp: nowEpoch + 86400, iat: nowEpoch, - }), + }, }); const result = await communication.introspectToken(introspectionUrlObj, authenticationHeader, token); assert.strictEqual(result.active, true); }); it('covers success inactive', async function () { - communication.axios.resolves({ - data: JSON.stringify({ + communication.got.resolves({ + body: { active: false, - }), + }, }); const result = await communication.introspectToken(introspectionUrlObj, authenticationHeader, token); assert.strictEqual(result.active, false); }); it('covers failure', async function () { - communication.axios.resolves('what kind of response is this?'); + communication.got.resolves({ body: 'what kind of response is this?' }); await assert.rejects(() => communication.introspectToken(introspectionUrlObj, authenticationHeader, token)); }); }); // introspectToken @@ -958,14 +886,14 @@ describe('Communication', function () { ticket = 'XXXThisIsATicketXXX'; }); it('covers success', async function () { - const expected = { data: 'blah', statusCode: 200 }; - communication.axios.resolves(expected); + const expected = { body: 'blah', statusCode: 200 }; + communication.got.resolves(expected); const result = await communication.deliverTicket(ticketEndpointUrlObj, resourceUrlObj, subjectUrlObj, ticket); assert.deepStrictEqual(result, expected); }); it('covers failure', async function () { const expectedException = new Error('oh no'); - communication.axios.rejects(expectedException); + communication.got.rejects(expectedException); await assert.rejects(() => communication.deliverTicket(ticketEndpointUrlObj, resourceUrlObj, subjectUrlObj, ticket), expectedException); }); }); // deliverTicket