From c6eccda2c7945c2cd9f324a5f7fea927af52fdd2 Mon Sep 17 00:00:00 2001 From: Moti Zilberman Date: Wed, 8 Nov 2023 07:19:40 -0800 Subject: [PATCH] Add tests for HTTPS-specific behaviour (#41341) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/41341 * Extends `dev-middleware`'s test utilities to enable testing against an HTTPS server with a self-signed cert. * Runs the CDP transport integration tests (D51002261) using both HTTP and HTTPS. * Adds a test to explicitly cover the `ws=...` / `wss=...` variation in `devtoolsFrontendUrl` first introduced in D49158227. Changelog: [Internal] Reviewed By: blakef Differential Revision: D51006835 fbshipit-source-id: df3db8cd865898248cd0d8f307f75949a7f313fd --- flow-typed/npm/selfsigned_v2.x.x.js | 101 +++++++++ flow-typed/npm/undici_v5.x.x.js | 26 +++ packages/dev-middleware/package.json | 2 + .../src/__tests__/FetchUtils.js | 29 ++- .../src/__tests__/InspectorDebuggerUtils.js | 5 +- .../src/__tests__/InspectorDeviceUtils.js | 5 +- .../InspectorProxyCdpTransport-test.js | 200 +++++++++--------- .../__tests__/InspectorProxyHttpApi-test.js | 75 ++++++- .../src/__tests__/ServerUtils.js | 49 ++++- .../src/inspector-proxy/types.js | 4 +- yarn.lock | 32 +++ 11 files changed, 407 insertions(+), 121 deletions(-) create mode 100644 flow-typed/npm/selfsigned_v2.x.x.js create mode 100644 flow-typed/npm/undici_v5.x.x.js diff --git a/flow-typed/npm/selfsigned_v2.x.x.js b/flow-typed/npm/selfsigned_v2.x.x.js new file mode 100644 index 00000000000..be353372f13 --- /dev/null +++ b/flow-typed/npm/selfsigned_v2.x.x.js @@ -0,0 +1,101 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + * @oncall react_native + */ + +declare module 'selfsigned' { + declare interface SelfsignedOptions { + /** + * The number of days before expiration + * + * @default 365 */ + days?: number; + + /** + * The date before which the certificate should not be valid + * + * @default now */ + notBeforeDate?: Date; + + /** + * the size for the private key in bits + * @default 1024 + */ + keySize?: number; + /** + * additional extensions for the certificate + */ + extensions?: mixed[]; + /** + * The signature algorithm sha256 or sha1 + * @default "sha1" + */ + algorithm?: string; + /** + * include PKCS#7 as part of the output + * @default false + */ + pkcs7?: boolean; + /** + * generate client cert signed by the original key + * @default false + */ + clientCertificate?: boolean; + /** + * client certificate's common name + * @default "John Doe jdoe123" + */ + clientCertificateCN?: string; + /** + * the size for the client private key in bits + * @default 1024 + */ + clientCertificateKeySize?: number; + } + + declare interface GenerateResult { + private: string; + public: string; + cert: string; + fingerprint: string; + } + + declare export function generate( + attrs?: pki$CertificateField[], + opts?: SelfsignedOptions, + ): GenerateResult; + + declare export function generate( + attrs?: pki$CertificateField[], + opts?: SelfsignedOptions, + /** Optional callback, if not provided the generation is synchronous */ + done?: (err: void | Error, result: GenerateResult) => mixed, + ): void; + + // definitions from node-forge's `pki` and `asn1` namespaces + declare interface pki$CertificateFieldOptions { + name?: string | void; + type?: string | void; + shortName?: string | void; + } + + declare enum asn1$Class { + UNIVERSAL = 0x00, + APPLICATION = 0x40, + CONTEXT_SPECIFIC = 0x80, + PRIVATE = 0xc0, + } + + declare interface pki$CertificateField extends pki$CertificateFieldOptions { + valueConstructed?: boolean | void; + valueTagClass?: asn1$Class | void; + value?: mixed[] | string | void; + extensions?: mixed[] | void; + } +} diff --git a/flow-typed/npm/undici_v5.x.x.js b/flow-typed/npm/undici_v5.x.x.js new file mode 100644 index 00000000000..48360acd233 --- /dev/null +++ b/flow-typed/npm/undici_v5.x.x.js @@ -0,0 +1,26 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + * @oncall react_native + */ + +// Very incomplete types for the undici package. + +declare interface undici$Agent$Options { + connect?: tls$connectOptions; +} + +declare module 'undici' { + declare export class Dispatcher extends events$EventEmitter { + constructor(): void; + } + + declare export class Agent extends Dispatcher { + constructor(opts?: undici$Agent$Options): void; + } +} diff --git a/packages/dev-middleware/package.json b/packages/dev-middleware/package.json index f0521c39986..8b156a76065 100644 --- a/packages/dev-middleware/package.json +++ b/packages/dev-middleware/package.json @@ -30,6 +30,7 @@ "debug": "^2.2.0", "node-fetch": "^2.2.0", "open": "^7.0.3", + "selfsigned": "^2.4.1", "serve-static": "^1.13.1", "temp-dir": "^2.0.0" }, @@ -37,6 +38,7 @@ "node": ">=18" }, "devDependencies": { + "undici": "^5.27.2", "wait-for-expect": "^3.0.2" } } diff --git a/packages/dev-middleware/src/__tests__/FetchUtils.js b/packages/dev-middleware/src/__tests__/FetchUtils.js index 57c6b385e6c..6c89a5763d5 100644 --- a/packages/dev-middleware/src/__tests__/FetchUtils.js +++ b/packages/dev-middleware/src/__tests__/FetchUtils.js @@ -11,8 +11,33 @@ import type {JSONSerializable} from '../inspector-proxy/types'; -export async function fetchJson(url: string): Promise { - const response = await fetch(url); +import {Agent} from 'undici'; + +/** + * A version of `fetch` that is usable with the HTTPS server created in + * ServerUtils (which uses a self-signed certificate). + */ +export async function fetchLocal( + url: string, + options?: Parameters[1] & {dispatcher?: mixed}, +): ReturnType { + return await fetch(url, { + ...options, + // Node's native `fetch` comes from undici and supports the same options, + // including `dispatcher` which we use to make it accept self-signed + // certificates. + dispatcher: + options?.dispatcher ?? + new Agent({ + connect: { + rejectUnauthorized: false, + }, + }), + }); +} + +export async function fetchJson(url: string): Promise { + const response = await fetchLocal(url); if (!response.ok) { throw new Error(`HTTP ${response.status} ${response.statusText}`); } diff --git a/packages/dev-middleware/src/__tests__/InspectorDebuggerUtils.js b/packages/dev-middleware/src/__tests__/InspectorDebuggerUtils.js index eb1f33bebfc..bf6ece4abd3 100644 --- a/packages/dev-middleware/src/__tests__/InspectorDebuggerUtils.js +++ b/packages/dev-middleware/src/__tests__/InspectorDebuggerUtils.js @@ -19,7 +19,10 @@ export class DebuggerAgent { _readyPromise: Promise; constructor(url: string, signal?: AbortSignal) { - const ws = new WebSocket(url); + const ws = new WebSocket(url, { + // The mock server uses a self-signed certificate. + rejectUnauthorized: false, + }); this._ws = ws; ws.on('message', data => { this.__handle(JSON.parse(data.toString())); diff --git a/packages/dev-middleware/src/__tests__/InspectorDeviceUtils.js b/packages/dev-middleware/src/__tests__/InspectorDeviceUtils.js index ee5e8e91311..a1170fbfaea 100644 --- a/packages/dev-middleware/src/__tests__/InspectorDeviceUtils.js +++ b/packages/dev-middleware/src/__tests__/InspectorDeviceUtils.js @@ -27,7 +27,10 @@ export class DeviceAgent { _readyPromise: Promise; constructor(url: string, signal?: AbortSignal) { - const ws = new WebSocket(url); + const ws = new WebSocket(url, { + // The mock server uses a self-signed certificate. + rejectUnauthorized: false, + }); this._ws = ws; ws.on('message', data => { this.__handle(JSON.parse(data.toString())); diff --git a/packages/dev-middleware/src/__tests__/InspectorProxyCdpTransport-test.js b/packages/dev-middleware/src/__tests__/InspectorProxyCdpTransport-test.js index f354cf90272..6d76d304e84 100644 --- a/packages/dev-middleware/src/__tests__/InspectorProxyCdpTransport-test.js +++ b/packages/dev-middleware/src/__tests__/InspectorProxyCdpTransport-test.js @@ -23,126 +23,130 @@ jest.useRealTimers(); jest.setTimeout(10000); -describe('inspector proxy CDP transport', () => { - const serverRef = withServerForEachTest({ - logger: undefined, - projectRoot: '', - }); - const autoCleanup = withAbortSignalForEachTest(); - afterEach(() => { - jest.clearAllMocks(); - }); +describe.each(['HTTP', 'HTTPS'])( + 'inspector proxy CDP transport over %s', + protocol => { + const serverRef = withServerForEachTest({ + logger: undefined, + projectRoot: '', + secure: protocol === 'HTTPS', + }); + const autoCleanup = withAbortSignalForEachTest(); + afterEach(() => { + jest.clearAllMocks(); + }); - test('connection/disconnection and message from debugger to device', async () => { - const device1 = await createDeviceMock( - `${serverRef.serverBaseWsUrl}/inspector/device?device=device1&name=foo&app=bar`, - autoCleanup.signal, - ); - try { - device1.getPages.mockImplementation(() => [ - { - app: 'bar-app', - id: 'page1', - title: 'bar-title', - vm: 'bar-vm', - }, - ]); - - let pageList: Array = []; - await until(async () => { - pageList = (await fetchJson( - `${serverRef.serverBaseUrl}/json`, - // $FlowIgnore[unclear-type] - ): any); - expect(pageList).toHaveLength(1); - }); - const [{webSocketDebuggerUrl}] = pageList; - expect(webSocketDebuggerUrl).toBeDefined(); - - const debugger_ = await createDebuggerMock( - webSocketDebuggerUrl, + test('connection/disconnection and message from debugger to device', async () => { + const device1 = await createDeviceMock( + `${serverRef.serverBaseWsUrl}/inspector/device?device=device1&name=foo&app=bar`, autoCleanup.signal, ); try { - await until(() => expect(device1.connect).toBeCalled()); + device1.getPages.mockImplementation(() => [ + { + app: 'bar-app', + id: 'page1', + title: 'bar-title', + vm: 'bar-vm', + }, + ]); - debugger_.send({ - method: 'Runtime.enable', - id: 0, + let pageList: Array = []; + await until(async () => { + pageList = (await fetchJson( + `${serverRef.serverBaseUrl}/json`, + // $FlowIgnore[unclear-type] + ): any); + expect(pageList).toHaveLength(1); }); + const [{webSocketDebuggerUrl}] = pageList; + expect(webSocketDebuggerUrl).toBeDefined(); - await until(() => expect(device1.wrappedEvent).toBeCalled()); + const debugger_ = await createDebuggerMock( + webSocketDebuggerUrl, + autoCleanup.signal, + ); + try { + await until(() => expect(device1.connect).toBeCalled()); - expect(device1.wrappedEventParsed).toBeCalledWith({ - pageId: 'page1', - wrappedEvent: { + debugger_.send({ method: 'Runtime.enable', id: 0, - }, - }); + }); - debugger_.close(); + await until(() => expect(device1.wrappedEvent).toBeCalled()); - await until(() => expect(device1.disconnect).toBeCalled()); + expect(device1.wrappedEventParsed).toBeCalledWith({ + pageId: 'page1', + wrappedEvent: { + method: 'Runtime.enable', + id: 0, + }, + }); + + debugger_.close(); + + await until(() => expect(device1.disconnect).toBeCalled()); + } finally { + debugger_.close(); + } } finally { - debugger_.close(); + device1.close(); } - } finally { - device1.close(); - } - }); + }); - test('message and disconnection from device to debugger', async () => { - const device1 = await createDeviceMock( - `${serverRef.serverBaseWsUrl}/inspector/device?device=device1&name=foo&app=bar`, - autoCleanup.signal, - ); - try { - device1.getPages.mockImplementation(() => [ - { - app: 'bar-app', - id: 'page1', - title: 'bar-title', - vm: 'bar-vm', - }, - ]); - - let pageList: Array = []; - await until(async () => { - pageList = (await fetchJson( - `${serverRef.serverBaseUrl}/json`, - // $FlowIgnore[unclear-type] - ): any); - expect(pageList).toHaveLength(1); - }); - const [{webSocketDebuggerUrl}] = pageList; - expect(webSocketDebuggerUrl).toBeDefined(); - - const debugger_ = await createDebuggerMock( - webSocketDebuggerUrl, + test('message and disconnection from device to debugger', async () => { + const device1 = await createDeviceMock( + `${serverRef.serverBaseWsUrl}/inspector/device?device=device1&name=foo&app=bar`, autoCleanup.signal, ); - let debuggerSocketClosed = false; - debugger_.socket.once('close', () => { - debuggerSocketClosed = true; - }); try { - await until(() => expect(device1.connect).toBeCalled()); + device1.getPages.mockImplementation(() => [ + { + app: 'bar-app', + id: 'page1', + title: 'bar-title', + vm: 'bar-vm', + }, + ]); - device1.sendWrappedEvent('page1', { - id: 0, + let pageList: Array = []; + await until(async () => { + pageList = (await fetchJson( + `${serverRef.serverBaseUrl}/json`, + // $FlowIgnore[unclear-type] + ): any); + expect(pageList).toHaveLength(1); }); + const [{webSocketDebuggerUrl}] = pageList; + expect(webSocketDebuggerUrl).toBeDefined(); - await until(() => expect(debugger_.handle).toBeCalledWith({id: 0})); + const debugger_ = await createDebuggerMock( + webSocketDebuggerUrl, + autoCleanup.signal, + ); + let debuggerSocketClosed = false; + debugger_.socket.once('close', () => { + debuggerSocketClosed = true; + }); + try { + await until(() => expect(device1.connect).toBeCalled()); - device1.close(); + device1.sendWrappedEvent('page1', { + id: 0, + }); - await until(() => expect(debuggerSocketClosed).toBe(true)); + await until(() => expect(debugger_.handle).toBeCalledWith({id: 0})); + + device1.close(); + + await until(() => expect(debuggerSocketClosed).toBe(true)); + } finally { + debugger_.close(); + } } finally { - debugger_.close(); + device1.close(); } - } finally { - device1.close(); - } - }); -}); + }); + }, +); diff --git a/packages/dev-middleware/src/__tests__/InspectorProxyHttpApi-test.js b/packages/dev-middleware/src/__tests__/InspectorProxyHttpApi-test.js index 7828effb15a..125433757ed 100644 --- a/packages/dev-middleware/src/__tests__/InspectorProxyHttpApi-test.js +++ b/packages/dev-middleware/src/__tests__/InspectorProxyHttpApi-test.js @@ -9,6 +9,11 @@ * @oncall react_native */ +import type { + JsonPagesListResponse, + JsonVersionResponse, +} from '../inspector-proxy/types'; + import {fetchJson} from './FetchUtils'; import {createDeviceMock} from './InspectorDeviceUtils'; import {withAbortSignalForEachTest} from './ResourceUtils'; @@ -31,14 +36,18 @@ describe('inspector proxy HTTP API', () => { describe('/json/version endpoint', () => { test('returns version', async () => { - const json = await fetchJson(`${serverRef.serverBaseUrl}/json/version`); + const json = await fetchJson( + `${serverRef.serverBaseUrl}/json/version`, + ); expect(json).toMatchSnapshot(); }); }); describe.each(['/json', '/json/list'])('%s endpoint', endpoint => { test('empty on start', async () => { - const json = await fetchJson(`${serverRef.serverBaseUrl}${endpoint}`); + const json = await fetchJson( + `${serverRef.serverBaseUrl}${endpoint}`, + ); expect(json).toEqual([]); }); @@ -59,7 +68,7 @@ describe('inspector proxy HTTP API', () => { jest.advanceTimersByTime(PAGES_POLLING_DELAY); - const jsonBefore = await fetchJson( + const jsonBefore = await fetchJson( `${serverRef.serverBaseUrl}${endpoint}`, ); @@ -74,7 +83,7 @@ describe('inspector proxy HTTP API', () => { jest.advanceTimersByTime(PAGES_POLLING_DELAY); - const jsonAfter = await fetchJson( + const jsonAfter = await fetchJson( `${serverRef.serverBaseUrl}${endpoint}`, ); @@ -115,13 +124,13 @@ describe('inspector proxy HTTP API', () => { jest.advanceTimersByTime(PAGES_POLLING_DELAY); - const jsonBefore = await fetchJson( + const jsonBefore = await fetchJson( `${serverRef.serverBaseUrl}${endpoint}`, ); device1.close(); - const jsonAfter = await fetchJson( + const jsonAfter = await fetchJson( `${serverRef.serverBaseUrl}${endpoint}`, ); @@ -171,7 +180,9 @@ describe('inspector proxy HTTP API', () => { jest.advanceTimersByTime(10 * PAGES_POLLING_DELAY); try { - const json = await fetchJson(`${serverRef.serverBaseUrl}${endpoint}`); + const json = await fetchJson( + `${serverRef.serverBaseUrl}${endpoint}`, + ); expect(json).toEqual([ { description: 'bar-app', @@ -207,5 +218,55 @@ describe('inspector proxy HTTP API', () => { device2.close(); } }); + + describe('HTTP vs HTTPS', () => { + const secureServerRef = withServerForEachTest({ + logger: undefined, + projectRoot: '', + secure: true, + }); + + test('uses `wss` scheme and param if server is HTTPS', async () => { + const page = { + app: 'bar-app', + id: 'page1', + title: 'bar-title', + vm: 'bar-vm', + }; + + let deviceHttp, deviceHttps; + + try { + deviceHttp = await createDeviceMock( + `${serverRef.serverBaseWsUrl}/inspector/device?device=device1&name=foo&app=bar`, + autoCleanup.signal, + ); + deviceHttp.getPages.mockImplementation(() => [page]); + + deviceHttps = await createDeviceMock( + `${secureServerRef.serverBaseWsUrl}/inspector/device?device=device1&name=foo&app=bar`, + autoCleanup.signal, + ); + deviceHttps.getPages.mockImplementation(() => [page]); + + jest.advanceTimersByTime(PAGES_POLLING_DELAY); + + const [pageHttp] = await fetchJson( + `${serverRef.serverBaseUrl}${endpoint}`, + ); + const [pageHttps] = await fetchJson( + `${secureServerRef.serverBaseUrl}${endpoint}`, + ); + + expect(pageHttp.webSocketDebuggerUrl).toMatch(/^ws:\/\//); + expect(pageHttps.webSocketDebuggerUrl).toMatch(/^wss:\/\//); + expect(pageHttp.devtoolsFrontendUrl).toMatch(/[&?]ws=/); + expect(pageHttps.devtoolsFrontendUrl).toMatch(/[&?]wss=/); + } finally { + deviceHttp?.close(); + deviceHttps?.close(); + } + }); + }); }); }); diff --git a/packages/dev-middleware/src/__tests__/ServerUtils.js b/packages/dev-middleware/src/__tests__/ServerUtils.js index 6550a18ebf5..9050227d931 100644 --- a/packages/dev-middleware/src/__tests__/ServerUtils.js +++ b/packages/dev-middleware/src/__tests__/ServerUtils.js @@ -12,10 +12,15 @@ import {createDevMiddleware} from '../'; import connect from 'connect'; import http from 'http'; +import https from 'https'; +import * as selfsigned from 'selfsigned'; import url from 'url'; type CreateDevMiddlewareOptions = Parameters[0]; -type CreateServerOptions = Omit; +type CreateServerOptions = { + ...Omit, + secure?: boolean, +}; export function withServerForEachTest(options: CreateServerOptions): $ReadOnly<{ serverBaseUrl: string, @@ -35,11 +40,17 @@ export function withServerForEachTest(options: CreateServerOptions): $ReadOnly<{ ); }, }; - let server: http$Server; + let server: http$Server | https$Server; beforeEach(async () => { server = await createServer(options); - const serverBaseUrl = baseUrlForServer(server, 'http'); - const serverBaseWsUrl = baseUrlForServer(server, 'ws'); + const serverBaseUrl = baseUrlForServer( + server, + options.secure ?? false ? 'https' : 'http', + ); + const serverBaseWsUrl = baseUrlForServer( + server, + options.secure ?? false ? 'wss' : 'ws', + ); Object.defineProperty(ref, 'serverBaseUrl', {value: serverBaseUrl}); Object.defineProperty(ref, 'serverBaseWsUrl', {value: serverBaseWsUrl}); }); @@ -51,16 +62,30 @@ export function withServerForEachTest(options: CreateServerOptions): $ReadOnly<{ export async function createServer( options: CreateServerOptions, -): Promise { +): Promise { const app = connect(); - const httpServer = http.createServer(app); + const {secure = false, ...devMiddlewareOptions} = options; + let httpServer; + if (secure) { + const {cert, private: key} = selfsigned.generate( + [{name: 'commonName', value: 'localhost'}], + {days: 1}, + ); + httpServer = https.createServer( + {cert, key}, + // $FlowFixMe[incompatible-call] The types for `connect` and `https` are subtly incompatible as written. + app, + ); + } else { + httpServer = http.createServer(app); + } return new Promise((resolve, reject) => { httpServer.once('error', reject); httpServer.listen(() => { const {middleware, websocketEndpoints} = createDevMiddleware({ - ...options, - serverBaseUrl: baseUrlForServer(httpServer, 'http'), + ...devMiddlewareOptions, + serverBaseUrl: baseUrlForServer(httpServer, secure ? 'https' : 'http'), }); app.use(middleware); httpServer.on('upgrade', (request, socket, head) => { @@ -83,8 +108,12 @@ export async function createServer( }); } -export function baseUrlForServer(server: http$Server, scheme: string): string { +export function baseUrlForServer( + server: http$Server | https$Server, + scheme: string, +): string { const address = server.address(); - // Assumption: `server` is local and listening on `localhost`. + // Assumption: `server` is local and listening on `localhost`. We can't use + // the IP address because HTTPS requires a hostname. return `${scheme}://localhost:${address.port}`; } diff --git a/packages/dev-middleware/src/inspector-proxy/types.js b/packages/dev-middleware/src/inspector-proxy/types.js index 931a46b851a..2f805357524 100644 --- a/packages/dev-middleware/src/inspector-proxy/types.js +++ b/packages/dev-middleware/src/inspector-proxy/types.js @@ -138,5 +138,5 @@ export type JSONSerializable = | number | string | null - | Array - | {[string]: JSONSerializable}; + | $ReadOnlyArray + | {+[string]: JSONSerializable}; diff --git a/yarn.lock b/yarn.lock index ac36fd3baf1..e267e2ff80c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1366,6 +1366,11 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@fastify/busboy@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.0.0.tgz#f22824caff3ae506b18207bad4126dbc6ccdb6b8" + integrity sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ== + "@firebase/analytics-compat@0.1.13": version "0.1.13" resolved "https://registry.yarnpkg.com/@firebase/analytics-compat/-/analytics-compat-0.1.13.tgz#61e1d6f9e4d033c3ed9943d91530eb3e0f382f92" @@ -2745,6 +2750,13 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== +"@types/node-forge@^1.3.0": + version "1.3.8" + resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.8.tgz#044ad98354ff309a031a55a40ad122f3be1ac2bb" + integrity sha512-vGXshY9vim9CJjrpcS5raqSjEfKlJcWy2HNdgUasR66fAnVEYarrf1ULV4nfvpC1nZq/moA9qyqBcu83x+Jlrg== + dependencies: + "@types/node" "*" + "@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@^18.0.0": version "18.16.14" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.14.tgz#ab67bb907f1146afc6fedb9ce60ae8a99c989631" @@ -7417,6 +7429,11 @@ node-fetch@2.6.7, node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.7: dependencies: whatwg-url "^5.0.0" +node-forge@^1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -8474,6 +8491,14 @@ selenium-webdriver@4.1.2: tmp "^0.2.1" ws ">=7.4.6" +selfsigned@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0" + integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q== + dependencies: + "@types/node-forge" "^1.3.0" + node-forge "^1" + "semver@2 >=2.2.1 || 3.x || 4 || 5 || 7", semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8: version "7.5.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e" @@ -9198,6 +9223,13 @@ unbzip2-stream@1.4.3: buffer "^5.2.1" through "^2.3.8" +undici@^5.27.2: + version "5.27.2" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.27.2.tgz#a270c563aea5b46cc0df2550523638c95c5d4411" + integrity sha512-iS857PdOEy/y3wlM3yRp+6SNQQ6xU0mmZcwRSriqk+et/cwWAtwmIGf6WkoDN2EK/AMdCO/dfXzIwi+rFMrjjQ== + dependencies: + "@fastify/busboy" "^2.0.0" + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc"