chore: use http-request-plus@1
This commit is contained in:
parent
bc0afb589e
commit
ab96c549ae
@ -67,38 +67,44 @@ ${pkg.name} v${pkg.version}`
|
||||
// sequence path of the current call
|
||||
const callPath = []
|
||||
|
||||
let url
|
||||
let { token } = opts
|
||||
if (opts.url !== '') {
|
||||
url = new URL(opts.url)
|
||||
const { username } = url
|
||||
if (username !== '') {
|
||||
token = username
|
||||
url.username = ''
|
||||
}
|
||||
} else {
|
||||
url = new URL('https://localhost/')
|
||||
if (opts.host !== '') {
|
||||
url.host = opts.host
|
||||
} else {
|
||||
const { hostname = 'localhost', port } = config?.http?.listen?.https ?? {}
|
||||
url.hostname = hostname
|
||||
url.port = port
|
||||
}
|
||||
}
|
||||
|
||||
url = new URL('/api/v1', url)
|
||||
const baseRequest = {
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
cookie: `authenticationToken=${token}`,
|
||||
},
|
||||
pathname: '/api/v1',
|
||||
method: 'POST',
|
||||
rejectUnauthorized: false,
|
||||
}
|
||||
let { token } = opts
|
||||
if (opts.url !== '') {
|
||||
const { protocol, host, username } = new URL(opts.url)
|
||||
Object.assign(baseRequest, { protocol, host })
|
||||
if (username !== '') {
|
||||
token = username
|
||||
}
|
||||
} else {
|
||||
baseRequest.protocol = 'https:'
|
||||
if (opts.host !== '') {
|
||||
baseRequest.host = opts.host
|
||||
} else {
|
||||
const { hostname = 'localhost', port } = config?.http?.listen?.https ?? {}
|
||||
baseRequest.hostname = hostname
|
||||
baseRequest.port = port
|
||||
}
|
||||
}
|
||||
baseRequest.headers.cookie = `authenticationToken=${token}`
|
||||
|
||||
const call = async ({ method, params }) => {
|
||||
if (callPath.length !== 0) {
|
||||
process.stderr.write(`\n${colors.bold(`--- call #${callPath.join('.')}`)} ---\n\n`)
|
||||
}
|
||||
|
||||
const response = await hrp.post(baseRequest, {
|
||||
const response = await hrp(url, {
|
||||
...baseRequest,
|
||||
|
||||
body: format.request(0, method, params),
|
||||
})
|
||||
|
||||
|
@ -32,7 +32,7 @@
|
||||
"content-type": "^1.0.4",
|
||||
"cson-parser": "^4.0.7",
|
||||
"getopts": "^2.2.3",
|
||||
"http-request-plus": "^0.14.0",
|
||||
"http-request-plus": "github:JsCommunity/http-request-plus#v1",
|
||||
"json-rpc-protocol": "^0.13.1",
|
||||
"promise-toolbox": "^0.21.0",
|
||||
"pumpify": "^2.0.1",
|
||||
|
@ -35,7 +35,7 @@
|
||||
"form-data": "^4.0.0",
|
||||
"fs-extra": "^11.1.0",
|
||||
"get-stream": "^6.0.0",
|
||||
"http-request-plus": "^0.14.0",
|
||||
"http-request-plus": "github:JsCommunity/http-request-plus#v1",
|
||||
"human-format": "^1.0.0",
|
||||
"lodash": "^4.17.4",
|
||||
"pretty-ms": "^7.0.0",
|
||||
|
@ -230,10 +230,15 @@ export async function upload(args) {
|
||||
)
|
||||
formData.append('file', input, { filename: 'file', knownLength: length })
|
||||
try {
|
||||
return await hrp.post(url.toString(), { body: formData, headers: formData.getHeaders() }).readAll('utf-8')
|
||||
const response = await hrp(url.toString(), { body: formData, headers: formData.getHeaders(), method: 'POST' })
|
||||
return await response.text()
|
||||
} catch (e) {
|
||||
console.log('ERROR', e)
|
||||
console.log('ERROR content', await e.response.readAll('utf-8'))
|
||||
const { response } = e
|
||||
if (response !== undefined) {
|
||||
console.log('ERROR content', await response.text())
|
||||
}
|
||||
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@
|
||||
"@xen-orchestra/log": "^0.6.0",
|
||||
"d3-time-format": "^3.0.0",
|
||||
"golike-defer": "^0.5.1",
|
||||
"http-request-plus": "^0.14.0",
|
||||
"http-request-plus": "github:JsCommunity/http-request-plus#v1",
|
||||
"json-rpc-protocol": "^0.13.2",
|
||||
"lodash": "^4.17.15",
|
||||
"promise-toolbox": "^0.21.0",
|
||||
|
@ -35,7 +35,7 @@
|
||||
"bind-property-descriptor": "^2.0.0",
|
||||
"blocked": "^1.2.1",
|
||||
"debug": "^4.0.1",
|
||||
"http-request-plus": "^0.14.0",
|
||||
"http-request-plus": "github:JsCommunity/http-request-plus#v1",
|
||||
"jest-diff": "^29.0.3",
|
||||
"json-rpc-protocol": "^0.13.1",
|
||||
"kindof": "^2.0.0",
|
||||
|
@ -5,7 +5,6 @@ import ms from 'ms'
|
||||
import httpRequest from 'http-request-plus'
|
||||
import map from 'lodash/map'
|
||||
import noop from 'lodash/noop'
|
||||
import omit from 'lodash/omit'
|
||||
import ProxyAgent from 'proxy-agent'
|
||||
import { coalesceCalls } from '@vates/coalesce-calls'
|
||||
import { Collection } from 'xo-collection'
|
||||
@ -392,7 +391,7 @@ export class Xapi extends EventEmitter {
|
||||
const response = await this._addSyncStackTrace(
|
||||
pRetry(
|
||||
async () =>
|
||||
httpRequest($cancelToken, url.href, {
|
||||
httpRequest(url, {
|
||||
rejectUnauthorized: !this._allowUnauthorized,
|
||||
|
||||
// this is an inactivity timeout (unclear in Node doc)
|
||||
@ -403,6 +402,8 @@ export class Xapi extends EventEmitter {
|
||||
// Support XS <= 6.5 with Node => 12
|
||||
minVersion: 'TLSv1',
|
||||
agent: this.httpAgent,
|
||||
|
||||
signal: $cancelToken,
|
||||
}),
|
||||
{
|
||||
when: { code: 302 },
|
||||
@ -411,7 +412,7 @@ export class Xapi extends EventEmitter {
|
||||
if (response === undefined) {
|
||||
throw error
|
||||
}
|
||||
response.cancel()
|
||||
response.destroy()
|
||||
url = await this._replaceHostAddressInUrl(new URL(response.headers.location, url))
|
||||
},
|
||||
}
|
||||
@ -467,40 +468,45 @@ export class Xapi extends EventEmitter {
|
||||
url.search = new URLSearchParams(query)
|
||||
await this._setHostAddressInUrl(url, host)
|
||||
|
||||
const doRequest = httpRequest.put.bind(undefined, $cancelToken, {
|
||||
agent: this.httpAgent,
|
||||
const doRequest = (url, opts) =>
|
||||
httpRequest(url, {
|
||||
agent: this.httpAgent,
|
||||
|
||||
body,
|
||||
headers,
|
||||
rejectUnauthorized: !this._allowUnauthorized,
|
||||
body,
|
||||
headers,
|
||||
method: 'PUT',
|
||||
rejectUnauthorized: !this._allowUnauthorized,
|
||||
signal: $cancelToken,
|
||||
|
||||
// this is an inactivity timeout (unclear in Node doc)
|
||||
timeout: this._httpInactivityTimeout,
|
||||
// this is an inactivity timeout (unclear in Node doc)
|
||||
timeout: this._httpInactivityTimeout,
|
||||
|
||||
// Support XS <= 6.5 with Node => 12
|
||||
minVersion: 'TLSv1',
|
||||
})
|
||||
// Support XS <= 6.5 with Node => 12
|
||||
minVersion: 'TLSv1',
|
||||
|
||||
...opts,
|
||||
})
|
||||
|
||||
const dummyUrl = new URL(url)
|
||||
dummyUrl.searchParams.delete('task_id')
|
||||
|
||||
// if body is a stream, sends a dummy request to probe for a redirection
|
||||
// before consuming body
|
||||
const response = await this._addSyncStackTrace(
|
||||
isStream
|
||||
? doRequest(url.href, {
|
||||
? doRequest(dummyUrl, {
|
||||
body: '',
|
||||
|
||||
// omit task_id because this request will fail on purpose
|
||||
query: 'task_id' in query ? omit(query, 'task_id') : query,
|
||||
|
||||
maxRedirects: 0,
|
||||
}).then(
|
||||
response => {
|
||||
response.cancel()
|
||||
return doRequest(url.href)
|
||||
response.destroy()
|
||||
return doRequest(url)
|
||||
},
|
||||
async error => {
|
||||
let response
|
||||
if (error != null && (response = error.response) != null) {
|
||||
response.cancel()
|
||||
response.destroy()
|
||||
|
||||
const {
|
||||
headers: { location },
|
||||
@ -510,14 +516,14 @@ export class Xapi extends EventEmitter {
|
||||
// ensure the original query is sent
|
||||
const newUrl = new URL(location, url)
|
||||
newUrl.searchParams.set('task_id', query.task_id)
|
||||
return doRequest((await this._replaceHostAddressInUrl(newUrl)).href)
|
||||
return doRequest(await this._replaceHostAddressInUrl(newUrl))
|
||||
}
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
)
|
||||
: doRequest(url.href)
|
||||
: doRequest(url)
|
||||
)
|
||||
|
||||
if (pTaskResult !== undefined) {
|
||||
@ -540,13 +546,13 @@ export class Xapi extends EventEmitter {
|
||||
const { req } = response
|
||||
if (!req.finished) {
|
||||
await new Promise((resolve, reject) => {
|
||||
req.on('finish', resolve).on('error', reject)
|
||||
req.on('finish', resolve)
|
||||
response.on('error', reject)
|
||||
})
|
||||
}
|
||||
|
||||
if (useHack) {
|
||||
response.cancel()
|
||||
response.destroy()
|
||||
} else {
|
||||
// consume the response
|
||||
response.resume()
|
||||
|
@ -1,6 +1,5 @@
|
||||
import httpRequestPlus from 'http-request-plus'
|
||||
import { format, parse } from 'json-rpc-protocol'
|
||||
import { join } from 'path'
|
||||
|
||||
import XapiError from '../_XapiError'
|
||||
|
||||
@ -8,41 +7,37 @@ import UnsupportedTransport from './_UnsupportedTransport'
|
||||
|
||||
// https://github.com/xenserver/xenadmin/blob/0df39a9d83cd82713f32d24704852a0fd57b8a64/XenModel/XenAPI/Session.cs#L403-L433
|
||||
export default ({ secureOptions, url, agent }) => {
|
||||
return (method, args) =>
|
||||
httpRequestPlus
|
||||
.post(url, {
|
||||
...secureOptions,
|
||||
body: format.request(0, method, args),
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
pathname: join(url.pathname, 'jsonrpc'),
|
||||
agent,
|
||||
})
|
||||
.readAll('utf8')
|
||||
.then(
|
||||
text => {
|
||||
let response
|
||||
try {
|
||||
response = parse(text)
|
||||
} catch (error) {
|
||||
throw new UnsupportedTransport()
|
||||
}
|
||||
url = new URL('./jsonrpc', Object.assign(new URL('http://localhost'), url))
|
||||
|
||||
if (response.type === 'response') {
|
||||
return response.result
|
||||
}
|
||||
return async function (method, args) {
|
||||
const res = await httpRequestPlus(url, {
|
||||
...secureOptions,
|
||||
body: format.request(0, method, args),
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
agent,
|
||||
}).catch(error => {
|
||||
console.warn('xen-api/transports/json-rpc', error)
|
||||
|
||||
throw XapiError.wrap(response.error)
|
||||
},
|
||||
error => {
|
||||
if (error.response !== undefined) {
|
||||
// HTTP error
|
||||
throw new UnsupportedTransport()
|
||||
}
|
||||
throw new UnsupportedTransport()
|
||||
})
|
||||
|
||||
throw error
|
||||
}
|
||||
)
|
||||
const text = await res.text()
|
||||
|
||||
let response
|
||||
try {
|
||||
response = parse(text)
|
||||
} catch (error) {
|
||||
throw new UnsupportedTransport()
|
||||
}
|
||||
|
||||
if (response.type === 'response') {
|
||||
return response.result
|
||||
}
|
||||
|
||||
throw XapiError.wrap(response.error)
|
||||
}
|
||||
}
|
||||
|
@ -473,14 +473,15 @@ async function call(args) {
|
||||
noop
|
||||
)
|
||||
|
||||
return hrp
|
||||
.post(url, httpOptions, {
|
||||
body: input,
|
||||
headers: {
|
||||
'content-length': length,
|
||||
},
|
||||
})
|
||||
.readAll('utf-8')
|
||||
const response = await hrp(url, {
|
||||
...httpOptions,
|
||||
body: input,
|
||||
headers: {
|
||||
'content-length': length,
|
||||
},
|
||||
method: 'POST',
|
||||
})
|
||||
return response.text()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@
|
||||
"chalk": "^5.0.1",
|
||||
"fs-extra": "^11.1.0",
|
||||
"getopts": "^2.3.0",
|
||||
"http-request-plus": "^0.14.0",
|
||||
"http-request-plus": "github:JsCommunity/http-request-plus#v1",
|
||||
"human-format": "^1.0.0",
|
||||
"lodash": "^4.17.4",
|
||||
"micromatch": "^4.0.2",
|
||||
|
@ -35,11 +35,6 @@ const indexName = (name, index) => {
|
||||
return name.slice(0, NAME_MAX_LENGTH - suffix.length) + suffix
|
||||
}
|
||||
|
||||
const onRequest = req => {
|
||||
req.setTimeout(REQUEST_TIMEOUT)
|
||||
req.on('timeout', req.abort)
|
||||
}
|
||||
|
||||
class Netbox {
|
||||
#allowUnauthorized
|
||||
#endpoint
|
||||
@ -108,15 +103,15 @@ class Netbox {
|
||||
const options = {
|
||||
headers: { 'Content-Type': 'application/json', Authorization: `Token ${this.#token}` },
|
||||
method,
|
||||
onRequest,
|
||||
rejectUnauthorized: !this.#allowUnauthorized,
|
||||
timeout: REQUEST_TIMEOUT,
|
||||
}
|
||||
|
||||
const httpRequest = async () => {
|
||||
try {
|
||||
const response = await this.#xo.httpRequest(url, options)
|
||||
this.#netboxApiVersion = response.headers['api-version']
|
||||
const body = await response.readAll()
|
||||
const body = await response.text()
|
||||
if (body.length > 0) {
|
||||
return JSON.parse(body)
|
||||
}
|
||||
@ -127,7 +122,7 @@ class Netbox {
|
||||
body: dataDebug,
|
||||
}
|
||||
try {
|
||||
const body = await error.response.readAll()
|
||||
const body = await error.response.text()
|
||||
if (body.length > 0) {
|
||||
error.data.error = JSON.parse(body)
|
||||
}
|
||||
|
@ -689,7 +689,7 @@ ${entriesWithMissingStats.map(({ listItem }) => listItem).join('\n')}`
|
||||
payload.vm_uuid = xapiObject.uuid
|
||||
}
|
||||
// JSON is not well formed, can't use the default node parser
|
||||
return JSON5.parse(await (await xapi.getResource('/rrd_updates', payload)).readAll())
|
||||
return JSON5.parse(await (await xapi.getResource('/rrd_updates', payload)).text())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,7 +121,7 @@ class XoServerIcinga2 {
|
||||
exit_status: icinga2Status,
|
||||
}),
|
||||
})
|
||||
.readAll()
|
||||
.then(response => response.text())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,10 +38,7 @@ class XoServerHooks {
|
||||
body: JSON.stringify({ ...data, type }),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
method: 'POST',
|
||||
onRequest: req => {
|
||||
req.setTimeout(1e4)
|
||||
req.on('timeout', req.abort)
|
||||
},
|
||||
timeout: 1e4,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -82,7 +82,7 @@
|
||||
"helmet": "^3.9.0",
|
||||
"highland": "^2.11.1",
|
||||
"http-proxy": "^1.16.2",
|
||||
"http-request-plus": "^0.14.0",
|
||||
"http-request-plus": "github:JsCommunity/http-request-plus#v1",
|
||||
"http-server-plus": "^1.0.0",
|
||||
"human-format": "^1.0.0",
|
||||
"iterable-backoff": "^0.1.0",
|
||||
|
@ -1133,7 +1133,7 @@ async function handleExport(req, res, { xapi, vmRef, compress, format = 'xva' })
|
||||
const stream =
|
||||
format === 'ova' ? await xapi.exportVmOva(vmRef) : await xapi.VM_export(FAIL_ON_QUEUE, vmRef, { compress })
|
||||
|
||||
res.on('close', () => stream.cancel())
|
||||
res.on('close', () => stream.destroy())
|
||||
// Remove the filename as it is already part of the URL.
|
||||
stream.headers['content-disposition'] = 'attachment'
|
||||
|
||||
|
@ -263,7 +263,7 @@ export default class XapiStats {
|
||||
start: timestamp,
|
||||
},
|
||||
})
|
||||
.then(response => response.readAll().then(JSON5.parse))
|
||||
.then(response => response.text().then(JSON5.parse))
|
||||
}
|
||||
|
||||
// To avoid multiple requests, we keep a cash for the stats and
|
||||
|
@ -65,11 +65,7 @@ export default {
|
||||
async _getXenUpdates() {
|
||||
const response = await this.xo.httpRequest('http://updates.xensource.com/XenServer/updates.xml')
|
||||
|
||||
if (response.statusCode !== 200) {
|
||||
throw new Error('cannot fetch patches list from Citrix')
|
||||
}
|
||||
|
||||
const data = parseXml(await response.readAll()).patchdata
|
||||
const data = parseXml(await response.buffer()).patchdata
|
||||
|
||||
const patches = { __proto__: null }
|
||||
forEach(data.patches.patch, patch => {
|
||||
|
@ -26,13 +26,11 @@ export default class Http {
|
||||
})
|
||||
}
|
||||
|
||||
httpRequest(...args) {
|
||||
return hrp(
|
||||
{
|
||||
agent: this._agent,
|
||||
},
|
||||
...args
|
||||
)
|
||||
httpRequest(url, opts) {
|
||||
return hrp(url, {
|
||||
...opts,
|
||||
agent: this._agent,
|
||||
})
|
||||
}
|
||||
|
||||
// Inject the proxy into the environnement, it will be automatically used by `_agent` and by most libs (e.g `axios`)
|
||||
|
@ -419,6 +419,8 @@ export default class Proxy {
|
||||
async callProxyMethod(id, method, params, { assertType = 'scalar' } = {}) {
|
||||
const proxy = await this._getProxy(id)
|
||||
|
||||
const url = new URL('https://localhost/api/v1')
|
||||
|
||||
const request = {
|
||||
body: format.request(0, method, params),
|
||||
headers: {
|
||||
@ -426,14 +428,12 @@ export default class Proxy {
|
||||
Cookie: cookie.serialize('authenticationToken', proxy.authenticationToken),
|
||||
},
|
||||
method: 'POST',
|
||||
pathname: '/api/v1',
|
||||
protocol: 'https:',
|
||||
rejectUnauthorized: false,
|
||||
timeout: this._app.config.getDuration('xo-proxy.callTimeout'),
|
||||
}
|
||||
|
||||
if (proxy.address !== undefined) {
|
||||
request.host = proxy.address
|
||||
url.host = proxy.address
|
||||
} else {
|
||||
const vm = this._app.getXapi(proxy.vmUuid).getObjectByUuid(proxy.vmUuid)
|
||||
|
||||
@ -444,11 +444,10 @@ export default class Proxy {
|
||||
throw error
|
||||
}
|
||||
|
||||
// use hostname field to avoid issues with IPv6 addresses
|
||||
request.hostname = address
|
||||
url.hostname = address.includes(':') ? `[${address}]` : address
|
||||
}
|
||||
|
||||
const response = await hrp(request)
|
||||
const response = await hrp(url, request)
|
||||
|
||||
const authenticationToken = parseSetCookie(response, {
|
||||
map: true,
|
||||
|
16
yarn.lock
16
yarn.lock
@ -10786,14 +10786,11 @@ http-proxy@^1.16.2, http-proxy@^1.17.0, http-proxy@^1.18.1:
|
||||
follow-redirects "^1.0.0"
|
||||
requires-port "^1.0.0"
|
||||
|
||||
http-request-plus@^0.14.0:
|
||||
"http-request-plus@github:JsCommunity/http-request-plus#v1":
|
||||
version "0.14.0"
|
||||
resolved "https://registry.yarnpkg.com/http-request-plus/-/http-request-plus-0.14.0.tgz#a190ece4b5b67d66ab442d2983a1a1bcd363b866"
|
||||
integrity sha512-EX/OoPA2vWO7k9Whq+vOSN/nH/P1Ae9smCSzhBIIEU8WLDJyXoH9S8pY4Ieh4yQartrMB1vc2o6T4KV4oIQsDQ==
|
||||
resolved "https://codeload.github.com/JsCommunity/http-request-plus/tar.gz/af641418c9a91e0285f9662e2794822045062b0c"
|
||||
dependencies:
|
||||
is-redirect "^1.0.0"
|
||||
promise-toolbox "^0.19.2"
|
||||
pump "^3.0.0"
|
||||
"@xen-orchestra/log" "^0.6.0"
|
||||
|
||||
http-server-plus@^0.12.0:
|
||||
version "0.12.0"
|
||||
@ -11621,11 +11618,6 @@ is-promise@^2.0.0, is-promise@^2.2.2:
|
||||
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1"
|
||||
integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==
|
||||
|
||||
is-redirect@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
|
||||
integrity sha512-cr/SlUEe5zOGmzvj9bUyC4LVvkNVAXu4GytXLNMr1pny+a65MpQ9IJzFHD5vi7FyJgb4qt27+eS3TuQnqB+RQw==
|
||||
|
||||
is-regex@^1.0.3, is-regex@^1.0.4, is-regex@^1.0.5, is-regex@^1.1.0, is-regex@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
|
||||
@ -15879,7 +15871,7 @@ promise-polyfill@^6.0.1:
|
||||
resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-6.1.0.tgz#dfa96943ea9c121fca4de9b5868cb39d3472e057"
|
||||
integrity sha512-g0LWaH0gFsxovsU7R5LrrhHhWAWiHRnh1GPrhXnPgYsDkIqjRYUYSZEsej/wtleDrz5xVSIDbeKfidztp2XHFQ==
|
||||
|
||||
promise-toolbox@^0.19.0, promise-toolbox@^0.19.2:
|
||||
promise-toolbox@^0.19.0:
|
||||
version "0.19.2"
|
||||
resolved "https://registry.yarnpkg.com/promise-toolbox/-/promise-toolbox-0.19.2.tgz#7453d117313a9afba6add0c67c46210f7b5833f8"
|
||||
integrity sha512-3956j2kaS4nJG1ANd4SZBQj8GrxLSlvfpVzMT4I7k7K8BkhhpAChXOI3B1VMlU7TQstShBh4D4uKt9zFjahKNg==
|
||||
|
Loading…
Reference in New Issue
Block a user