aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--package.json3
-rw-r--r--server/lib/object-storage/shared/object-storage-helpers.ts89
-rw-r--r--server/tests/api/object-storage/videos.ts42
-rw-r--r--yarn.lock43
5 files changed, 92 insertions, 86 deletions
diff --git a/.gitignore b/.gitignore
index 5e06248f1..c6029ad65 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,6 +47,7 @@ yarn-error.log
47/*.zip 47/*.zip
48/*.tar.xz 48/*.tar.xz
49/*.asc 49/*.asc
50*.DS_Store
50/server/tools/import-mediacore.ts 51/server/tools/import-mediacore.ts
51/docker-volume/ 52/docker-volume/
52/init.mp4 53/init.mp4
diff --git a/package.json b/package.json
index 97438afdb..360bd781f 100644
--- a/package.json
+++ b/package.json
@@ -78,7 +78,8 @@
78 }, 78 },
79 "dependencies": { 79 "dependencies": {
80 "@aws-sdk/client-s3": "^3.23.0", 80 "@aws-sdk/client-s3": "^3.23.0",
81 "@babel/parser": "7.17.9", 81 "@aws-sdk/lib-storage": "^3.72.0",
82 "@babel/parser": "7.17.8",
82 "@peertube/feed": "^5.0.1", 83 "@peertube/feed": "^5.0.1",
83 "@peertube/http-signature": "^1.4.0", 84 "@peertube/http-signature": "^1.4.0",
84 "@uploadx/core": "^5.1.0", 85 "@uploadx/core": "^5.1.0",
diff --git a/server/lib/object-storage/shared/object-storage-helpers.ts b/server/lib/object-storage/shared/object-storage-helpers.ts
index ecb82856e..a2de92532 100644
--- a/server/lib/object-storage/shared/object-storage-helpers.ts
+++ b/server/lib/object-storage/shared/object-storage-helpers.ts
@@ -1,19 +1,14 @@
1import { close, createReadStream, createWriteStream, ensureDir, open, ReadStream, stat } from 'fs-extra' 1import { createReadStream, createWriteStream, ensureDir, ReadStream, stat } from 'fs-extra'
2import { min } from 'lodash'
3import { dirname } from 'path' 2import { dirname } from 'path'
4import { Readable } from 'stream' 3import { Readable } from 'stream'
5import { 4import {
6 CompletedPart,
7 CompleteMultipartUploadCommand,
8 CreateMultipartUploadCommand,
9 CreateMultipartUploadCommandInput,
10 DeleteObjectCommand, 5 DeleteObjectCommand,
11 GetObjectCommand, 6 GetObjectCommand,
12 ListObjectsV2Command, 7 ListObjectsV2Command,
13 PutObjectCommand, 8 PutObjectCommand,
14 PutObjectCommandInput, 9 PutObjectCommandInput
15 UploadPartCommand
16} from '@aws-sdk/client-s3' 10} from '@aws-sdk/client-s3'
11import { Upload } from '@aws-sdk/lib-storage'
17import { pipelinePromise } from '@server/helpers/core-utils' 12import { pipelinePromise } from '@server/helpers/core-utils'
18import { isArray } from '@server/helpers/custom-validators/misc' 13import { isArray } from '@server/helpers/custom-validators/misc'
19import { logger } from '@server/helpers/logger' 14import { logger } from '@server/helpers/logger'
@@ -37,13 +32,12 @@ async function storeObject (options: {
37 logger.debug('Uploading file %s to %s%s in bucket %s', inputPath, bucketInfo.PREFIX, objectStorageKey, bucketInfo.BUCKET_NAME, lTags()) 32 logger.debug('Uploading file %s to %s%s in bucket %s', inputPath, bucketInfo.PREFIX, objectStorageKey, bucketInfo.BUCKET_NAME, lTags())
38 33
39 const stats = await stat(inputPath) 34 const stats = await stat(inputPath)
35 const fileStream = createReadStream(inputPath)
40 36
41 // If bigger than max allowed size we do a multipart upload
42 if (stats.size > CONFIG.OBJECT_STORAGE.MAX_UPLOAD_PART) { 37 if (stats.size > CONFIG.OBJECT_STORAGE.MAX_UPLOAD_PART) {
43 return multiPartUpload({ inputPath, objectStorageKey, bucketInfo }) 38 return multiPartUpload({ content: fileStream, objectStorageKey, bucketInfo })
44 } 39 }
45 40
46 const fileStream = createReadStream(inputPath)
47 return objectStoragePut({ objectStorageKey, content: fileStream, bucketInfo }) 41 return objectStoragePut({ objectStorageKey, content: fileStream, bucketInfo })
48} 42}
49 43
@@ -163,18 +157,14 @@ async function objectStoragePut (options: {
163} 157}
164 158
165async function multiPartUpload (options: { 159async function multiPartUpload (options: {
166 inputPath: string 160 content: ReadStream
167 objectStorageKey: string 161 objectStorageKey: string
168 bucketInfo: BucketInfo 162 bucketInfo: BucketInfo
169}) { 163}) {
170 const { objectStorageKey, inputPath, bucketInfo } = options 164 const { content, objectStorageKey, bucketInfo } = options
171 165
172 const key = buildKey(objectStorageKey, bucketInfo) 166 const input: PutObjectCommandInput = {
173 const s3Client = getClient() 167 Body: content,
174
175 const statResult = await stat(inputPath)
176
177 const input: CreateMultipartUploadCommandInput = {
178 Bucket: bucketInfo.BUCKET_NAME, 168 Bucket: bucketInfo.BUCKET_NAME,
179 Key: buildKey(objectStorageKey, bucketInfo) 169 Key: buildKey(objectStorageKey, bucketInfo)
180 } 170 }
@@ -183,60 +173,19 @@ async function multiPartUpload (options: {
183 input.ACL = CONFIG.OBJECT_STORAGE.UPLOAD_ACL 173 input.ACL = CONFIG.OBJECT_STORAGE.UPLOAD_ACL
184 } 174 }
185 175
186 const createMultipartCommand = new CreateMultipartUploadCommand(input) 176 const parallelUploads3 = new Upload({
187 const createResponse = await s3Client.send(createMultipartCommand) 177 client: getClient(),
188 178 queueSize: 4,
189 const fd = await open(inputPath, 'r') 179 partSize: CONFIG.OBJECT_STORAGE.MAX_UPLOAD_PART,
190 let partNumber = 1 180 leavePartsOnError: false,
191 const parts: CompletedPart[] = [] 181 params: input
192 const partSize = CONFIG.OBJECT_STORAGE.MAX_UPLOAD_PART
193
194 for (let start = 0; start < statResult.size; start += partSize) {
195 logger.debug(
196 'Uploading part %d of file to %s%s in bucket %s',
197 partNumber, bucketInfo.PREFIX, objectStorageKey, bucketInfo.BUCKET_NAME, lTags()
198 )
199
200 // FIXME: Remove when https://github.com/aws/aws-sdk-js-v3/pull/2637 is released
201 // The s3 sdk needs to know the length of the http body beforehand, but doesn't support
202 // streams with start and end set, so it just tries to stat the file in stream.path.
203 // This fails for us because we only want to send part of the file. The stream type
204 // is modified so we can set the byteLength here, which s3 detects because array buffers
205 // have this field set
206 const stream: ReadStream & { byteLength: number } =
207 createReadStream(
208 inputPath,
209 { fd, autoClose: false, start, end: (start + partSize) - 1 }
210 ) as ReadStream & { byteLength: number }
211
212 // Calculate if the part size is more than what's left over, and in that case use left over bytes for byteLength
213 stream.byteLength = min([ statResult.size - start, partSize ])
214
215 const uploadPartCommand = new UploadPartCommand({
216 Bucket: bucketInfo.BUCKET_NAME,
217 Key: key,
218 UploadId: createResponse.UploadId,
219 PartNumber: partNumber,
220 Body: stream
221 })
222 const uploadResponse = await s3Client.send(uploadPartCommand)
223
224 parts.push({ ETag: uploadResponse.ETag, PartNumber: partNumber })
225 partNumber += 1
226 }
227 await close(fd)
228
229 const completeUploadCommand = new CompleteMultipartUploadCommand({
230 Bucket: bucketInfo.BUCKET_NAME,
231 Key: key,
232 UploadId: createResponse.UploadId,
233 MultipartUpload: { Parts: parts }
234 }) 182 })
235 await s3Client.send(completeUploadCommand) 183
184 await parallelUploads3.done()
236 185
237 logger.debug( 186 logger.debug(
238 'Completed %s%s in bucket %s in %d parts', 187 'Completed %s%s in bucket %s',
239 bucketInfo.PREFIX, objectStorageKey, bucketInfo.BUCKET_NAME, partNumber - 1, lTags() 188 bucketInfo.PREFIX, objectStorageKey, bucketInfo.BUCKET_NAME, lTags()
240 ) 189 )
241 190
242 return getPrivateUrl(bucketInfo, objectStorageKey) 191 return getPrivateUrl(bucketInfo, objectStorageKey)
diff --git a/server/tests/api/object-storage/videos.ts b/server/tests/api/object-storage/videos.ts
index 498efcb17..22ad06305 100644
--- a/server/tests/api/object-storage/videos.ts
+++ b/server/tests/api/object-storage/videos.ts
@@ -1,9 +1,17 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import bytes from 'bytes'
4import * as chai from 'chai' 5import * as chai from 'chai'
6import { stat } from 'fs-extra'
5import { merge } from 'lodash' 7import { merge } from 'lodash'
6import { checkTmpIsEmpty, expectLogDoesNotContain, expectStartWith, MockObjectStorage } from '@server/tests/shared' 8import {
9 checkTmpIsEmpty,
10 expectLogDoesNotContain,
11 expectStartWith,
12 generateHighBitrateVideo,
13 MockObjectStorage
14} from '@server/tests/shared'
7import { areObjectStorageTestsDisabled } from '@shared/core-utils' 15import { areObjectStorageTestsDisabled } from '@shared/core-utils'
8import { HttpStatusCode, VideoDetails } from '@shared/models' 16import { HttpStatusCode, VideoDetails } from '@shared/models'
9import { 17import {
@@ -107,6 +115,10 @@ async function checkFiles (options: {
107} 115}
108 116
109function runTestSuite (options: { 117function runTestSuite (options: {
118 fixture?: string
119
120 maxUploadPart?: string
121
110 playlistBucket: string 122 playlistBucket: string
111 playlistPrefix?: string 123 playlistPrefix?: string
112 124
@@ -114,10 +126,9 @@ function runTestSuite (options: {
114 webtorrentPrefix?: string 126 webtorrentPrefix?: string
115 127
116 useMockBaseUrl?: boolean 128 useMockBaseUrl?: boolean
117
118 maxUploadPart?: string
119}) { 129}) {
120 const mockObjectStorage = new MockObjectStorage() 130 const mockObjectStorage = new MockObjectStorage()
131 const { fixture } = options
121 let baseMockUrl: string 132 let baseMockUrl: string
122 133
123 let servers: PeerTubeServer[] 134 let servers: PeerTubeServer[]
@@ -144,7 +155,7 @@ function runTestSuite (options: {
144 155
145 credentials: ObjectStorageCommand.getCredentialsConfig(), 156 credentials: ObjectStorageCommand.getCredentialsConfig(),
146 157
147 max_upload_part: options.maxUploadPart || '2MB', 158 max_upload_part: options.maxUploadPart || '5MB',
148 159
149 streaming_playlists: { 160 streaming_playlists: {
150 bucket_name: options.playlistBucket, 161 bucket_name: options.playlistBucket,
@@ -181,7 +192,7 @@ function runTestSuite (options: {
181 it('Should upload a video and move it to the object storage without transcoding', async function () { 192 it('Should upload a video and move it to the object storage without transcoding', async function () {
182 this.timeout(40000) 193 this.timeout(40000)
183 194
184 const { uuid } = await servers[0].videos.quickUpload({ name: 'video 1' }) 195 const { uuid } = await servers[0].videos.quickUpload({ name: 'video 1', fixture })
185 uuidsToDelete.push(uuid) 196 uuidsToDelete.push(uuid)
186 197
187 await waitJobs(servers) 198 await waitJobs(servers)
@@ -197,7 +208,7 @@ function runTestSuite (options: {
197 it('Should upload a video and move it to the object storage with transcoding', async function () { 208 it('Should upload a video and move it to the object storage with transcoding', async function () {
198 this.timeout(120000) 209 this.timeout(120000)
199 210
200 const { uuid } = await servers[1].videos.quickUpload({ name: 'video 2' }) 211 const { uuid } = await servers[1].videos.quickUpload({ name: 'video 2', fixture })
201 uuidsToDelete.push(uuid) 212 uuidsToDelete.push(uuid)
202 213
203 await waitJobs(servers) 214 await waitJobs(servers)
@@ -390,12 +401,25 @@ describe('Object storage for videos', function () {
390 }) 401 })
391 }) 402 })
392 403
393 describe('Test object storage with small upload part', function () { 404 describe('Test object storage with file bigger than upload part', function () {
405 let fixture: string
406 const maxUploadPart = '5MB'
407
408 before(async function () {
409 fixture = await generateHighBitrateVideo()
410
411 const { size } = await stat(fixture)
412
413 if (bytes.parse(maxUploadPart) > size) {
414 throw Error(`Fixture file is too small (${size}) to make sense for this test.`)
415 }
416 })
417
394 runTestSuite({ 418 runTestSuite({
419 maxUploadPart,
395 playlistBucket: 'streaming-playlists', 420 playlistBucket: 'streaming-playlists',
396 webtorrentBucket: 'videos', 421 webtorrentBucket: 'videos',
397 422 fixture
398 maxUploadPart: '5KB'
399 }) 423 })
400 }) 424 })
401}) 425})
diff --git a/yarn.lock b/yarn.lock
index a2e5def80..104f4c241 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -484,6 +484,16 @@
484 dependencies: 484 dependencies:
485 tslib "^2.3.1" 485 tslib "^2.3.1"
486 486
487"@aws-sdk/lib-storage@^3.72.0":
488 version "3.72.0"
489 resolved "https://registry.yarnpkg.com/@aws-sdk/lib-storage/-/lib-storage-3.72.0.tgz#035c577e306d6472aa5cb15220936262cb394763"
490 integrity sha512-z2L//IMN9fkXMhFyC0F9SXTH0oHA7zsOsLOyQS2hqKXAE3TGTK6d0hj6vmut4RH0wGzXOQ9zrh0DexAVdv29pA==
491 dependencies:
492 buffer "5.6.0"
493 events "3.3.0"
494 stream-browserify "3.0.0"
495 tslib "^2.3.1"
496
487"@aws-sdk/md5-js@3.58.0": 497"@aws-sdk/md5-js@3.58.0":
488 version "3.58.0" 498 version "3.58.0"
489 resolved "https://registry.yarnpkg.com/@aws-sdk/md5-js/-/md5-js-3.58.0.tgz#a7ecf5cc8a81ce247fd620f8c981802d0427737f" 499 resolved "https://registry.yarnpkg.com/@aws-sdk/md5-js/-/md5-js-3.58.0.tgz#a7ecf5cc8a81ce247fd620f8c981802d0427737f"
@@ -989,7 +999,12 @@
989 resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.4.tgz#d5f92f57cf2c74ffe9b37981c0e72fee7311372e" 999 resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.4.tgz#d5f92f57cf2c74ffe9b37981c0e72fee7311372e"
990 integrity sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng== 1000 integrity sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==
991 1001
992"@babel/parser@7.17.9", "@babel/parser@^7.16.4", "@babel/parser@^7.16.7", "@babel/parser@^7.17.9", "@babel/parser@^7.6.0", "@babel/parser@^7.9.6": 1002"@babel/parser@7.17.8":
1003 version "7.17.8"
1004 resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.8.tgz#2817fb9d885dd8132ea0f8eb615a6388cca1c240"
1005 integrity sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ==
1006
1007"@babel/parser@^7.16.4", "@babel/parser@^7.16.7", "@babel/parser@^7.17.9", "@babel/parser@^7.6.0", "@babel/parser@^7.9.6":
993 version "7.17.9" 1008 version "7.17.9"
994 resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.9.tgz#9c94189a6062f0291418ca021077983058e171ef" 1009 resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.9.tgz#9c94189a6062f0291418ca021077983058e171ef"
995 integrity sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg== 1010 integrity sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg==
@@ -2555,7 +2570,7 @@ base32.js@0.1.0:
2555 resolved "https://registry.yarnpkg.com/base32.js/-/base32.js-0.1.0.tgz#b582dec693c2f11e893cf064ee6ac5b6131a2202" 2570 resolved "https://registry.yarnpkg.com/base32.js/-/base32.js-0.1.0.tgz#b582dec693c2f11e893cf064ee6ac5b6131a2202"
2556 integrity sha1-tYLexpPC8R6JPPBk7mrFthMaIgI= 2571 integrity sha1-tYLexpPC8R6JPPBk7mrFthMaIgI=
2557 2572
2558base64-js@^1.2.0, base64-js@^1.3.1: 2573base64-js@^1.0.2, base64-js@^1.2.0, base64-js@^1.3.1:
2559 version "1.5.1" 2574 version "1.5.1"
2560 resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" 2575 resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
2561 integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== 2576 integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
@@ -2818,6 +2833,14 @@ buffer-writer@2.0.0:
2818 resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" 2833 resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04"
2819 integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== 2834 integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==
2820 2835
2836buffer@5.6.0:
2837 version "5.6.0"
2838 resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786"
2839 integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==
2840 dependencies:
2841 base64-js "^1.0.2"
2842 ieee754 "^1.1.4"
2843
2821buffer@^5.2.0: 2844buffer@^5.2.0:
2822 version "5.7.1" 2845 version "5.7.1"
2823 resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" 2846 resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
@@ -4228,7 +4251,7 @@ event-target-shim@^5.0.0:
4228 resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" 4251 resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
4229 integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== 4252 integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
4230 4253
4231events@^3.3.0: 4254events@3.3.0, events@^3.3.0:
4232 version "3.3.0" 4255 version "3.3.0"
4233 resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" 4256 resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
4234 integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== 4257 integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
@@ -5006,7 +5029,7 @@ iconv-lite@0.6.3:
5006 dependencies: 5029 dependencies:
5007 safer-buffer ">= 2.1.2 < 3.0.0" 5030 safer-buffer ">= 2.1.2 < 3.0.0"
5008 5031
5009ieee754@^1.1.13, ieee754@^1.2.1: 5032ieee754@^1.1.13, ieee754@^1.1.4, ieee754@^1.2.1:
5010 version "1.2.1" 5033 version "1.2.1"
5011 resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" 5034 resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
5012 integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== 5035 integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
@@ -5066,7 +5089,7 @@ inflight@^1.0.4:
5066 once "^1.3.0" 5089 once "^1.3.0"
5067 wrappy "1" 5090 wrappy "1"
5068 5091
5069inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: 5092inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4:
5070 version "2.0.4" 5093 version "2.0.4"
5071 resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 5094 resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
5072 integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 5095 integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -7353,7 +7376,7 @@ readable-stream@^2.0.0, readable-stream@^2.2.2, readable-stream@~2.3.6:
7353 string_decoder "~1.1.1" 7376 string_decoder "~1.1.1"
7354 util-deprecate "~1.0.1" 7377 util-deprecate "~1.0.1"
7355 7378
7356readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.4.0, readable-stream@^3.6.0: 7379readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0:
7357 version "3.6.0" 7380 version "3.6.0"
7358 resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" 7381 resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
7359 integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== 7382 integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
@@ -8017,6 +8040,14 @@ statuses@1.5.0, "statuses@>= 1.5.0 < 2", statuses@~1.5.0:
8017 resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" 8040 resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
8018 integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= 8041 integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
8019 8042
8043stream-browserify@3.0.0:
8044 version "3.0.0"
8045 resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f"
8046 integrity sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==
8047 dependencies:
8048 inherits "~2.0.4"
8049 readable-stream "^3.5.0"
8050
8020stream-combiner@~0.0.4: 8051stream-combiner@~0.0.4:
8021 version "0.0.4" 8052 version "0.0.4"
8022 resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14" 8053 resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14"