diff --git a/package.json b/package.json index c3c1b292..3862fc51 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,6 @@ "express-basic-auth": "1.2.1", "fast-xml-parser": "4.5.1", "mime-types": "2.1.35", - "node-fetch": "2.7.0", "openpgp": "5.11.2", "pm2": "5.4.3", "range-parser": "^1.2.1", @@ -75,8 +74,7 @@ "@types/express": "5.0.0", "@types/mime-types": "2.1.4", "@types/node": "22.10.6", - "@types/node-fetch": "2.6.12", - "@types/range-parser": "^1.2.7", + "@types/range-parser": "1.2.7", "@vitest/coverage-istanbul": "2.1.8", "@vitest/spy": "2.1.8", "eslint": "9.17.0", diff --git a/src/commands/download-file.ts b/src/commands/download-file.ts index a4d06ba0..233e2083 100644 --- a/src/commands/download-file.ts +++ b/src/commands/download-file.ts @@ -79,12 +79,13 @@ export default class DownloadFile extends Command { user.bucket, user.mnemonic, driveFile.fileId, + driveFile.size, StreamUtils.writeStreamToWritableStream(fileWriteStream), undefined, { abortController: new AbortController(), progressCallback: (progress) => { - progressBar.update(progress); + progressBar.update(progress * 0.99); }, }, ); diff --git a/src/commands/upload-file.ts b/src/commands/upload-file.ts index 3f8a82b4..b63f86a4 100644 --- a/src/commands/upload-file.ts +++ b/src/commands/upload-file.ts @@ -74,14 +74,14 @@ export default class UploadFile extends Command { CLIUtils.done(); - const timer = CLIUtils.timer(); // 2. Upload file to the Network const fileStream = createReadStream(filePath); + const timer = CLIUtils.timer(); const progressBar = CLIUtils.progress({ format: 'Uploading file [{bar}] {percentage}%', linewrap: true, }); - progressBar.start(1, 0); + progressBar.start(100, 0); const [uploadPromise, abortable] = await networkFacade.uploadFromStream( user.bucket, user.mnemonic, @@ -89,7 +89,7 @@ export default class UploadFile extends Command { fileStream, { progressCallback: (progress) => { - progressBar.update(progress); + progressBar.update(progress * 0.99); }, }, ); @@ -100,7 +100,6 @@ export default class UploadFile extends Command { }); const uploadResult = await uploadPromise; - progressBar.stop(); // 3. Create the file in Drive const fileInfo = path.parse(filePath); @@ -115,6 +114,9 @@ export default class UploadFile extends Command { name: '', }); + progressBar.update(100); + progressBar.stop(); + const uploadTime = timer.stop(); this.log('\n'); // eslint-disable-next-line max-len diff --git a/src/services/crypto.service.ts b/src/services/crypto.service.ts index ad5829cb..b43dd6f7 100644 --- a/src/services/crypto.service.ts +++ b/src/services/crypto.service.ts @@ -166,11 +166,11 @@ export class CryptoService { return decryptedStream; } - public async getEncryptionTransform(key: Buffer, iv: Buffer): Promise { + public getEncryptionTransform = (key: Buffer, iv: Buffer): Transform => { const cipher = createCipheriv('aes-256-ctr', key, iv); - return cipher; - } + }; + /** * Generates the key and the iv by transforming a secret and a salt. * It will generate the same key and iv if the same secret and salt is used. diff --git a/src/services/network/download.service.ts b/src/services/network/download.service.ts index 1907e30a..028cc2a7 100644 --- a/src/services/network/download.service.ts +++ b/src/services/network/download.service.ts @@ -1,4 +1,5 @@ import axios from 'axios'; +import { DownloadProgressCallback } from '../../types/network.types'; export class DownloadService { static readonly instance = new DownloadService(); @@ -6,7 +7,7 @@ export class DownloadService { async downloadFile( url: string, options: { - progressCallback?: (progress: number) => void; + progressCallback?: DownloadProgressCallback; abortController?: AbortController; rangeHeader?: string; }, @@ -14,10 +15,8 @@ export class DownloadService { const response = await axios.get(url, { responseType: 'stream', onDownloadProgress(progressEvent) { - if (options.progressCallback && progressEvent.total) { - const reportedProgress = progressEvent.loaded / progressEvent.total; - - options.progressCallback(reportedProgress); + if (options.progressCallback && progressEvent.loaded) { + options.progressCallback(progressEvent.loaded); } }, headers: { diff --git a/src/services/network/network-facade.service.ts b/src/services/network/network-facade.service.ts index 339523f5..04b4bb10 100644 --- a/src/services/network/network-facade.service.ts +++ b/src/services/network/network-facade.service.ts @@ -10,13 +10,12 @@ import { import { Environment } from '@internxt/inxt-js'; import { randomBytes } from 'node:crypto'; import { Readable, Transform } from 'node:stream'; -import { DownloadOptions, UploadOptions, UploadProgressCallback } from '../../types/network.types'; +import { DownloadOptions, UploadOptions, UploadProgressCallback, DownloadProgressCallback } from '../../types/network.types'; import { CryptoService } from '../crypto.service'; import { UploadService } from './upload.service'; import { DownloadService } from './download.service'; import { ValidationService } from '../validation.service'; import { HashStream } from '../../utils/hash.utils'; -import { ProgressTransform } from '../../utils/stream.utils'; import { RangeOptions } from '../../utils/network.utils'; export class NetworkFacade { @@ -54,6 +53,7 @@ export class NetworkFacade { bucketId: string, mnemonic: string, fileId: string, + size: number, to: WritableStream, rangeOptions?: RangeOptions, options?: DownloadOptions, @@ -62,13 +62,10 @@ export class NetworkFacade { let fileStream: ReadableStream; const abortable = options?.abortController ?? new AbortController(); - const onProgress: UploadProgressCallback = (progress: number) => { + const onProgress: DownloadProgressCallback = (loadedBytes: number) => { if (!options?.progressCallback) return; - options.progressCallback(progress); - }; - - const onDownloadProgress = (progress: number) => { - onProgress(progress); + const reportedProgress = Math.round((loadedBytes / size) * 100); + options.progressCallback(reportedProgress); }; const decryptFile: DecryptFileFunction = async (_, key, iv) => { @@ -97,7 +94,7 @@ export class NetworkFacade { } const encryptedContentStream = await this.downloadService.downloadFile(downloadable.url, { - progressCallback: onDownloadProgress, + progressCallback: onProgress, abortController: options?.abortController, rangeHeader: rangeOptions?.range, }); @@ -142,38 +139,31 @@ export class NetworkFacade { const hashStream = new HashStream(); const abortable = options?.abortController ?? new AbortController(); let encryptionTransform: Transform; - const progressTransform = new ProgressTransform({ totalBytes: size }, (progress) => { - if (options?.progressCallback) { - options.progressCallback(progress * 0.95); - } - }); + let hash: Buffer; - const onProgress: UploadProgressCallback = (progress: number) => { + const onProgress: UploadProgressCallback = (loadedBytes: number) => { if (!options?.progressCallback) return; - options.progressCallback(progress); + const reportedProgress = Math.round((loadedBytes / size) * 100); + options.progressCallback(reportedProgress); }; const encryptFile: EncryptFileFunction = async (_, key, iv) => { - encryptionTransform = from - .pipe( - await this.cryptoService.getEncryptionTransform( - Buffer.from(key as ArrayBuffer), - Buffer.from(iv as ArrayBuffer), - ), - ) - .pipe(hashStream); + const encryptionCipher = this.cryptoService.getEncryptionTransform( + Buffer.from(key as ArrayBuffer), + Buffer.from(iv as ArrayBuffer), + ); + encryptionTransform = from.pipe(encryptionCipher).pipe(hashStream); }; const uploadFile: UploadFileFunction = async (url) => { - await this.uploadService.uploadFile(url, encryptionTransform.pipe(progressTransform), { + await this.uploadService.uploadFile(url, encryptionTransform, { abortController: abortable, - progressCallback: () => { - // No progress here, we are using the progressTransform - }, + progressCallback: onProgress, }); - - return hashStream.getHash().toString('hex'); + hash = hashStream.getHash(); + return hash.toString('hex'); }; + const uploadOperation = async () => { const uploadResult = await NetworkUpload.uploadFile( this.network, @@ -184,12 +174,10 @@ export class NetworkFacade { encryptFile, uploadFile, ); - const fileHash: Buffer = Buffer.from(''); - onProgress(1); return { fileId: uploadResult, - hash: fileHash, + hash: hash, }; }; diff --git a/src/services/network/upload.service.ts b/src/services/network/upload.service.ts index 836d39f0..f11a2a4e 100644 --- a/src/services/network/upload.service.ts +++ b/src/services/network/upload.service.ts @@ -1,20 +1,21 @@ import { Readable } from 'node:stream'; -import fetch from 'node-fetch'; -import { AbortSignal } from 'node-fetch/externals'; +import axios from 'axios'; import { UploadOptions } from '../../types/network.types'; export class UploadService { public static readonly instance: UploadService = new UploadService(); async uploadFile(url: string, from: Readable, options: UploadOptions): Promise<{ etag: string }> { - const response = await fetch(url, { - method: 'PUT', - body: from, - signal: options.abortController?.signal as AbortSignal, + const response = await axios.put(url, from, { + signal: options.abortController?.signal, + onUploadProgress: (progressEvent) => { + if (options.progressCallback && progressEvent.loaded) { + options.progressCallback(progressEvent.loaded); + } + }, }); - const etag = response.headers.get('etag'); - options.progressCallback(1); + const etag = response.headers['etag']; if (!etag) { throw new Error('Missing Etag in response when uploading file'); } diff --git a/src/types/network.types.ts b/src/types/network.types.ts index 7a668dcc..800fe09e 100644 --- a/src/types/network.types.ts +++ b/src/types/network.types.ts @@ -3,7 +3,8 @@ export interface NetworkCredentials { pass: string; } -export type UploadProgressCallback = (progress: number) => void; +export type DownloadProgressCallback = (downloadedBytes: number) => void; +export type UploadProgressCallback = (uploadedBytes: number) => void; export interface NetworkOperationBaseOptions { progressCallback: UploadProgressCallback; abortController?: AbortController; diff --git a/src/webdav/handlers/GET.handler.ts b/src/webdav/handlers/GET.handler.ts index 9b72dceb..ce85a6e0 100644 --- a/src/webdav/handlers/GET.handler.ts +++ b/src/webdav/handlers/GET.handler.ts @@ -43,15 +43,6 @@ export class GETRequestHandler implements WebDavMethodHandler { const { user } = await authService.getAuthDetails(); webdavLogger.info(`[GET] [${driveFile.uuid}] Network ready for download`); - const writable = new WritableStream({ - write(chunk) { - res.write(chunk); - }, - close() { - res.end(); - }, - }); - const range = req.headers['range']; const rangeOptions = NetworkUtils.parseRangeHeader({ range, @@ -66,10 +57,20 @@ export class GETRequestHandler implements WebDavMethodHandler { res.header('Content-Type', 'application/octet-stream'); res.header('Content-length', contentLength.toString()); + const writable = new WritableStream({ + write(chunk) { + res.write(chunk); + }, + close() { + res.end(); + }, + }); + const [executeDownload] = await networkFacade.downloadToStream( driveFile.bucket, user.mnemonic, driveFile.fileId, + contentLength, writable, rangeOptions, ); diff --git a/src/webdav/handlers/MKCOL.handler.ts b/src/webdav/handlers/MKCOL.handler.ts index d847299d..44578f94 100644 --- a/src/webdav/handlers/MKCOL.handler.ts +++ b/src/webdav/handlers/MKCOL.handler.ts @@ -7,7 +7,6 @@ import { webdavLogger } from '../../utils/logger.utils'; import { XMLUtils } from '../../utils/xml.utils'; import { AsyncUtils } from '../../utils/async.utils'; import { DriveFolderItem } from '../../types/drive.types'; -import { MethodNotAllowed } from '../../utils/errors.utils'; export class MKCOLRequestHandler implements WebDavMethodHandler { constructor( @@ -21,8 +20,6 @@ export class MKCOLRequestHandler implements WebDavMethodHandler { const { driveDatabaseManager, driveFolderService } = this.dependencies; const resource = await WebDavUtils.getRequestedResource(req); - if (resource.type === 'file') throw new MethodNotAllowed('Files cannot be created with MKCOL. Use PUT instead.'); - webdavLogger.info(`[MKCOL] Request received for ${resource.type} at ${resource.url}`); const parentResource = await WebDavUtils.getRequestedResource(resource.parentPath); @@ -34,7 +31,7 @@ export class MKCOLRequestHandler implements WebDavMethodHandler { })) as DriveFolderItem; const [createFolder] = driveFolderService.createFolder({ - plainName: resource.name, + plainName: resource.path.base, parentFolderUuid: parentFolderItem.uuid, }); diff --git a/src/webdav/webdav-server.ts b/src/webdav/webdav-server.ts index e26861d9..a141f83f 100644 --- a/src/webdav/webdav-server.ts +++ b/src/webdav/webdav-server.ts @@ -171,7 +171,8 @@ export class WebDavServer { server.listen(configs.port, () => { webdavLogger.info( - `Internxt WebDav server listening at ${configs.protocol}://${ConfigService.WEBDAV_LOCAL_URL}:${configs.port}`, + `Internxt ${SdkManager.getAppDetails().clientVersion} WebDav server ` + + `listening at ${configs.protocol}://${ConfigService.WEBDAV_LOCAL_URL}:${configs.port}`, ); }); }; diff --git a/test/services/network/download.service.test.ts b/test/services/network/download.service.test.ts index a577764c..8a74caf7 100644 --- a/test/services/network/download.service.test.ts +++ b/test/services/network/download.service.test.ts @@ -48,6 +48,6 @@ describe('Download Service', () => { await sut.downloadFile('https://example.com/file', options); - expect(options.progressCallback).toHaveBeenCalledWith(1); + expect(options.progressCallback).toHaveBeenCalledWith(100); }); }); diff --git a/test/services/network/network-facade.service.test.ts b/test/services/network/network-facade.service.test.ts index 7998bbc6..69fad07c 100644 --- a/test/services/network/network-facade.service.test.ts +++ b/test/services/network/network-facade.service.test.ts @@ -10,6 +10,8 @@ import { DownloadService } from '../../../src/services/network/download.service' import { Readable } from 'node:stream'; import axios from 'axios'; import { fail } from 'node:assert'; +import crypto from 'node:crypto'; +import { HashStream } from '../../../src/utils/hash.utils'; describe('Network Facade Service', () => { beforeEach(() => { @@ -77,6 +79,68 @@ describe('Network Facade Service', () => { expect(uploadResult.fileId).to.be.equal('uploaded_file_id'); }); + it('When a file is uploaded, then it should report progress', async () => { + const bucket = 'f1858bc9675f9e4f7ab29429'; + const networkMock = getNetworkMock(); + + const sut = new NetworkFacade( + networkMock, + UploadService.instance, + DownloadService.instance, + CryptoService.instance, + ); + const file = crypto.randomBytes(16).toString('hex'); + const readStream = new Readable({ + read() { + this.push(file); + this.push(null); + }, + }); + const options = { + progressCallback: vi.fn(), + abortController: new AbortController(), + }; + + vi.spyOn(HashStream.prototype, 'getHash').mockImplementation(() => Buffer.from('')); + + vi.spyOn(axios, 'put').mockImplementation((_, __, config) => { + config?.onUploadProgress?.({ + loaded: file.length, + total: file.length, + bytes: file.length, + lengthComputable: true, + }); + return Promise.resolve({ + data: readStream, + headers: { + etag: 'any-etag', + }, + }); + }); + + vi.spyOn(networkMock, 'startUpload').mockResolvedValue({ + uploads: [{ index: 0, url: 'any-url', uuid: 'any-uuid', urls: [] }], + }); + + vi.spyOn(networkMock, 'finishUpload') + // @ts-expect-error - We only mock the properties we need + .mockResolvedValue({ + id: 'any-id', + }); + + const [executeUpload] = await sut.uploadFromStream( + bucket, + 'animal fog wink trade december thumb sight cousin crunch plunge captain enforce letter creek text', + file.length, + readStream, + options, + ); + + await executeUpload; + + expect(options.progressCallback).toHaveBeenCalledWith(100); + }); + it('When a file is downloaded, should write it to a stream', async () => { const encryptedContent = Buffer.from('b6ccfa381c150f3a4b65245bffa4d84087', 'hex'); const bucket = 'cd8abd7e8b13081660b58dbe'; @@ -120,6 +184,7 @@ describe('Network Facade Service', () => { // eslint-disable-next-line max-len 'index course habit soon assist dragon tragic helmet salute stuff later twice consider grit pulse cement obvious trick sponsor stereo hello win royal more', 'f1858bc9675f9e4f7ab29429', + encryptedContent.length, writable, ); @@ -166,6 +231,7 @@ describe('Network Facade Service', () => { // eslint-disable-next-line max-len 'index course habit soon assist dragon tragic helmet salute stuff later twice consider grit pulse cement obvious trick sponsor stereo hello win royal more', 'f1858bc9675f9e4f7ab29429', + encryptedContent.length, writable, ); @@ -214,7 +280,12 @@ describe('Network Facade Service', () => { const options = { progressCallback: vi.fn() }; vi.spyOn(axios, 'get').mockImplementation((_, config) => { - config?.onDownloadProgress?.({ loaded: 100, total: 100, bytes: 100, lengthComputable: true }); + config?.onDownloadProgress?.({ + loaded: encryptedContent.length, + total: encryptedContent.length, + bytes: encryptedContent.length, + lengthComputable: true, + }); return Promise.resolve({ data: readableContent }); }); @@ -223,6 +294,7 @@ describe('Network Facade Service', () => { // eslint-disable-next-line max-len 'index course habit soon assist dragon tragic helmet salute stuff later twice consider grit pulse cement obvious trick sponsor stereo hello win royal more', 'f1858bc9675f9e4f7ab29429', + encryptedContent.length, writable, undefined, options, @@ -230,6 +302,6 @@ describe('Network Facade Service', () => { await executeDownload; - expect(options.progressCallback).toHaveBeenCalledWith(1); + expect(options.progressCallback).toHaveBeenCalledWith(100); }); }); diff --git a/test/services/network/upload.service.test.ts b/test/services/network/upload.service.test.ts index 3eac29bb..4999dfd7 100644 --- a/test/services/network/upload.service.test.ts +++ b/test/services/network/upload.service.test.ts @@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { UploadService } from '../../../src/services/network/upload.service'; import nock from 'nock'; import { Readable } from 'node:stream'; +import crypto from 'node:crypto'; describe('Upload Service', () => { const sut = UploadService.instance; @@ -12,9 +13,10 @@ describe('Upload Service', () => { it('When a file is uploaded and etag is missing, should throw an error', async () => { const url = 'https://example.com/upload'; + const file = crypto.randomBytes(16).toString('hex'); const data = new Readable({ read() { - this.push('test content'); + this.push(file); this.push(null); }, }); @@ -34,9 +36,10 @@ describe('Upload Service', () => { it('When a file is uploaded and etag is returned, the etag should be returned', async () => { const url = 'https://example.com/upload'; + const file = crypto.randomBytes(16).toString('hex'); const data = new Readable({ read() { - this.push('test content'); + this.push(file); this.push(null); }, }); @@ -55,9 +58,10 @@ describe('Upload Service', () => { it('When a file is uploaded, should update the progress', async () => { const url = 'https://example.com/upload'; + const file = crypto.randomBytes(16).toString('hex'); const data = new Readable({ read() { - this.push('test content'); + this.push(file); this.push(null); }, }); @@ -71,7 +75,7 @@ describe('Upload Service', () => { }); await sut.uploadFile(url, data, options); - expect(options.progressCallback).toHaveBeenCalledWith(1); + expect(options.progressCallback).toHaveBeenCalledWith(file.length); }); it('When a file is uploaded and the upload is aborted, should cancel the request', async () => { diff --git a/test/webdav/handlers/GET.handler.test.ts b/test/webdav/handlers/GET.handler.test.ts index 2be73414..145c928c 100644 --- a/test/webdav/handlers/GET.handler.test.ts +++ b/test/webdav/handlers/GET.handler.test.ts @@ -134,6 +134,7 @@ describe('GET request handler', () => { mockFile.bucket, mockAuthDetails.user.mnemonic, mockFile.fileId, + mockFile.size, expect.any(Object), undefined, ); @@ -205,6 +206,7 @@ describe('GET request handler', () => { mockFile.bucket, mockAuthDetails.user.mnemonic, mockFile.fileId, + mockSize - rangeStart, expect.any(Object), expectedRangeOptions, ); diff --git a/yarn.lock b/yarn.lock index 62d4d89f..d6a6680a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2372,14 +2372,6 @@ dependencies: "@types/node" "*" -"@types/node-fetch@2.6.12": - version "2.6.12" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.12.tgz#8ab5c3ef8330f13100a7479e2cd56d3386830a03" - integrity sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA== - dependencies: - "@types/node" "*" - form-data "^4.0.0" - "@types/node-forge@^1.3.0": version "1.3.11" resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" @@ -2406,7 +2398,7 @@ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.17.tgz#fc560f60946d0aeff2f914eb41679659d3310e1a" integrity sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ== -"@types/range-parser@*", "@types/range-parser@^1.2.7": +"@types/range-parser@*", "@types/range-parser@1.2.7": version "1.2.7" resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== @@ -5980,13 +5972,6 @@ node-addon-api@^7.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== -node-fetch@2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== - 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" @@ -7583,11 +7568,6 @@ touch@^3.1.0: resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.1.tgz#097a23d7b161476435e5c1344a95c0f75b4a5694" integrity sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA== -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - triple-beam@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984" @@ -7965,19 +7945,6 @@ web-streams-polyfill@~3.0.3: resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.0.3.tgz#f49e487eedeca47a207c1aee41ee5578f884b42f" integrity sha512-d2H/t0eqRNM4w2WvmTdoeIvzAUSpK7JmATB8Nr2lb7nQ9BTIJVjbQ/TRFVEh2gUH1HwclPdoPtfMoFfetXaZnA== -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"