From e0bbe2e84dba35dde037ec67fafcca81c2b6b9db Mon Sep 17 00:00:00 2001 From: nd0ut Date: Wed, 28 Dec 2022 19:43:47 +0400 Subject: [PATCH 01/16] refactor(upload-client): fix variable shadowing --- packages/upload-client/src/tools/buildFormData.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/upload-client/src/tools/buildFormData.ts b/packages/upload-client/src/tools/buildFormData.ts index 2376ff97a..d15bbc311 100644 --- a/packages/upload-client/src/tools/buildFormData.ts +++ b/packages/upload-client/src/tools/buildFormData.ts @@ -90,9 +90,9 @@ function buildFormData(options: FormDataOptions): FormData | NodeFormData { const paramsList = getFormDataParams(options) for (const params of paramsList) { - const [key, value, ...options] = params + const [key, value, ...rest] = params // node form-data has another signature for append - formData.append(key, value as Blob, ...(options as [string])) + formData.append(key, value as Blob, ...(rest as [string])) } return formData From fe6360739f1ffb67f1f163b33ed3a594c55f6ac3 Mon Sep 17 00:00:00 2001 From: nd0ut Date: Thu, 29 Dec 2022 13:08:41 +0400 Subject: [PATCH 02/16] fix(upload-client): ensure that file name and type passed to the FormData --- packages/upload-client/src/api/base.ts | 11 ++++++++--- packages/upload-client/src/api/multipartStart.ts | 4 ++-- packages/upload-client/src/tools/buildFormData.ts | 6 +++--- .../src/tools/getFormData.react-native.ts | 12 ++++-------- packages/upload-client/src/tools/types.ts | 5 +++-- .../upload-client/src/uploadFile/uploadMultipart.ts | 9 ++++++--- 6 files changed, 26 insertions(+), 21 deletions(-) diff --git a/packages/upload-client/src/api/base.ts b/packages/upload-client/src/api/base.ts index 8e3745c3b..0b3d96095 100644 --- a/packages/upload-client/src/api/base.ts +++ b/packages/upload-client/src/api/base.ts @@ -1,7 +1,11 @@ import request from '../request/request.node' import buildFormData from '../tools/buildFormData' import getUrl from '../tools/getUrl' -import { defaultSettings, defaultFilename } from '../defaultSettings' +import { + defaultSettings, + defaultFilename, + defaultContentType +} from '../defaultSettings' import { getUserAgent } from '../tools/getUserAgent' import { camelizeKeys, CustomUserAgent } from '@uploadcare/api-client-utils' import { UploadClientError } from '../tools/errors' @@ -77,8 +81,9 @@ export default function base( data: buildFormData({ file: { data: file, - name: fileName ?? (file as File).name ?? defaultFilename, - contentType + name: fileName || (file as File)?.name || defaultFilename, + contentType: + contentType || (file as File)?.type || defaultContentType }, UPLOADCARE_PUB_KEY: publicKey, UPLOADCARE_STORE: getStoreValue(store), diff --git a/packages/upload-client/src/api/multipartStart.ts b/packages/upload-client/src/api/multipartStart.ts index 06f9e7fbf..87a9c6bc6 100644 --- a/packages/upload-client/src/api/multipartStart.ts +++ b/packages/upload-client/src/api/multipartStart.ts @@ -75,9 +75,9 @@ export default function multipartStart( 'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent }) }, data: buildFormData({ - filename: fileName ?? defaultFilename, + filename: fileName || defaultFilename, size: size, - content_type: contentType ?? defaultContentType, + content_type: contentType || defaultContentType, part_size: multipartChunkSize, UPLOADCARE_STORE: getStoreValue(store), UPLOADCARE_PUB_KEY: publicKey, diff --git a/packages/upload-client/src/tools/buildFormData.ts b/packages/upload-client/src/tools/buildFormData.ts index d15bbc311..9446b73e3 100644 --- a/packages/upload-client/src/tools/buildFormData.ts +++ b/packages/upload-client/src/tools/buildFormData.ts @@ -16,8 +16,8 @@ type SimpleType = string | number | undefined type ObjectType = KeyValue interface FileOptions { - name?: string - contentType?: string + name: string + contentType: string } interface FileType extends FileOptions { @@ -63,7 +63,7 @@ function collectParams( ): void { if (isFileValue(inputValue)) { const { name, contentType }: FileOptions = inputValue - const file = transformFile(inputValue.data, name) as Blob // lgtm [js/superfluous-trailing-arguments] + const file = transformFile(inputValue.data, name, contentType) as Blob // lgtm [js/superfluous-trailing-arguments] const options = getFileOptions({ name, contentType }) params.push([inputKey, file, ...options]) } else if (isObjectValue(inputValue)) { diff --git a/packages/upload-client/src/tools/getFormData.react-native.ts b/packages/upload-client/src/tools/getFormData.react-native.ts index 488e3a354..115910193 100644 --- a/packages/upload-client/src/tools/getFormData.react-native.ts +++ b/packages/upload-client/src/tools/getFormData.react-native.ts @@ -5,19 +5,15 @@ import { ReactNativeAsset } from './types' -export const getFileOptions: GetFormDataFileAppendOptions = ({ name }) => - name ? [name] : [] +export const getFileOptions: GetFormDataFileAppendOptions = () => [] export const transformFile: FileTransformer = ( file: BrowserFile | NodeFile, - name?: string + name: string, + contentType: string ): ReactNativeAsset => { - if (!file) { - return file - } const uri = URL.createObjectURL(file as BrowserFile) - const type = (file as BrowserFile).type - return { uri, name, type } + return { uri, name, type: contentType } } export default (): FormData => new FormData() diff --git a/packages/upload-client/src/tools/types.ts b/packages/upload-client/src/tools/types.ts index 9afd47b84..e8c3635fb 100644 --- a/packages/upload-client/src/tools/types.ts +++ b/packages/upload-client/src/tools/types.ts @@ -1,10 +1,11 @@ import { BrowserFile, NodeFile } from '../request/types' -export type ReactNativeAsset = { name?: string; type?: string; uri: string } +export type ReactNativeAsset = { type: string; uri: string; name?: string } export type FileTransformer = ( file: NodeFile | BrowserFile, - name?: string + name: string, + contentType: string ) => NodeFile | BrowserFile | ReactNativeAsset export type GetFormDataFileAppendOptions = (options: { diff --git a/packages/upload-client/src/uploadFile/uploadMultipart.ts b/packages/upload-client/src/uploadFile/uploadMultipart.ts index bfce18d45..8c48af5cb 100644 --- a/packages/upload-client/src/uploadFile/uploadMultipart.ts +++ b/packages/upload-client/src/uploadFile/uploadMultipart.ts @@ -1,4 +1,7 @@ -import defaultSettings from '../defaultSettings' +import defaultSettings, { + defaultContentType, + defaultFilename +} from '../defaultSettings' import { prepareChunks } from './prepareChunks.node' import multipartStart from '../api/multipartStart' import multipartUpload, { @@ -122,8 +125,8 @@ const uploadMultipart = ( return multipartStart(size, { publicKey, - contentType, - fileName: fileName ?? (file as File).name, + contentType: contentType || (file as File).type || defaultContentType, + fileName: fileName || (file as File).name || defaultFilename, baseURL, secureSignature, secureExpire, From ac0393c98a5ba8c9864d67c35f2c3bcfdf18dabe Mon Sep 17 00:00:00 2001 From: nd0ut Date: Thu, 29 Dec 2022 13:18:49 +0400 Subject: [PATCH 03/16] refactor(upload-client): move file types to the root --- packages/upload-client/src/UploadClient.ts | 3 +-- packages/upload-client/src/api/base.ts | 3 ++- packages/upload-client/src/api/multipartUpload.ts | 2 +- packages/upload-client/src/index.ts | 2 +- packages/upload-client/src/request/types.ts | 4 +--- packages/upload-client/src/tools/buildFormData.ts | 2 +- packages/upload-client/src/tools/getFormData.react-native.ts | 2 +- packages/upload-client/src/tools/isMultipart.ts | 2 +- packages/upload-client/src/tools/types.ts | 2 +- packages/upload-client/src/types.ts | 3 +++ packages/upload-client/src/uploadFile/index.ts | 2 +- packages/upload-client/src/uploadFile/types.ts | 2 +- packages/upload-client/src/uploadFile/uploadDirect.ts | 2 +- packages/upload-client/src/uploadFile/uploadMultipart.ts | 2 +- packages/upload-client/src/uploadFileGroup/index.ts | 2 +- packages/upload-client/src/uploadFileGroup/types.ts | 2 +- 16 files changed, 19 insertions(+), 18 deletions(-) diff --git a/packages/upload-client/src/UploadClient.ts b/packages/upload-client/src/UploadClient.ts index 0d1d6a601..964ebca3b 100644 --- a/packages/upload-client/src/UploadClient.ts +++ b/packages/upload-client/src/UploadClient.ts @@ -25,8 +25,7 @@ import { UploadcareGroup } from './tools/UploadcareGroup' import { FileFromOptions, uploadFile } from './uploadFile' import { FileInfo, GroupId, GroupInfo, Token, Url, Uuid } from './api/types' -import { BrowserFile, NodeFile } from './request/types' -import { Settings } from './types' +import { Settings, BrowserFile, NodeFile } from './types' import uploadFileGroup, { GroupFromOptions } from './uploadFileGroup' /** diff --git a/packages/upload-client/src/api/base.ts b/packages/upload-client/src/api/base.ts index 0b3d96095..0b4dd7525 100644 --- a/packages/upload-client/src/api/base.ts +++ b/packages/upload-client/src/api/base.ts @@ -13,7 +13,8 @@ import { retryIfFailed } from '../tools/retryIfFailed' /* Types */ import { Uuid, ProgressCallback, Metadata } from './types' -import { FailedResponse, NodeFile, BrowserFile } from '../request/types' +import { FailedResponse } from '../request/types' +import { NodeFile, BrowserFile } from '../types' import { getStoreValue } from '../tools/getStoreValue' export type BaseResponse = { diff --git a/packages/upload-client/src/api/multipartUpload.ts b/packages/upload-client/src/api/multipartUpload.ts index 0f23844b3..589fcfbd0 100644 --- a/packages/upload-client/src/api/multipartUpload.ts +++ b/packages/upload-client/src/api/multipartUpload.ts @@ -3,7 +3,7 @@ import { MultipartPart } from './multipartStart' import request from '../request/request.node' import { ComputableProgressInfo, ProgressCallback } from './types' -import { NodeFile, BrowserFile } from '../request/types' +import { NodeFile, BrowserFile } from '../types' import { retryIfFailed } from '../tools/retryIfFailed' import defaultSettings from '../defaultSettings' diff --git a/packages/upload-client/src/index.ts b/packages/upload-client/src/index.ts index 0b2e829f8..bc74affe6 100644 --- a/packages/upload-client/src/index.ts +++ b/packages/upload-client/src/index.ts @@ -32,7 +32,7 @@ export { UploadcareFile } from './tools/UploadcareFile' export { UploadcareGroup } from './tools/UploadcareGroup' export { UploadClientError } from './tools/errors' export { Settings } from './types' -export { NodeFile, BrowserFile } from './request/types' +export { NodeFile, BrowserFile } from './types' export { BaseOptions, BaseResponse } from './api/base' export { FileInfo, GroupId, GroupInfo, Token, Url, Uuid } from './api/types' export { InfoOptions } from './api/info' diff --git a/packages/upload-client/src/request/types.ts b/packages/upload-client/src/request/types.ts index 52bd248d4..b8a6f1ec9 100644 --- a/packages/upload-client/src/request/types.ts +++ b/packages/upload-client/src/request/types.ts @@ -4,6 +4,7 @@ import { ProgressCallback, UnknownProgressInfo } from '../api/types' +import { BrowserFile, NodeFile } from '../types' export type Headers = { [key: string]: string | string[] | undefined @@ -41,6 +42,3 @@ export type FailedResponse = { errorCode: string } } - -export type BrowserFile = Blob | File -export type NodeFile = Buffer // | NodeJS.ReadableStream diff --git a/packages/upload-client/src/tools/buildFormData.ts b/packages/upload-client/src/tools/buildFormData.ts index 9446b73e3..386a5a3c4 100644 --- a/packages/upload-client/src/tools/buildFormData.ts +++ b/packages/upload-client/src/tools/buildFormData.ts @@ -1,7 +1,7 @@ import getFormData, { getFileOptions, transformFile } from './getFormData.node' import NodeFormData from 'form-data' -import { BrowserFile, NodeFile } from '../request/types' +import { BrowserFile, NodeFile } from '../types' import { isFileData } from '../uploadFile/types' /** diff --git a/packages/upload-client/src/tools/getFormData.react-native.ts b/packages/upload-client/src/tools/getFormData.react-native.ts index 115910193..6490f2e68 100644 --- a/packages/upload-client/src/tools/getFormData.react-native.ts +++ b/packages/upload-client/src/tools/getFormData.react-native.ts @@ -1,4 +1,4 @@ -import { BrowserFile, NodeFile } from '../request/types' +import { BrowserFile, NodeFile } from '../types' import { FileTransformer, GetFormDataFileAppendOptions, diff --git a/packages/upload-client/src/tools/isMultipart.ts b/packages/upload-client/src/tools/isMultipart.ts index 1d864a6b6..728cb3ceb 100644 --- a/packages/upload-client/src/tools/isMultipart.ts +++ b/packages/upload-client/src/tools/isMultipart.ts @@ -1,7 +1,7 @@ import defaultSettings from '../defaultSettings' /* Types */ -import { NodeFile, BrowserFile } from '../request/types' +import { NodeFile, BrowserFile } from '../types' /** * Get file size. diff --git a/packages/upload-client/src/tools/types.ts b/packages/upload-client/src/tools/types.ts index e8c3635fb..b224582c4 100644 --- a/packages/upload-client/src/tools/types.ts +++ b/packages/upload-client/src/tools/types.ts @@ -1,4 +1,4 @@ -import { BrowserFile, NodeFile } from '../request/types' +import { BrowserFile, NodeFile } from '../types' export type ReactNativeAsset = { type: string; uri: string; name?: string } diff --git a/packages/upload-client/src/types.ts b/packages/upload-client/src/types.ts index bed0fa88b..691d09a3e 100644 --- a/packages/upload-client/src/types.ts +++ b/packages/upload-client/src/types.ts @@ -28,3 +28,6 @@ export interface Settings extends Partial { source?: string jsonpCallback?: string } + +export type BrowserFile = Blob | File +export type NodeFile = Buffer // | NodeJS.ReadableStream diff --git a/packages/upload-client/src/uploadFile/index.ts b/packages/upload-client/src/uploadFile/index.ts index c0cfe93dc..249a012c9 100644 --- a/packages/upload-client/src/uploadFile/index.ts +++ b/packages/upload-client/src/uploadFile/index.ts @@ -6,7 +6,7 @@ import defaultSettings from '../defaultSettings' /* Types */ import { Url, Uuid, ProgressCallback, Metadata } from '../api/types' import { CustomUserAgent } from '@uploadcare/api-client-utils' -import { NodeFile, BrowserFile } from '../request/types' +import { NodeFile, BrowserFile } from '../types' import { isFileData, isUrl, isUuid } from './types' import { UploadcareFile } from '../tools/UploadcareFile' import { isMultipart, getFileSize } from '../tools/isMultipart' diff --git a/packages/upload-client/src/uploadFile/types.ts b/packages/upload-client/src/uploadFile/types.ts index 44ba71634..e991d36bf 100644 --- a/packages/upload-client/src/uploadFile/types.ts +++ b/packages/upload-client/src/uploadFile/types.ts @@ -1,5 +1,5 @@ import { Url, Uuid } from '..' -import { NodeFile, BrowserFile } from '../request/types' +import { NodeFile, BrowserFile } from '../types' /** * FileData type guard. diff --git a/packages/upload-client/src/uploadFile/uploadDirect.ts b/packages/upload-client/src/uploadFile/uploadDirect.ts index 67446e2bd..c2e2aaa41 100644 --- a/packages/upload-client/src/uploadFile/uploadDirect.ts +++ b/packages/upload-client/src/uploadFile/uploadDirect.ts @@ -2,7 +2,7 @@ import base from '../api/base' import { UploadcareFile } from '../tools/UploadcareFile' import { isReadyPoll } from '../tools/isReadyPoll' -import { NodeFile, BrowserFile } from '../request/types' +import { NodeFile, BrowserFile } from '../types' import { Metadata, ProgressCallback } from '../api/types' import { CustomUserAgent } from '@uploadcare/api-client-utils' diff --git a/packages/upload-client/src/uploadFile/uploadMultipart.ts b/packages/upload-client/src/uploadFile/uploadMultipart.ts index 8c48af5cb..e0098b286 100644 --- a/packages/upload-client/src/uploadFile/uploadMultipart.ts +++ b/packages/upload-client/src/uploadFile/uploadMultipart.ts @@ -21,7 +21,7 @@ import { ProgressCallback, UnknownProgressInfo } from '../api/types' -import { NodeFile, BrowserFile } from '../request/types' +import { NodeFile, BrowserFile } from '../types' export type MultipartOptions = { publicKey: string diff --git a/packages/upload-client/src/uploadFileGroup/index.ts b/packages/upload-client/src/uploadFileGroup/index.ts index 9bad65aed..bf4b974fa 100644 --- a/packages/upload-client/src/uploadFileGroup/index.ts +++ b/packages/upload-client/src/uploadFileGroup/index.ts @@ -13,7 +13,7 @@ import { Url, Uuid } from '../api/types' -import { NodeFile, BrowserFile } from '../request/types' +import { NodeFile, BrowserFile } from '../types' export type GroupFromOptions = { jsonpCallback?: string diff --git a/packages/upload-client/src/uploadFileGroup/types.ts b/packages/upload-client/src/uploadFileGroup/types.ts index de6231ebb..ac61838fc 100644 --- a/packages/upload-client/src/uploadFileGroup/types.ts +++ b/packages/upload-client/src/uploadFileGroup/types.ts @@ -1,6 +1,6 @@ import { isFileData, isUrl, isUuid } from '../uploadFile/types' import { Url, Uuid } from '../api/types' -import { NodeFile, BrowserFile } from '../request/types' +import { NodeFile, BrowserFile } from '../types' /** * FileData type guard. From f586f847aca842a4f4fdb0d292172f996230a159 Mon Sep 17 00:00:00 2001 From: nd0ut Date: Thu, 29 Dec 2022 16:32:07 +0400 Subject: [PATCH 04/16] feat(upload-client): support react-native asset file input --- packages/upload-client/src/UploadClient.ts | 10 +-- packages/upload-client/src/api/base.ts | 23 +++--- .../upload-client/src/api/multipartUpload.ts | 8 +-- packages/upload-client/src/index.ts | 2 +- .../upload-client/src/request/request.node.ts | 11 +-- packages/upload-client/src/request/types.ts | 6 +- .../upload-client/src/tools/buildFormData.ts | 8 +-- .../src/tools/getBlobFromReactNativeAsset.ts | 14 ++++ .../upload-client/src/tools/getContentType.ts | 18 +++++ .../upload-client/src/tools/getFileName.ts | 23 ++++++ .../upload-client/src/tools/getFileSize.ts | 17 +++++ .../src/tools/getFormData.react-native.ts | 25 ++++--- .../upload-client/src/tools/isFileData.ts | 29 ++++++++ .../upload-client/src/tools/isMultipart.ts | 10 --- packages/upload-client/src/tools/types.ts | 8 +-- packages/upload-client/src/types.ts | 11 ++- .../upload-client/src/uploadFile/index.ts | 21 +++--- .../src/uploadFile/prepareChunks.browser.ts | 12 ++-- .../src/uploadFile/prepareChunks.node.ts | 12 ++-- .../uploadFile/prepareChunks.react-native.ts | 23 ++++-- .../src/uploadFile/sliceChunk.ts | 12 ++-- .../upload-client/src/uploadFile/types.ts | 33 +++------ .../src/uploadFile/uploadDirect.ts | 8 +-- .../src/uploadFile/uploadMultipart.ts | 37 +++++----- .../src/uploadFileGroup/index.ts | 16 ++--- .../src/uploadFileGroup/types.ts | 13 ++-- .../tools/getBlobFromReactNativeAsset.test.ts | 34 +++++++++ .../test/tools/getContentType.test.ts | 32 +++++++++ .../test/tools/getFileName.test.ts | 35 +++++++++ .../test/tools/getFileSize.test.ts | 44 ++++++++++++ .../test/tools/isFileData.test.ts | 71 +++++++++++++++++++ 31 files changed, 478 insertions(+), 148 deletions(-) create mode 100644 packages/upload-client/src/tools/getBlobFromReactNativeAsset.ts create mode 100644 packages/upload-client/src/tools/getContentType.ts create mode 100644 packages/upload-client/src/tools/getFileName.ts create mode 100644 packages/upload-client/src/tools/getFileSize.ts create mode 100644 packages/upload-client/src/tools/isFileData.ts create mode 100644 packages/upload-client/test/tools/getBlobFromReactNativeAsset.test.ts create mode 100644 packages/upload-client/test/tools/getContentType.test.ts create mode 100644 packages/upload-client/test/tools/getFileName.test.ts create mode 100644 packages/upload-client/test/tools/getFileSize.test.ts create mode 100644 packages/upload-client/test/tools/isFileData.test.ts diff --git a/packages/upload-client/src/UploadClient.ts b/packages/upload-client/src/UploadClient.ts index 964ebca3b..a170ce981 100644 --- a/packages/upload-client/src/UploadClient.ts +++ b/packages/upload-client/src/UploadClient.ts @@ -25,7 +25,7 @@ import { UploadcareGroup } from './tools/UploadcareGroup' import { FileFromOptions, uploadFile } from './uploadFile' import { FileInfo, GroupId, GroupInfo, Token, Url, Uuid } from './api/types' -import { Settings, BrowserFile, NodeFile } from './types' +import { SupportedFileInput, BrowserFile, NodeFile, Settings } from './types' import uploadFileGroup, { GroupFromOptions } from './uploadFileGroup' /** @@ -55,7 +55,7 @@ export default class UploadClient { } base( - file: NodeFile | BrowserFile, + file: SupportedFileInput, options: Partial = {} ): Promise { const settings = this.getSettings() @@ -115,7 +115,7 @@ export default class UploadClient { } multipartUpload( - part: Buffer | Blob, + part: BrowserFile | NodeFile, url: MultipartPart, options: Partial = {} ): Promise { @@ -141,7 +141,7 @@ export default class UploadClient { } uploadFile( - data: NodeFile | BrowserFile | Url | Uuid, + data: SupportedFileInput | Url | Uuid, options: Partial = {} ): Promise { const settings = this.getSettings() @@ -150,7 +150,7 @@ export default class UploadClient { } uploadFileGroup( - data: (NodeFile | BrowserFile)[] | Url[] | Uuid[], + data: SupportedFileInput[] | Url[] | Uuid[], options: Partial = {} ): Promise { const settings = this.getSettings() diff --git a/packages/upload-client/src/api/base.ts b/packages/upload-client/src/api/base.ts index 0b4dd7525..59903e060 100644 --- a/packages/upload-client/src/api/base.ts +++ b/packages/upload-client/src/api/base.ts @@ -1,21 +1,19 @@ +import { camelizeKeys, CustomUserAgent } from '@uploadcare/api-client-utils' +import { defaultSettings } from '../defaultSettings' import request from '../request/request.node' import buildFormData from '../tools/buildFormData' +import { UploadClientError } from '../tools/errors' import getUrl from '../tools/getUrl' -import { - defaultSettings, - defaultFilename, - defaultContentType -} from '../defaultSettings' import { getUserAgent } from '../tools/getUserAgent' -import { camelizeKeys, CustomUserAgent } from '@uploadcare/api-client-utils' -import { UploadClientError } from '../tools/errors' import { retryIfFailed } from '../tools/retryIfFailed' /* Types */ -import { Uuid, ProgressCallback, Metadata } from './types' import { FailedResponse } from '../request/types' -import { NodeFile, BrowserFile } from '../types' +import { getContentType } from '../tools/getContentType' +import { getFileName } from '../tools/getFileName' import { getStoreValue } from '../tools/getStoreValue' +import { SupportedFileInput } from '../types' +import { Metadata, ProgressCallback, Uuid } from './types' export type BaseResponse = { file: Uuid @@ -50,7 +48,7 @@ export type BaseOptions = { * Can be canceled and has progress. */ export default function base( - file: NodeFile | BrowserFile, + file: SupportedFileInput, { publicKey, fileName, @@ -82,9 +80,8 @@ export default function base( data: buildFormData({ file: { data: file, - name: fileName || (file as File)?.name || defaultFilename, - contentType: - contentType || (file as File)?.type || defaultContentType + name: fileName || getFileName(file), + contentType: contentType || getContentType(file) }, UPLOADCARE_PUB_KEY: publicKey, UPLOADCARE_STORE: getStoreValue(store), diff --git a/packages/upload-client/src/api/multipartUpload.ts b/packages/upload-client/src/api/multipartUpload.ts index 589fcfbd0..dbc554a9e 100644 --- a/packages/upload-client/src/api/multipartUpload.ts +++ b/packages/upload-client/src/api/multipartUpload.ts @@ -2,10 +2,10 @@ import { MultipartPart } from './multipartStart' import request from '../request/request.node' -import { ComputableProgressInfo, ProgressCallback } from './types' -import { NodeFile, BrowserFile } from '../types' -import { retryIfFailed } from '../tools/retryIfFailed' import defaultSettings from '../defaultSettings' +import { retryIfFailed } from '../tools/retryIfFailed' +import { SupportedFileInput } from '../types' +import { ComputableProgressInfo, ProgressCallback } from './types' export type MultipartUploadOptions = { publicKey?: string @@ -25,7 +25,7 @@ export type MultipartUploadResponse = { */ export default function multipartUpload( - part: NodeFile | BrowserFile, + part: SupportedFileInput, url: MultipartPart, { signal, diff --git a/packages/upload-client/src/index.ts b/packages/upload-client/src/index.ts index bc74affe6..9eb2d7760 100644 --- a/packages/upload-client/src/index.ts +++ b/packages/upload-client/src/index.ts @@ -32,7 +32,7 @@ export { UploadcareFile } from './tools/UploadcareFile' export { UploadcareGroup } from './tools/UploadcareGroup' export { UploadClientError } from './tools/errors' export { Settings } from './types' -export { NodeFile, BrowserFile } from './types' +export { NodeFile, BrowserFile, ReactNativeAsset } from './types' export { BaseOptions, BaseResponse } from './api/base' export { FileInfo, GroupId, GroupInfo, Token, Url, Uuid } from './api/types' export { InfoOptions } from './api/info' diff --git a/packages/upload-client/src/request/request.node.ts b/packages/upload-client/src/request/request.node.ts index 6f7da6e1e..2736ffa1a 100644 --- a/packages/upload-client/src/request/request.node.ts +++ b/packages/upload-client/src/request/request.node.ts @@ -2,12 +2,13 @@ import NodeFormData from 'form-data' import http from 'http' import https from 'https' -import { parse } from 'url' import { Readable, Transform } from 'stream' +import { parse } from 'url' -import { onCancel, CancelError } from '@uploadcare/api-client-utils' -import { RequestOptions, RequestResponse } from './types' +import { CancelError, onCancel } from '@uploadcare/api-client-utils' import { ProgressCallback } from '../api/types' +import { SupportedFileInput } from '../types' +import { RequestOptions, RequestResponse } from './types' // ProgressEmitter is a simple PassThrough-style transform stream which keeps // track of the number of bytes which have been piped through it and will @@ -44,7 +45,7 @@ const getLength = (formData: NodeFormData): Promise => }) function isFormData( - formData?: NodeFormData | FormData | Buffer | Blob + formData?: NodeFormData | FormData | SupportedFileInput ): formData is NodeFormData { if (formData && formData.toString() === '[object FormData]') { return true @@ -54,7 +55,7 @@ function isFormData( } function isReadable( - data?: Readable | NodeFormData | FormData | Buffer | Blob, + data?: Readable | NodeFormData | FormData | SupportedFileInput, isFormData?: boolean ): data is Readable { if (data && (data instanceof Readable || isFormData)) { diff --git a/packages/upload-client/src/request/types.ts b/packages/upload-client/src/request/types.ts index b8a6f1ec9..32b129ae8 100644 --- a/packages/upload-client/src/request/types.ts +++ b/packages/upload-client/src/request/types.ts @@ -4,7 +4,7 @@ import { ProgressCallback, UnknownProgressInfo } from '../api/types' -import { BrowserFile, NodeFile } from '../types' +import { SupportedFileInput } from '../types' export type Headers = { [key: string]: string | string[] | undefined @@ -14,7 +14,7 @@ export type RequestOptions = { method?: string url: string query?: string - data?: NodeFormData | FormData | BrowserFile | NodeFile + data?: NodeFormData | FormData | SupportedFileInput headers?: Headers signal?: AbortSignal onProgress?: ProgressCallback @@ -24,7 +24,7 @@ export type ErrorRequestInfo = { method?: string url: string query?: string - data?: NodeFormData | FormData | BrowserFile | NodeFile + data?: NodeFormData | FormData | SupportedFileInput headers?: Headers } diff --git a/packages/upload-client/src/tools/buildFormData.ts b/packages/upload-client/src/tools/buildFormData.ts index 386a5a3c4..36bf264b7 100644 --- a/packages/upload-client/src/tools/buildFormData.ts +++ b/packages/upload-client/src/tools/buildFormData.ts @@ -1,8 +1,8 @@ import getFormData, { getFileOptions, transformFile } from './getFormData.node' import NodeFormData from 'form-data' -import { BrowserFile, NodeFile } from '../types' -import { isFileData } from '../uploadFile/types' +import { SupportedFileInput } from '../types' +import { isFileData } from './isFileData' /** * Constructs FormData instance. @@ -21,7 +21,7 @@ interface FileOptions { } interface FileType extends FileOptions { - data: BrowserFile | NodeFile + data: SupportedFileInput } type InputValue = FileType | SimpleType | ObjectType @@ -33,7 +33,7 @@ type FormDataOptions = { type Params = Array< [ string, - string | BrowserFile | NodeFile, + string | SupportedFileInput, ...(string | { [key: string]: string | undefined })[] ] > diff --git a/packages/upload-client/src/tools/getBlobFromReactNativeAsset.ts b/packages/upload-client/src/tools/getBlobFromReactNativeAsset.ts new file mode 100644 index 000000000..56289a150 --- /dev/null +++ b/packages/upload-client/src/tools/getBlobFromReactNativeAsset.ts @@ -0,0 +1,14 @@ +import { ReactNativeAsset } from '../types' + +const memo: WeakMap = new WeakMap() + +export const getBlobFromReactNativeAsset = async ( + asset: ReactNativeAsset +): Promise => { + if (memo.has(asset)) { + return memo.get(asset) as Blob + } + const blob = await fetch(asset.uri).then((res) => res.blob()) + memo.set(asset, blob) + return blob +} diff --git a/packages/upload-client/src/tools/getContentType.ts b/packages/upload-client/src/tools/getContentType.ts new file mode 100644 index 000000000..a3a2c164e --- /dev/null +++ b/packages/upload-client/src/tools/getContentType.ts @@ -0,0 +1,18 @@ +import { defaultContentType } from '../defaultSettings' +import { SupportedFileInput } from '../types' +import { isBlob, isFile, isReactNativeAsset } from './isFileData' + +export const getContentType = (file: SupportedFileInput): string => { + let contentType = '' + if (isBlob(file) || isFile(file) || isReactNativeAsset(file)) { + contentType = file.type + } + if (contentType) { + return contentType + } + console.warn( + `Cannot determine content type. Using default content type: ${defaultContentType}`, + file + ) + return defaultContentType +} diff --git a/packages/upload-client/src/tools/getFileName.ts b/packages/upload-client/src/tools/getFileName.ts new file mode 100644 index 000000000..c496dd985 --- /dev/null +++ b/packages/upload-client/src/tools/getFileName.ts @@ -0,0 +1,23 @@ +import { defaultFilename } from '../defaultSettings' +import { SupportedFileInput } from '../types' +import { isBlob, isBuffer, isFile, isReactNativeAsset } from './isFileData' + +export const getFileName = (file: SupportedFileInput): string => { + let filename = '' + + if (isFile(file) && file.name) { + filename = file.name + } else if (isBlob(file) || isBuffer(file)) { + filename = '' + } else if (isReactNativeAsset(file) && file.name) { + filename = file.name + } + if (filename) { + return filename + } + console.warn( + `Cannot determine filename. Using default filename: ${defaultFilename}`, + file + ) + return defaultFilename +} diff --git a/packages/upload-client/src/tools/getFileSize.ts b/packages/upload-client/src/tools/getFileSize.ts new file mode 100644 index 000000000..660a0a059 --- /dev/null +++ b/packages/upload-client/src/tools/getFileSize.ts @@ -0,0 +1,17 @@ +import { SupportedFileInput } from '../types' +import { getBlobFromReactNativeAsset } from './getBlobFromReactNativeAsset' +import { isBlob, isBuffer, isFile, isReactNativeAsset } from './isFileData' + +export const getFileSize = async (file: SupportedFileInput) => { + if (isBuffer(file)) { + return file.length + } + if (isFile(file) || isBlob(file)) { + return file.size + } + if (isReactNativeAsset(file)) { + const blob = await getBlobFromReactNativeAsset(file) + return blob.size + } + throw new Error(`Unknown file type. Cannot determine file size.`) +} diff --git a/packages/upload-client/src/tools/getFormData.react-native.ts b/packages/upload-client/src/tools/getFormData.react-native.ts index 6490f2e68..6a69c181b 100644 --- a/packages/upload-client/src/tools/getFormData.react-native.ts +++ b/packages/upload-client/src/tools/getFormData.react-native.ts @@ -1,19 +1,26 @@ -import { BrowserFile, NodeFile } from '../types' -import { - FileTransformer, - GetFormDataFileAppendOptions, - ReactNativeAsset -} from './types' +import { SupportedFileInput, ReactNativeAsset } from '../types' +import { isBlob, isReactNativeAsset } from './isFileData' +import { FileTransformer, GetFormDataFileAppendOptions } from './types' export const getFileOptions: GetFormDataFileAppendOptions = () => [] export const transformFile: FileTransformer = ( - file: BrowserFile | NodeFile, + file: SupportedFileInput, name: string, contentType: string ): ReactNativeAsset => { - const uri = URL.createObjectURL(file as BrowserFile) - return { uri, name, type: contentType } + if (isReactNativeAsset(file)) { + return { + uri: file.uri, + name: file.name || name, + type: file.type || contentType + } + } + if (isBlob(file)) { + const uri = URL.createObjectURL(file) + return { uri, name: name, type: file.type || contentType } + } + throw new Error(`Unsupported file type.`) } export default (): FormData => new FormData() diff --git a/packages/upload-client/src/tools/isFileData.ts b/packages/upload-client/src/tools/isFileData.ts new file mode 100644 index 000000000..c9b1f015e --- /dev/null +++ b/packages/upload-client/src/tools/isFileData.ts @@ -0,0 +1,29 @@ +import { SupportedFileInput, ReactNativeAsset } from '../types' + +export const isBlob = (data: unknown): data is Blob => { + return typeof Blob !== 'undefined' && data instanceof Blob +} + +export const isFile = (data: unknown): data is File => { + return typeof File !== 'undefined' && data instanceof File +} + +export const isBuffer = (data: unknown): data is Buffer => { + return typeof Buffer !== 'undefined' && data instanceof Buffer +} + +export const isReactNativeAsset = (data: unknown): data is ReactNativeAsset => { + return ( + !!data && + typeof data === 'object' && + !Array.isArray(data) && + 'uri' in data && + typeof (data as Record<'uri', unknown>).uri === 'string' + ) +} + +export const isFileData = (data: unknown): data is SupportedFileInput => { + return ( + isBlob(data) || isFile(data) || isBuffer(data) || isReactNativeAsset(data) + ) +} diff --git a/packages/upload-client/src/tools/isMultipart.ts b/packages/upload-client/src/tools/isMultipart.ts index 728cb3ceb..c43366f6e 100644 --- a/packages/upload-client/src/tools/isMultipart.ts +++ b/packages/upload-client/src/tools/isMultipart.ts @@ -1,15 +1,5 @@ import defaultSettings from '../defaultSettings' -/* Types */ -import { NodeFile, BrowserFile } from '../types' - -/** - * Get file size. - */ -export const getFileSize = (file: NodeFile | BrowserFile): number => { - return (file as Buffer).length || (file as Blob).size -} - /** * Check if FileData is multipart data. */ diff --git a/packages/upload-client/src/tools/types.ts b/packages/upload-client/src/tools/types.ts index b224582c4..bde68fddc 100644 --- a/packages/upload-client/src/tools/types.ts +++ b/packages/upload-client/src/tools/types.ts @@ -1,12 +1,10 @@ -import { BrowserFile, NodeFile } from '../types' - -export type ReactNativeAsset = { type: string; uri: string; name?: string } +import { SupportedFileInput } from '../types' export type FileTransformer = ( - file: NodeFile | BrowserFile, + file: SupportedFileInput, name: string, contentType: string -) => NodeFile | BrowserFile | ReactNativeAsset +) => SupportedFileInput export type GetFormDataFileAppendOptions = (options: { [key: string]: string | undefined diff --git a/packages/upload-client/src/types.ts b/packages/upload-client/src/types.ts index 691d09a3e..a33df11e6 100644 --- a/packages/upload-client/src/types.ts +++ b/packages/upload-client/src/types.ts @@ -30,4 +30,13 @@ export interface Settings extends Partial { } export type BrowserFile = Blob | File -export type NodeFile = Buffer // | NodeJS.ReadableStream +export type NodeFile = Buffer +export type ReactNativeAsset = { + type: string + uri: string + name?: string +} +export type ReactNativeFile = ReactNativeAsset | Blob + +export type SupportedFileInput = BrowserFile | NodeFile | ReactNativeFile +export type AnySlicable = BrowserFile | NodeFile diff --git a/packages/upload-client/src/uploadFile/index.ts b/packages/upload-client/src/uploadFile/index.ts index 249a012c9..59b325fe9 100644 --- a/packages/upload-client/src/uploadFile/index.ts +++ b/packages/upload-client/src/uploadFile/index.ts @@ -1,15 +1,17 @@ +import defaultSettings from '../defaultSettings' import uploadDirect from './uploadDirect' -import uploadFromUrl from './uploadFromUrl' import uploadFromUploaded from './uploadFromUploaded' -import defaultSettings from '../defaultSettings' +import uploadFromUrl from './uploadFromUrl' /* Types */ -import { Url, Uuid, ProgressCallback, Metadata } from '../api/types' import { CustomUserAgent } from '@uploadcare/api-client-utils' -import { NodeFile, BrowserFile } from '../types' -import { isFileData, isUrl, isUuid } from './types' +import { Metadata, ProgressCallback, Url, Uuid } from '../api/types' +import { getFileSize } from '../tools/getFileSize' +import { isFileData } from '../tools/isFileData' +import { isMultipart } from '../tools/isMultipart' import { UploadcareFile } from '../tools/UploadcareFile' -import { isMultipart, getFileSize } from '../tools/isMultipart' +import { SupportedFileInput } from '../types' +import { isUrl, isUuid } from './types' import uploadMultipart from './uploadMultipart' export type FileFromOptions = { @@ -49,8 +51,8 @@ export type FileFromOptions = { * Uploads file from provided data. */ -function uploadFile( - data: NodeFile | BrowserFile | Url | Uuid, +async function uploadFile( + data: SupportedFileInput | Url | Uuid, { publicKey, @@ -85,7 +87,7 @@ function uploadFile( }: FileFromOptions ): Promise { if (isFileData(data)) { - const fileSize = getFileSize(data) + const fileSize = await getFileSize(data) if (isMultipart(fileSize, multipartMinFileSize)) { return uploadMultipart(data, { @@ -93,6 +95,7 @@ function uploadFile( contentType, multipartChunkSize, + fileSize, fileName, baseURL, secureSignature, diff --git a/packages/upload-client/src/uploadFile/prepareChunks.browser.ts b/packages/upload-client/src/uploadFile/prepareChunks.browser.ts index d309bfe55..c77e01d64 100644 --- a/packages/upload-client/src/uploadFile/prepareChunks.browser.ts +++ b/packages/upload-client/src/uploadFile/prepareChunks.browser.ts @@ -1,10 +1,12 @@ +import { SupportedFileInput, BrowserFile } from '../types' import { sliceChunk } from './sliceChunk' +import { PrepareChunks } from './types' -export function prepareChunks( - file: Buffer | Blob, +export const prepareChunks: PrepareChunks = async ( + file: SupportedFileInput, fileSize: number, chunkSize: number -): (index: number) => Buffer | Blob { - return (index: number): Buffer | Blob => - sliceChunk(file, index, fileSize, chunkSize) +) => { + return (index: number): BrowserFile => + sliceChunk(file as BrowserFile, index, fileSize, chunkSize) } diff --git a/packages/upload-client/src/uploadFile/prepareChunks.node.ts b/packages/upload-client/src/uploadFile/prepareChunks.node.ts index d309bfe55..db7f0d31f 100644 --- a/packages/upload-client/src/uploadFile/prepareChunks.node.ts +++ b/packages/upload-client/src/uploadFile/prepareChunks.node.ts @@ -1,10 +1,12 @@ +import { SupportedFileInput, NodeFile } from '../types' import { sliceChunk } from './sliceChunk' +import { PrepareChunks } from './types' -export function prepareChunks( - file: Buffer | Blob, +export const prepareChunks: PrepareChunks = async ( + file: SupportedFileInput, fileSize: number, chunkSize: number -): (index: number) => Buffer | Blob { - return (index: number): Buffer | Blob => - sliceChunk(file, index, fileSize, chunkSize) +) => { + return (index: number): NodeFile => + sliceChunk(file as NodeFile, index, fileSize, chunkSize) } diff --git a/packages/upload-client/src/uploadFile/prepareChunks.react-native.ts b/packages/upload-client/src/uploadFile/prepareChunks.react-native.ts index d1ccd1f43..09ad55955 100644 --- a/packages/upload-client/src/uploadFile/prepareChunks.react-native.ts +++ b/packages/upload-client/src/uploadFile/prepareChunks.react-native.ts @@ -1,4 +1,8 @@ +import { isReactNativeAsset } from '../tools/isFileData' +import { SupportedFileInput } from '../types' +import { getBlobFromReactNativeAsset } from '../tools/getBlobFromReactNativeAsset' import { sliceChunk } from './sliceChunk' +import { PrepareChunks } from './types' /** * React-native hack for blob slicing @@ -10,14 +14,21 @@ import { sliceChunk } from './sliceChunk' * See https://github.com/uploadcare/uploadcare-js-api-clients/issues/306 * and https://github.com/facebook/react-native/issues/27543 */ -export function prepareChunks( - file: Buffer | Blob, +export const prepareChunks: PrepareChunks = async ( + file: SupportedFileInput, fileSize: number, chunkSize: number -): (index: number) => Buffer | Blob { - const chunks: (Buffer | Blob)[] = [] - return (index: number): Buffer | Blob => { - const chunk = sliceChunk(file, index, fileSize, chunkSize) +) => { + let blob: Blob + if (isReactNativeAsset(file)) { + blob = await getBlobFromReactNativeAsset(file) + } else { + blob = file as Blob + } + + const chunks: Blob[] = [] + return (index: number): Blob => { + const chunk = sliceChunk(blob, index, fileSize, chunkSize) chunks.push(chunk) return chunk } diff --git a/packages/upload-client/src/uploadFile/sliceChunk.ts b/packages/upload-client/src/uploadFile/sliceChunk.ts index 214c0bc76..efdbd3a14 100644 --- a/packages/upload-client/src/uploadFile/sliceChunk.ts +++ b/packages/upload-client/src/uploadFile/sliceChunk.ts @@ -1,11 +1,15 @@ -export const sliceChunk = ( - file: Buffer | Blob, +import { AnySlicable } from '../types' + +type Slicable = T & { slice: (start: number, end: number) => Slicable } + +export const sliceChunk = ( + file: Slicable, index: number, fileSize: number, chunkSize: number -): Buffer | Blob => { +): Slicable => { const start = chunkSize * index const end = Math.min(start + chunkSize, fileSize) - return file.slice(start, end) + return file.slice(start, end) as Slicable } diff --git a/packages/upload-client/src/uploadFile/types.ts b/packages/upload-client/src/uploadFile/types.ts index e991d36bf..56ec1dbbe 100644 --- a/packages/upload-client/src/uploadFile/types.ts +++ b/packages/upload-client/src/uploadFile/types.ts @@ -1,26 +1,11 @@ import { Url, Uuid } from '..' -import { NodeFile, BrowserFile } from '../types' - -/** - * FileData type guard. - */ -export const isFileData = ( - data: NodeFile | BrowserFile | Url | Uuid -): data is NodeFile | BrowserFile => { - return ( - data !== undefined && - ((typeof Blob !== 'undefined' && data instanceof Blob) || - (typeof File !== 'undefined' && data instanceof File) || - (typeof Buffer !== 'undefined' && data instanceof Buffer)) - ) -} +import { SupportedFileInput, AnySlicable } from '../types' +import { isFileData } from '../tools/isFileData' /** * Uuid type guard. */ -export const isUuid = ( - data: NodeFile | BrowserFile | Url | Uuid -): data is Uuid => { +export const isUuid = (data: SupportedFileInput | Url | Uuid): data is Uuid => { const UUID_REGEX = '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}' const regExp = new RegExp(UUID_REGEX) @@ -31,14 +16,18 @@ export const isUuid = ( /** * Url type guard. * - * @param {NodeFile | BrowserFile | Url | Uuid} data + * @param {SupportedFileInput | Url | Uuid} data */ -export const isUrl = ( - data: NodeFile | BrowserFile | Url | Uuid -): data is Url => { +export const isUrl = (data: SupportedFileInput | Url | Uuid): data is Url => { const URL_REGEX = '^(?:\\w+:)?\\/\\/([^\\s\\.]+\\.\\S{2}|localhost[\\:?\\d]*)\\S*$' const regExp = new RegExp(URL_REGEX) return !isFileData(data) && regExp.test(data) } + +export type PrepareChunks = ( + file: SupportedFileInput, + fileSize: number, + chunkSize: number +) => Promise<(index: number) => AnySlicable> diff --git a/packages/upload-client/src/uploadFile/uploadDirect.ts b/packages/upload-client/src/uploadFile/uploadDirect.ts index c2e2aaa41..078c924d9 100644 --- a/packages/upload-client/src/uploadFile/uploadDirect.ts +++ b/packages/upload-client/src/uploadFile/uploadDirect.ts @@ -1,10 +1,10 @@ import base from '../api/base' -import { UploadcareFile } from '../tools/UploadcareFile' import { isReadyPoll } from '../tools/isReadyPoll' +import { UploadcareFile } from '../tools/UploadcareFile' -import { NodeFile, BrowserFile } from '../types' -import { Metadata, ProgressCallback } from '../api/types' import { CustomUserAgent } from '@uploadcare/api-client-utils' +import { Metadata, ProgressCallback } from '../api/types' +import { SupportedFileInput } from '../types' type DirectOptions = { publicKey: string @@ -31,7 +31,7 @@ type DirectOptions = { } const uploadDirect = ( - file: NodeFile | BrowserFile, + file: SupportedFileInput, { publicKey, diff --git a/packages/upload-client/src/uploadFile/uploadMultipart.ts b/packages/upload-client/src/uploadFile/uploadMultipart.ts index e0098b286..9fe59c463 100644 --- a/packages/upload-client/src/uploadFile/uploadMultipart.ts +++ b/packages/upload-client/src/uploadFile/uploadMultipart.ts @@ -1,19 +1,15 @@ -import defaultSettings, { - defaultContentType, - defaultFilename -} from '../defaultSettings' -import { prepareChunks } from './prepareChunks.node' +import { CustomUserAgent } from '@uploadcare/api-client-utils' +import multipartComplete from '../api/multipartComplete' import multipartStart from '../api/multipartStart' import multipartUpload, { - MultipartUploadResponse, - MultipartUploadOptions + MultipartUploadOptions, + MultipartUploadResponse } from '../api/multipartUpload' -import multipartComplete from '../api/multipartComplete' +import defaultSettings from '../defaultSettings' +import { isReadyPoll } from '../tools/isReadyPoll' import runWithConcurrency from '../tools/runWithConcurrency' import { UploadcareFile } from '../tools/UploadcareFile' -import { getFileSize } from '../tools/isMultipart' -import { isReadyPoll } from '../tools/isReadyPoll' -import { CustomUserAgent } from '@uploadcare/api-client-utils' +import { prepareChunks } from './prepareChunks.node' import { ComputableProgressInfo, @@ -21,7 +17,10 @@ import { ProgressCallback, UnknownProgressInfo } from '../api/types' -import { NodeFile, BrowserFile } from '../types' +import { getContentType } from '../tools/getContentType' +import { getFileName } from '../tools/getFileName' +import { getFileSize } from '../tools/getFileSize' +import { SupportedFileInput } from '../types' export type MultipartOptions = { publicKey: string @@ -66,8 +65,8 @@ const uploadPart = ( retryNetworkErrorMaxTimes }) -const uploadMultipart = ( - file: NodeFile | BrowserFile, +const uploadMultipart = async ( + file: SupportedFileInput, { publicKey, @@ -96,7 +95,7 @@ const uploadMultipart = ( metadata }: MultipartOptions ): Promise => { - const size = fileSize || getFileSize(file) + const size = fileSize ?? (await getFileSize(file)) let progressValues: number[] const createProgressHandler = ( @@ -125,8 +124,8 @@ const uploadMultipart = ( return multipartStart(size, { publicKey, - contentType: contentType || (file as File).type || defaultContentType, - fileName: fileName || (file as File).name || defaultFilename, + contentType: contentType || getContentType(file), + fileName: fileName || getFileName(file), baseURL, secureSignature, secureExpire, @@ -139,8 +138,8 @@ const uploadMultipart = ( retryNetworkErrorMaxTimes, metadata }) - .then(({ uuid, parts }) => { - const getChunk = prepareChunks(file, size, multipartChunkSize) + .then(async ({ uuid, parts }) => { + const getChunk = await prepareChunks(file, size, multipartChunkSize) return Promise.all([ uuid, runWithConcurrency( diff --git a/packages/upload-client/src/uploadFileGroup/index.ts b/packages/upload-client/src/uploadFileGroup/index.ts index bf4b974fa..bf88677b5 100644 --- a/packages/upload-client/src/uploadFileGroup/index.ts +++ b/packages/upload-client/src/uploadFileGroup/index.ts @@ -1,11 +1,10 @@ -import { uploadFile, FileFromOptions } from '../uploadFile' -import defaultSettings from '../defaultSettings' import group from '../api/group' -import { UploadcareGroup } from '../tools/UploadcareGroup' +import defaultSettings from '../defaultSettings' import { UploadcareFile } from '../tools/UploadcareFile' +import { UploadcareGroup } from '../tools/UploadcareGroup' +import { FileFromOptions, uploadFile } from '../uploadFile' /* Types */ -import { isFileDataArray, isUrlArray, isUuidArray } from './types' import { ComputableProgressInfo, ProgressCallback, @@ -13,14 +12,15 @@ import { Url, Uuid } from '../api/types' -import { NodeFile, BrowserFile } from '../types' +import { SupportedFileInput } from '../types' +import { isFileDataArray, isUrlArray, isUuidArray } from './types' export type GroupFromOptions = { jsonpCallback?: string } export default function uploadFileGroup( - data: (NodeFile | BrowserFile)[] | Url[] | Uuid[], + data: SupportedFileInput[] | Url[] | Uuid[], { publicKey, @@ -79,8 +79,8 @@ export default function uploadFileGroup( } return Promise.all( - (data as (NodeFile | BrowserFile)[]).map( - (file: NodeFile | BrowserFile | Url | Uuid, index: number) => + (data as SupportedFileInput[]).map( + (file: SupportedFileInput | Url | Uuid, index: number) => uploadFile(file, { publicKey, diff --git a/packages/upload-client/src/uploadFileGroup/types.ts b/packages/upload-client/src/uploadFileGroup/types.ts index ac61838fc..72fb4fdde 100644 --- a/packages/upload-client/src/uploadFileGroup/types.ts +++ b/packages/upload-client/src/uploadFileGroup/types.ts @@ -1,13 +1,14 @@ -import { isFileData, isUrl, isUuid } from '../uploadFile/types' import { Url, Uuid } from '../api/types' -import { NodeFile, BrowserFile } from '../types' +import { SupportedFileInput } from '../types' +import { isFileData } from '../tools/isFileData' +import { isUrl, isUuid } from '../uploadFile/types' /** * FileData type guard. */ export const isFileDataArray = ( - data: (NodeFile | BrowserFile)[] | Url[] | Uuid[] -): data is (NodeFile | BrowserFile)[] => { + data: SupportedFileInput[] | Url[] | Uuid[] +): data is SupportedFileInput[] => { for (const item of data) { if (!isFileData(item)) { return false @@ -21,7 +22,7 @@ export const isFileDataArray = ( * Uuid type guard. */ export const isUuidArray = ( - data: (NodeFile | BrowserFile)[] | Url[] | Uuid[] + data: SupportedFileInput[] | Url[] | Uuid[] ): data is Uuid[] => { for (const item of data) { if (!isUuid(item)) { @@ -36,7 +37,7 @@ export const isUuidArray = ( * Url type guard. */ export const isUrlArray = ( - data: (NodeFile | BrowserFile)[] | Url[] | Uuid[] + data: SupportedFileInput[] | Url[] | Uuid[] ): data is Url[] => { for (const item of data) { if (!isUrl(item)) { diff --git a/packages/upload-client/test/tools/getBlobFromReactNativeAsset.test.ts b/packages/upload-client/test/tools/getBlobFromReactNativeAsset.test.ts new file mode 100644 index 000000000..f9ea9fecb --- /dev/null +++ b/packages/upload-client/test/tools/getBlobFromReactNativeAsset.test.ts @@ -0,0 +1,34 @@ +/** + * @jest-environment jsdom + */ +import { getBlobFromReactNativeAsset } from '../../src/tools/getBlobFromReactNativeAsset' +import { expect, jest } from '@jest/globals' + +global.fetch = jest.fn(() => + Promise.resolve({ + blob: () => new Blob(['111']) + }) +) as unknown as typeof fetch + +beforeEach(() => (global.fetch as jest.Mock).mockClear()) + +describe('getBlobFromReactNativeAsset', () => { + it('should convert ReactNative asset as Blob', async () => { + const asset = { uri: 'file://data', name: '', type: 'text/plain' } + const blob = await getBlobFromReactNativeAsset(asset) + expect(blob instanceof Blob).toBeTruthy() + expect(global.fetch).toBeCalledTimes(1) + }) + + it('should memoize results', async () => { + const asset = { uri: 'file://data', name: '', type: 'text/plain' } + + const blob1 = await getBlobFromReactNativeAsset(asset) + expect(blob1 instanceof Blob).toBeTruthy() + + const blob2 = await getBlobFromReactNativeAsset(asset) + expect(blob2 instanceof Blob).toBeTruthy() + + expect(global.fetch).toBeCalledTimes(1) + }) +}) diff --git a/packages/upload-client/test/tools/getContentType.test.ts b/packages/upload-client/test/tools/getContentType.test.ts new file mode 100644 index 000000000..f34bcb90a --- /dev/null +++ b/packages/upload-client/test/tools/getContentType.test.ts @@ -0,0 +1,32 @@ +/** + * @jest-environment jsdom + */ +import { getContentType } from '../../src/tools/getContentType' + +describe('getContentType', () => { + it('should return content type of Blob', () => { + const blob = new Blob([''], { type: 'text/plain' }) + expect(getContentType(blob)).toEqual('text/plain') + }) + + it('should return content type of File', () => { + const file = new File([''], 'test.txt', { type: 'text/plain' }) + expect(getContentType(file)).toEqual('text/plain') + }) + + it('should return content type of ReactNative asset', () => { + const asset = { uri: 'file://data', name: 'test.txt', type: 'text/plain' } + expect(getContentType(asset)).toEqual('text/plain') + }) + + it('should return fallback value if no type found', () => { + const blob = new Blob(['']) + expect(getContentType(blob)).toEqual('application/octet-stream') + + const file = new File([''], 'test.txt') + expect(getContentType(file)).toEqual('application/octet-stream') + + const asset = { uri: 'file://data', name: 'test.txt', type: '' } + expect(getContentType(asset)).toEqual('application/octet-stream') + }) +}) diff --git a/packages/upload-client/test/tools/getFileName.test.ts b/packages/upload-client/test/tools/getFileName.test.ts new file mode 100644 index 000000000..0ae8c046e --- /dev/null +++ b/packages/upload-client/test/tools/getFileName.test.ts @@ -0,0 +1,35 @@ +/** + * @jest-environment jsdom + */ +import { expect, jest } from '@jest/globals' +import { getFileName } from '../../src/tools/getFileName' + +describe('getFileName', () => { + it('should return filename of File', () => { + const file = new File([''], 'test.txt') + expect(getFileName(file)).toEqual('test.txt') + }) + + it('should return filename of ReactNative asset', () => { + const asset = { uri: 'file://data', name: 'test.txt', type: 'text/plain' } + expect(getFileName(asset)).toEqual('test.txt') + }) + + it('should return fallback value if no filename found', () => { + const spy = jest.spyOn(console, 'warn').mockImplementation(() => ({})) + + const blob = new Blob(['']) + expect(getFileName(blob)).toEqual('original') + + const file = new File([''], '') + expect(getFileName(file)).toEqual('original') + + const buffer = Buffer.from('') + expect(getFileName(buffer)).toEqual('original') + + const asset = { uri: 'file://data', name: '', type: 'text/plain' } + expect(getFileName(asset)).toEqual('original') + + expect(spy).toBeCalledTimes(4) + }) +}) diff --git a/packages/upload-client/test/tools/getFileSize.test.ts b/packages/upload-client/test/tools/getFileSize.test.ts new file mode 100644 index 000000000..6b407e326 --- /dev/null +++ b/packages/upload-client/test/tools/getFileSize.test.ts @@ -0,0 +1,44 @@ +/** + * @jest-environment jsdom + */ +import { expect, jest } from '@jest/globals' +import { getFileSize } from '../../src/tools/getFileSize' + +global.fetch = jest.fn(() => + Promise.resolve({ + blob: () => new Blob(['111']) + }) +) as unknown as typeof fetch + +beforeEach(() => { + ;(fetch as jest.Mock).mockClear() +}) + +describe('getFileSize', () => { + it('should return size of File', async () => { + const file = new File(['111'], 'test.txt') + await expect(getFileSize(file)).resolves.toEqual(3) + }) + + it('should return size of Blob', async () => { + const blob = new Blob(['111']) + await expect(getFileSize(blob)).resolves.toEqual(3) + }) + + it('should return size of Buffer', async () => { + const buffer = Buffer.from('111') + await expect(getFileSize(buffer)).resolves.toEqual(3) + }) + + it('should return size of ReactNative asset', async () => { + const asset = { uri: 'file://data', name: 'test.txt', type: 'text/plain' } + await expect(getFileSize(asset)).resolves.toEqual(3) + }) + + it('should throw error if no size found', async () => { + const unknownFile = { unknown: 'file' } + await expect( + getFileSize(unknownFile as unknown as Blob) + ).rejects.toThrowError('Unknown file type. Cannot determine file size.') + }) +}) diff --git a/packages/upload-client/test/tools/isFileData.test.ts b/packages/upload-client/test/tools/isFileData.test.ts new file mode 100644 index 000000000..f28921b4d --- /dev/null +++ b/packages/upload-client/test/tools/isFileData.test.ts @@ -0,0 +1,71 @@ +/** + * @jest-environment jsdom + */ +import { expect, jest } from '@jest/globals' +import { + isFileData, + isBlob, + isFile, + isBuffer, + isReactNativeAsset +} from '../../src/tools/isFileData' + +describe('isFileData', () => { + describe('isBlob', () => { + it('should return true if Blob is passed', () => { + expect(isBlob(new Blob(['']))).toBe(true) + }) + it('should return true if File is passed', () => { + expect(isBlob(new File([''], 'test.txt'))).toBe(true) + }) + it('should return false if something else passed', () => { + expect(isBlob(Buffer.from(''))).toBe(false) + expect(isBlob('test')).toBe(false) + expect(isBlob({ uri: 'test' })).toBe(false) + }) + }) + describe('isFile', () => { + it('should return true if File is passed', () => { + expect(isFile(new File([''], 'test.txt'))).toBe(true) + }) + it('should return false if something else passed', () => { + expect(isFile(new Blob(['']))).toBe(false) + expect(isFile(Buffer.from(''))).toBe(false) + expect(isFile('test')).toBe(false) + expect(isFile({ uri: 'test' })).toBe(false) + }) + }) + describe('isBuffer', () => { + it('should return true if Buffer is passed', () => { + expect(isBuffer(Buffer.from(''))).toBe(true) + }) + it('should return false if something else passed', () => { + expect(isBuffer(new File([''], 'test.txt'))).toBe(false) + expect(isBuffer(new Blob(['']))).toBe(false) + expect(isBuffer('test')).toBe(false) + expect(isBuffer({ uri: 'test' })).toBe(false) + }) + }) + describe('isReactNativeAsset', () => { + it('should return true if ReactNative asset is passed', () => { + expect(isReactNativeAsset({ uri: 'test' })).toBe(true) + }) + it('should return false if something else passed', () => { + expect(isReactNativeAsset(Buffer.from(''))).toBe(false) + expect(isReactNativeAsset(new File([''], 'test.txt'))).toBe(false) + expect(isReactNativeAsset(new Blob(['']))).toBe(false) + expect(isReactNativeAsset('test')).toBe(false) + }) + }) + describe('isFileData', () => { + it('should return true if any supported file passed', () => { + expect(isFileData({ uri: 'test' })).toBe(true) + expect(isFileData(Buffer.from(''))).toBe(true) + expect(isFileData(new File([''], 'test.txt'))).toBe(true) + expect(isFileData(new Blob(['']))).toBe(true) + }) + it('should return false if something else passed', () => { + expect(isFileData('test')).toBe(false) + }) + }) +}) From d2bbd760da65692b8e3a3526199d29b6cc080841 Mon Sep 17 00:00:00 2001 From: nd0ut Date: Thu, 29 Dec 2022 16:58:49 +0400 Subject: [PATCH 05/16] feat(upload-client): export all the types --- packages/upload-client/src/UploadClient.ts | 2 +- packages/upload-client/src/api/base.ts | 8 +- packages/upload-client/src/api/fromUrl.ts | 14 +- .../upload-client/src/api/fromUrlStatus.ts | 10 +- .../upload-client/src/api/multipartStart.ts | 8 +- packages/upload-client/src/api/types.ts | 11 +- packages/upload-client/src/index.ts | 63 ++++-- .../upload-client/src/tools/UploadcareFile.ts | 9 +- packages/upload-client/src/tools/errors.ts | 2 +- .../upload-client/src/uploadFile/index.ts | 204 +----------------- .../src/uploadFile/uploadDirect.ts | 10 +- .../src/uploadFile/uploadFile.ts | 195 +++++++++++++++++ .../src/uploadFile/uploadFromUploaded.ts | 6 +- .../src/uploadFile/uploadFromUrl.ts | 6 +- .../src/uploadFile/uploadMultipart.ts | 7 +- .../src/uploadFileGroup/index.ts | 132 +----------- .../src/uploadFileGroup/uploadFileGroup.ts | 131 +++++++++++ .../test/tools/getFileSize.test.ts | 4 +- .../test/tools/isFileData.test.ts | 6 +- .../test/uploadFile/uploadFile.test.ts | 2 +- .../uploadFileGroup/groupFromObject.test.ts | 2 +- .../uploadFileGroup/groupFromUploaded.test.ts | 2 +- .../test/uploadFileGroup/groupFromUrl.test.ts | 2 +- 23 files changed, 435 insertions(+), 401 deletions(-) create mode 100644 packages/upload-client/src/uploadFile/uploadFile.ts create mode 100644 packages/upload-client/src/uploadFileGroup/uploadFileGroup.ts diff --git a/packages/upload-client/src/UploadClient.ts b/packages/upload-client/src/UploadClient.ts index a170ce981..4ff25ec62 100644 --- a/packages/upload-client/src/UploadClient.ts +++ b/packages/upload-client/src/UploadClient.ts @@ -26,7 +26,7 @@ import { FileFromOptions, uploadFile } from './uploadFile' import { FileInfo, GroupId, GroupInfo, Token, Url, Uuid } from './api/types' import { SupportedFileInput, BrowserFile, NodeFile, Settings } from './types' -import uploadFileGroup, { GroupFromOptions } from './uploadFileGroup' +import { uploadFileGroup, GroupFromOptions } from './uploadFileGroup' /** * Populate options with settings. diff --git a/packages/upload-client/src/api/base.ts b/packages/upload-client/src/api/base.ts index 59903e060..4cd86e736 100644 --- a/packages/upload-client/src/api/base.ts +++ b/packages/upload-client/src/api/base.ts @@ -1,4 +1,8 @@ -import { camelizeKeys, CustomUserAgent } from '@uploadcare/api-client-utils' +import { + camelizeKeys, + CustomUserAgent, + Metadata +} from '@uploadcare/api-client-utils' import { defaultSettings } from '../defaultSettings' import request from '../request/request.node' import buildFormData from '../tools/buildFormData' @@ -13,7 +17,7 @@ import { getContentType } from '../tools/getContentType' import { getFileName } from '../tools/getFileName' import { getStoreValue } from '../tools/getStoreValue' import { SupportedFileInput } from '../types' -import { Metadata, ProgressCallback, Uuid } from './types' +import { ProgressCallback, Uuid } from './types' export type BaseResponse = { file: Uuid diff --git a/packages/upload-client/src/api/fromUrl.ts b/packages/upload-client/src/api/fromUrl.ts index f33b92dc1..e2bf63520 100644 --- a/packages/upload-client/src/api/fromUrl.ts +++ b/packages/upload-client/src/api/fromUrl.ts @@ -1,6 +1,10 @@ -import { FileInfo, Metadata, Url } from './types' +import { FileInfo, Url } from './types' import { FailedResponse } from '../request/types' -import { CustomUserAgent, camelizeKeys } from '@uploadcare/api-client-utils' +import { + CustomUserAgent, + camelizeKeys, + Metadata +} from '@uploadcare/api-client-utils' import request from '../request/request.node' import getUrl from '../tools/getUrl' @@ -16,16 +20,16 @@ export enum TypeEnum { FileInfo = 'file_info' } -type TokenResponse = { +export type TokenResponse = { type: TypeEnum.Token token: string } -type FileInfoResponse = { +export type FileInfoResponse = { type: TypeEnum.FileInfo } & FileInfo -type FromUrlSuccessResponse = FileInfoResponse | TokenResponse +export type FromUrlSuccessResponse = FileInfoResponse | TokenResponse type Response = FailedResponse | FromUrlSuccessResponse diff --git a/packages/upload-client/src/api/fromUrlStatus.ts b/packages/upload-client/src/api/fromUrlStatus.ts index 77b934682..6618f67db 100644 --- a/packages/upload-client/src/api/fromUrlStatus.ts +++ b/packages/upload-client/src/api/fromUrlStatus.ts @@ -18,28 +18,28 @@ export enum Status { Success = 'success' } -type StatusUnknownResponse = { +export type StatusUnknownResponse = { status: Status.Unknown } -type StatusWaitingResponse = { +export type StatusWaitingResponse = { status: Status.Waiting } -type StatusProgressResponse = { +export type StatusProgressResponse = { status: Status.Progress size: number done: number total: number | 'unknown' } -type StatusErrorResponse = { +export type StatusErrorResponse = { status: Status.Error error: string errorCode: string } -type StatusSuccessResponse = { +export type StatusSuccessResponse = { status: Status.Success } & FileInfo diff --git a/packages/upload-client/src/api/multipartStart.ts b/packages/upload-client/src/api/multipartStart.ts index 87a9c6bc6..91737bf45 100644 --- a/packages/upload-client/src/api/multipartStart.ts +++ b/packages/upload-client/src/api/multipartStart.ts @@ -1,6 +1,10 @@ import { FailedResponse } from '../request/types' -import { Metadata, Uuid } from './types' -import { CustomUserAgent, camelizeKeys } from '@uploadcare/api-client-utils' +import { Uuid } from './types' +import { + CustomUserAgent, + camelizeKeys, + Metadata +} from '@uploadcare/api-client-utils' import request from '../request/request.node' import buildFormData from '../tools/buildFormData' diff --git a/packages/upload-client/src/api/types.ts b/packages/upload-client/src/api/types.ts index f642f900b..7f82d4e07 100644 --- a/packages/upload-client/src/api/types.ts +++ b/packages/upload-client/src/api/types.ts @@ -1,4 +1,9 @@ -import { ContentInfo, ImageInfo, VideoInfo } from '@uploadcare/api-client-utils' +import { + ContentInfo, + ImageInfo, + VideoInfo, + Metadata +} from '@uploadcare/api-client-utils' export type FileInfo = { size: number @@ -49,7 +54,3 @@ export type UnknownProgressInfo = { export type ProgressCallback = (arg: T) => void - -export type Metadata = { - [key: string]: string -} diff --git a/packages/upload-client/src/index.ts b/packages/upload-client/src/index.ts index 9eb2d7760..a2e3ef39e 100644 --- a/packages/upload-client/src/index.ts +++ b/packages/upload-client/src/index.ts @@ -10,36 +10,71 @@ export { default as multipartUpload } from './api/multipartUpload' export { default as multipartComplete } from './api/multipartComplete' /* High-Level API */ +export { uploadFile, FileFromOptions } from './uploadFile' +export { uploadDirect, DirectOptions } from './uploadFile/uploadDirect' export { - uploadFile, - uploadFromUrl, - uploadDirect, uploadFromUploaded, - uploadMultipart -} from './uploadFile' -export { default as uploadFileGroup } from './uploadFileGroup' + FromUploadedOptions +} from './uploadFile/uploadFromUploaded' +export { uploadFromUrl, UploadFromUrlOptions } from './uploadFile/uploadFromUrl' +export { uploadMultipart, MultipartOptions } from './uploadFile/uploadMultipart' +export { uploadFileGroup, GroupFromOptions } from './uploadFileGroup' /* Helpers */ export { default as UploadClient } from './UploadClient' export { getUserAgent, - GetUserAgentOptions, - UploadcareNetworkError + UploadcareNetworkError, + Metadata, + ContentInfo, + ImageInfo, + MimeInfo, + VideoInfo, + GeoLocation, + AudioInfo, + CustomUserAgent, + CustomUserAgentFn, + CustomUserAgentOptions, + GetUserAgentOptions } from '@uploadcare/api-client-utils' /* Types */ +export { Headers, ErrorRequestInfo } from './request/types' export { UploadcareFile } from './tools/UploadcareFile' export { UploadcareGroup } from './tools/UploadcareGroup' -export { UploadClientError } from './tools/errors' -export { Settings } from './types' +export { UploadClientError, ErrorResponseInfo } from './tools/errors' +export { Settings, SupportedFileInput, ReactNativeFile } from './types' export { NodeFile, BrowserFile, ReactNativeAsset } from './types' export { BaseOptions, BaseResponse } from './api/base' -export { FileInfo, GroupId, GroupInfo, Token, Url, Uuid } from './api/types' +export { + FileInfo, + GroupId, + GroupInfo, + Token, + Url, + Uuid, + ProgressCallback, + ComputableProgressInfo, + UnknownProgressInfo +} from './api/types' export { InfoOptions } from './api/info' -export { FromUrlOptions, FromUrlResponse } from './api/fromUrl' +export { + FromUrlOptions, + FromUrlResponse, + FromUrlSuccessResponse, + FileInfoResponse, + TokenResponse, + TypeEnum +} from './api/fromUrl' export { FromUrlStatusOptions, - FromUrlStatusResponse + FromUrlStatusResponse, + StatusUnknownResponse, + StatusWaitingResponse, + StatusProgressResponse, + StatusErrorResponse, + StatusSuccessResponse, + Status } from './api/fromUrlStatus' export { GroupOptions } from './api/group' export { GroupInfoOptions } from './api/groupInfo' @@ -53,5 +88,3 @@ export { MultipartUploadOptions, MultipartUploadResponse } from './api/multipartUpload' -export { FileFromOptions } from './uploadFile' -export { GroupFromOptions } from './uploadFileGroup' diff --git a/packages/upload-client/src/tools/UploadcareFile.ts b/packages/upload-client/src/tools/UploadcareFile.ts index 95aea921d..346f1e6fb 100644 --- a/packages/upload-client/src/tools/UploadcareFile.ts +++ b/packages/upload-client/src/tools/UploadcareFile.ts @@ -1,5 +1,10 @@ -import { FileInfo, Metadata, Uuid } from '../api/types' -import { ContentInfo, ImageInfo, VideoInfo } from '@uploadcare/api-client-utils' +import { FileInfo, Uuid } from '../api/types' +import { + ContentInfo, + ImageInfo, + VideoInfo, + Metadata +} from '@uploadcare/api-client-utils' export class UploadcareFile { readonly uuid: Uuid diff --git a/packages/upload-client/src/tools/errors.ts b/packages/upload-client/src/tools/errors.ts index 3ca9cf31f..ccea15752 100644 --- a/packages/upload-client/src/tools/errors.ts +++ b/packages/upload-client/src/tools/errors.ts @@ -1,6 +1,6 @@ import { Headers, ErrorRequestInfo } from '../request/types' -type ErrorResponseInfo = { +export type ErrorResponseInfo = { error?: { statusCode: number content: string diff --git a/packages/upload-client/src/uploadFile/index.ts b/packages/upload-client/src/uploadFile/index.ts index 59b325fe9..9367ecdb1 100644 --- a/packages/upload-client/src/uploadFile/index.ts +++ b/packages/upload-client/src/uploadFile/index.ts @@ -1,203 +1 @@ -import defaultSettings from '../defaultSettings' -import uploadDirect from './uploadDirect' -import uploadFromUploaded from './uploadFromUploaded' -import uploadFromUrl from './uploadFromUrl' - -/* Types */ -import { CustomUserAgent } from '@uploadcare/api-client-utils' -import { Metadata, ProgressCallback, Url, Uuid } from '../api/types' -import { getFileSize } from '../tools/getFileSize' -import { isFileData } from '../tools/isFileData' -import { isMultipart } from '../tools/isMultipart' -import { UploadcareFile } from '../tools/UploadcareFile' -import { SupportedFileInput } from '../types' -import { isUrl, isUuid } from './types' -import uploadMultipart from './uploadMultipart' - -export type FileFromOptions = { - publicKey: string - - fileName?: string - baseURL?: string - secureSignature?: string - secureExpire?: string - store?: boolean - - signal?: AbortSignal - onProgress?: ProgressCallback - - source?: string - integration?: string - userAgent?: CustomUserAgent - - retryThrottledRequestMaxTimes?: number - retryNetworkErrorMaxTimes?: number - - contentType?: string - multipartMinFileSize?: number - multipartChunkSize?: number - maxConcurrentRequests?: number - - baseCDN?: string - - checkForUrlDuplicates?: boolean - saveUrlForRecurrentUploads?: boolean - pusherKey?: string - - metadata?: Metadata -} - -/** - * Uploads file from provided data. - */ - -async function uploadFile( - data: SupportedFileInput | Url | Uuid, - { - publicKey, - - fileName, - baseURL = defaultSettings.baseURL, - secureSignature, - secureExpire, - store, - - signal, - onProgress, - - source, - integration, - userAgent, - - retryThrottledRequestMaxTimes, - retryNetworkErrorMaxTimes, - - contentType, - multipartMinFileSize, - multipartChunkSize, - maxConcurrentRequests, - - baseCDN = defaultSettings.baseCDN, - - checkForUrlDuplicates, - saveUrlForRecurrentUploads, - pusherKey, - - metadata - }: FileFromOptions -): Promise { - if (isFileData(data)) { - const fileSize = await getFileSize(data) - - if (isMultipart(fileSize, multipartMinFileSize)) { - return uploadMultipart(data, { - publicKey, - contentType, - multipartChunkSize, - - fileSize, - fileName, - baseURL, - secureSignature, - secureExpire, - store, - - signal, - onProgress, - - source, - integration, - userAgent, - - maxConcurrentRequests, - retryThrottledRequestMaxTimes, - retryNetworkErrorMaxTimes, - - baseCDN, - metadata - }) - } - - return uploadDirect(data, { - publicKey, - - fileName, - contentType, - baseURL, - secureSignature, - secureExpire, - store, - - signal, - onProgress, - - source, - integration, - userAgent, - - retryThrottledRequestMaxTimes, - retryNetworkErrorMaxTimes, - - baseCDN, - metadata - }) - } - - if (isUrl(data)) { - return uploadFromUrl(data, { - publicKey, - - fileName, - baseURL, - baseCDN, - checkForUrlDuplicates, - saveUrlForRecurrentUploads, - secureSignature, - secureExpire, - store, - - signal, - onProgress, - - source, - integration, - userAgent, - - retryThrottledRequestMaxTimes, - retryNetworkErrorMaxTimes, - pusherKey, - metadata - }) - } - - if (isUuid(data)) { - return uploadFromUploaded(data, { - publicKey, - - fileName, - baseURL, - - signal, - onProgress, - - source, - integration, - userAgent, - - retryThrottledRequestMaxTimes, - retryNetworkErrorMaxTimes, - - baseCDN - }) - } - - throw new TypeError(`File uploading from "${data}" is not supported`) -} - -export { - uploadFile, - uploadFromUrl, - uploadDirect, - uploadFromUploaded, - uploadMultipart -} +export { uploadFile, FileFromOptions } from './uploadFile' diff --git a/packages/upload-client/src/uploadFile/uploadDirect.ts b/packages/upload-client/src/uploadFile/uploadDirect.ts index 078c924d9..b7e7ff37a 100644 --- a/packages/upload-client/src/uploadFile/uploadDirect.ts +++ b/packages/upload-client/src/uploadFile/uploadDirect.ts @@ -2,11 +2,11 @@ import base from '../api/base' import { isReadyPoll } from '../tools/isReadyPoll' import { UploadcareFile } from '../tools/UploadcareFile' -import { CustomUserAgent } from '@uploadcare/api-client-utils' -import { Metadata, ProgressCallback } from '../api/types' +import { CustomUserAgent, Metadata } from '@uploadcare/api-client-utils' +import { ProgressCallback } from '../api/types' import { SupportedFileInput } from '../types' -type DirectOptions = { +export type DirectOptions = { publicKey: string fileName?: string @@ -30,7 +30,7 @@ type DirectOptions = { metadata?: Metadata } -const uploadDirect = ( +export const uploadDirect = ( file: SupportedFileInput, { publicKey, @@ -89,5 +89,3 @@ const uploadDirect = ( }) .then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN })) } - -export default uploadDirect diff --git a/packages/upload-client/src/uploadFile/uploadFile.ts b/packages/upload-client/src/uploadFile/uploadFile.ts new file mode 100644 index 000000000..4d01635a3 --- /dev/null +++ b/packages/upload-client/src/uploadFile/uploadFile.ts @@ -0,0 +1,195 @@ +import defaultSettings from '../defaultSettings' +import { uploadDirect } from './uploadDirect' +import { uploadFromUploaded } from './uploadFromUploaded' +import { uploadFromUrl } from './uploadFromUrl' + +/* Types */ +import { CustomUserAgent, Metadata } from '@uploadcare/api-client-utils' +import { ProgressCallback, Url, Uuid } from '../api/types' +import { getFileSize } from '../tools/getFileSize' +import { isFileData } from '../tools/isFileData' +import { isMultipart } from '../tools/isMultipart' +import { UploadcareFile } from '../tools/UploadcareFile' +import { SupportedFileInput } from '../types' +import { isUrl, isUuid } from './types' +import { uploadMultipart } from './uploadMultipart' + +export type FileFromOptions = { + publicKey: string + + fileName?: string + baseURL?: string + secureSignature?: string + secureExpire?: string + store?: boolean + + signal?: AbortSignal + onProgress?: ProgressCallback + + source?: string + integration?: string + userAgent?: CustomUserAgent + + retryThrottledRequestMaxTimes?: number + retryNetworkErrorMaxTimes?: number + + contentType?: string + multipartMinFileSize?: number + multipartChunkSize?: number + maxConcurrentRequests?: number + + baseCDN?: string + + checkForUrlDuplicates?: boolean + saveUrlForRecurrentUploads?: boolean + pusherKey?: string + + metadata?: Metadata +} + +/** + * Uploads file from provided data. + */ + +export async function uploadFile( + data: SupportedFileInput | Url | Uuid, + { + publicKey, + + fileName, + baseURL = defaultSettings.baseURL, + secureSignature, + secureExpire, + store, + + signal, + onProgress, + + source, + integration, + userAgent, + + retryThrottledRequestMaxTimes, + retryNetworkErrorMaxTimes, + + contentType, + multipartMinFileSize, + multipartChunkSize, + maxConcurrentRequests, + + baseCDN = defaultSettings.baseCDN, + + checkForUrlDuplicates, + saveUrlForRecurrentUploads, + pusherKey, + + metadata + }: FileFromOptions +): Promise { + if (isFileData(data)) { + const fileSize = await getFileSize(data) + + if (isMultipart(fileSize, multipartMinFileSize)) { + return uploadMultipart(data, { + publicKey, + contentType, + multipartChunkSize, + + fileSize, + fileName, + baseURL, + secureSignature, + secureExpire, + store, + + signal, + onProgress, + + source, + integration, + userAgent, + + maxConcurrentRequests, + retryThrottledRequestMaxTimes, + retryNetworkErrorMaxTimes, + + baseCDN, + metadata + }) + } + + return uploadDirect(data, { + publicKey, + + fileName, + contentType, + baseURL, + secureSignature, + secureExpire, + store, + + signal, + onProgress, + + source, + integration, + userAgent, + + retryThrottledRequestMaxTimes, + retryNetworkErrorMaxTimes, + + baseCDN, + metadata + }) + } + + if (isUrl(data)) { + return uploadFromUrl(data, { + publicKey, + + fileName, + baseURL, + baseCDN, + checkForUrlDuplicates, + saveUrlForRecurrentUploads, + secureSignature, + secureExpire, + store, + + signal, + onProgress, + + source, + integration, + userAgent, + + retryThrottledRequestMaxTimes, + retryNetworkErrorMaxTimes, + pusherKey, + metadata + }) + } + + if (isUuid(data)) { + return uploadFromUploaded(data, { + publicKey, + + fileName, + baseURL, + + signal, + onProgress, + + source, + integration, + userAgent, + + retryThrottledRequestMaxTimes, + retryNetworkErrorMaxTimes, + + baseCDN + }) + } + + throw new TypeError(`File uploading from "${data}" is not supported`) +} diff --git a/packages/upload-client/src/uploadFile/uploadFromUploaded.ts b/packages/upload-client/src/uploadFile/uploadFromUploaded.ts index e1ea9876e..6527f02ce 100644 --- a/packages/upload-client/src/uploadFile/uploadFromUploaded.ts +++ b/packages/upload-client/src/uploadFile/uploadFromUploaded.ts @@ -6,7 +6,7 @@ import { Uuid } from '..' import { ProgressCallback } from '../api/types' import { CustomUserAgent } from '@uploadcare/api-client-utils' -type FromUploadedOptions = { +export type FromUploadedOptions = { publicKey: string fileName?: string @@ -25,7 +25,7 @@ type FromUploadedOptions = { baseCDN?: string } -const uploadFromUploaded = ( +export const uploadFromUploaded = ( uuid: Uuid, { publicKey, @@ -63,5 +63,3 @@ const uploadFromUploaded = ( return result }) } - -export default uploadFromUploaded diff --git a/packages/upload-client/src/uploadFile/uploadFromUrl.ts b/packages/upload-client/src/uploadFile/uploadFromUrl.ts index 543b38b86..8d31ab148 100644 --- a/packages/upload-client/src/uploadFile/uploadFromUrl.ts +++ b/packages/upload-client/src/uploadFile/uploadFromUrl.ts @@ -87,7 +87,7 @@ function pollStrategy({ }) } -type UploadFromUrlOptions = { +export type UploadFromUrlOptions = { baseCDN?: string onProgress?: ProgressCallback pusherKey?: string @@ -152,7 +152,7 @@ const pushStrategy = ({ }) }) -const uploadFromUrl = ( +export const uploadFromUrl = ( sourceUrl: string, { publicKey, @@ -245,5 +245,3 @@ const uploadFromUrl = ( }) ) .then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN })) - -export default uploadFromUrl diff --git a/packages/upload-client/src/uploadFile/uploadMultipart.ts b/packages/upload-client/src/uploadFile/uploadMultipart.ts index 9fe59c463..90dd4f72f 100644 --- a/packages/upload-client/src/uploadFile/uploadMultipart.ts +++ b/packages/upload-client/src/uploadFile/uploadMultipart.ts @@ -1,4 +1,4 @@ -import { CustomUserAgent } from '@uploadcare/api-client-utils' +import { CustomUserAgent, Metadata } from '@uploadcare/api-client-utils' import multipartComplete from '../api/multipartComplete' import multipartStart from '../api/multipartStart' import multipartUpload, { @@ -13,7 +13,6 @@ import { prepareChunks } from './prepareChunks.node' import { ComputableProgressInfo, - Metadata, ProgressCallback, UnknownProgressInfo } from '../api/types' @@ -65,7 +64,7 @@ const uploadPart = ( retryNetworkErrorMaxTimes }) -const uploadMultipart = async ( +export const uploadMultipart = async ( file: SupportedFileInput, { publicKey, @@ -189,5 +188,3 @@ const uploadMultipart = async ( }) .then((fileInfo) => new UploadcareFile(fileInfo, { baseCDN })) } - -export default uploadMultipart diff --git a/packages/upload-client/src/uploadFileGroup/index.ts b/packages/upload-client/src/uploadFileGroup/index.ts index bf88677b5..75cbc22d0 100644 --- a/packages/upload-client/src/uploadFileGroup/index.ts +++ b/packages/upload-client/src/uploadFileGroup/index.ts @@ -1,131 +1 @@ -import group from '../api/group' -import defaultSettings from '../defaultSettings' -import { UploadcareFile } from '../tools/UploadcareFile' -import { UploadcareGroup } from '../tools/UploadcareGroup' -import { FileFromOptions, uploadFile } from '../uploadFile' - -/* Types */ -import { - ComputableProgressInfo, - ProgressCallback, - UnknownProgressInfo, - Url, - Uuid -} from '../api/types' -import { SupportedFileInput } from '../types' -import { isFileDataArray, isUrlArray, isUuidArray } from './types' - -export type GroupFromOptions = { - jsonpCallback?: string -} - -export default function uploadFileGroup( - data: SupportedFileInput[] | Url[] | Uuid[], - { - publicKey, - - fileName, - baseURL = defaultSettings.baseURL, - secureSignature, - secureExpire, - store, - - signal, - onProgress, - - source, - integration, - userAgent, - - retryThrottledRequestMaxTimes, - retryNetworkErrorMaxTimes, - - contentType, - multipartChunkSize = defaultSettings.multipartChunkSize, - - baseCDN = defaultSettings.baseCDN, - - jsonpCallback - }: FileFromOptions & GroupFromOptions -): Promise { - if (!isFileDataArray(data) && !isUrlArray(data) && !isUuidArray(data)) { - throw new TypeError(`Group uploading from "${data}" is not supported`) - } - - let progressValues: number[] - let isStillComputable = true - const filesCount = data.length - const createProgressHandler = ( - size: number, - index: number - ): ProgressCallback | undefined => { - if (!onProgress) return - if (!progressValues) { - progressValues = Array(size).fill(0) - } - - const normalize = (values: number[]): number => - values.reduce((sum, next) => sum + next) / size - - return (info: ComputableProgressInfo | UnknownProgressInfo): void => { - if (!info.isComputable || !isStillComputable) { - isStillComputable = false - onProgress({ isComputable: false }) - return - } - progressValues[index] = info.value - onProgress({ isComputable: true, value: normalize(progressValues) }) - } - } - - return Promise.all( - (data as SupportedFileInput[]).map( - (file: SupportedFileInput | Url | Uuid, index: number) => - uploadFile(file, { - publicKey, - - fileName, - baseURL, - secureSignature, - secureExpire, - store, - - signal, - onProgress: createProgressHandler(filesCount, index), - - source, - integration, - userAgent, - - retryThrottledRequestMaxTimes, - retryNetworkErrorMaxTimes, - - contentType, - multipartChunkSize, - - baseCDN - }) - ) - ).then((files) => { - const uuids = files.map((file) => file.uuid) - - return group(uuids, { - publicKey, - baseURL, - jsonpCallback, - secureSignature, - secureExpire, - signal, - source, - integration, - userAgent, - retryThrottledRequestMaxTimes, - retryNetworkErrorMaxTimes - }) - .then((groupInfo) => new UploadcareGroup(groupInfo, files)) - .then((group) => { - onProgress && onProgress({ isComputable: true, value: 1 }) - return group - }) - }) -} +export { uploadFileGroup, GroupFromOptions } from './uploadFileGroup' diff --git a/packages/upload-client/src/uploadFileGroup/uploadFileGroup.ts b/packages/upload-client/src/uploadFileGroup/uploadFileGroup.ts new file mode 100644 index 000000000..9a8adf0dc --- /dev/null +++ b/packages/upload-client/src/uploadFileGroup/uploadFileGroup.ts @@ -0,0 +1,131 @@ +import group from '../api/group' +import defaultSettings from '../defaultSettings' +import { UploadcareFile } from '../tools/UploadcareFile' +import { UploadcareGroup } from '../tools/UploadcareGroup' +import { uploadFile, FileFromOptions } from '../uploadFile' + +/* Types */ +import { + ComputableProgressInfo, + ProgressCallback, + UnknownProgressInfo, + Url, + Uuid +} from '../api/types' +import { SupportedFileInput } from '../types' +import { isFileDataArray, isUrlArray, isUuidArray } from './types' + +export type GroupFromOptions = { + jsonpCallback?: string +} + +export function uploadFileGroup( + data: SupportedFileInput[] | Url[] | Uuid[], + { + publicKey, + + fileName, + baseURL = defaultSettings.baseURL, + secureSignature, + secureExpire, + store, + + signal, + onProgress, + + source, + integration, + userAgent, + + retryThrottledRequestMaxTimes, + retryNetworkErrorMaxTimes, + + contentType, + multipartChunkSize = defaultSettings.multipartChunkSize, + + baseCDN = defaultSettings.baseCDN, + + jsonpCallback + }: FileFromOptions & GroupFromOptions +): Promise { + if (!isFileDataArray(data) && !isUrlArray(data) && !isUuidArray(data)) { + throw new TypeError(`Group uploading from "${data}" is not supported`) + } + + let progressValues: number[] + let isStillComputable = true + const filesCount = data.length + const createProgressHandler = ( + size: number, + index: number + ): ProgressCallback | undefined => { + if (!onProgress) return + if (!progressValues) { + progressValues = Array(size).fill(0) + } + + const normalize = (values: number[]): number => + values.reduce((sum, next) => sum + next) / size + + return (info: ComputableProgressInfo | UnknownProgressInfo): void => { + if (!info.isComputable || !isStillComputable) { + isStillComputable = false + onProgress({ isComputable: false }) + return + } + progressValues[index] = info.value + onProgress({ isComputable: true, value: normalize(progressValues) }) + } + } + + return Promise.all( + (data as SupportedFileInput[]).map( + (file: SupportedFileInput | Url | Uuid, index: number) => + uploadFile(file, { + publicKey, + + fileName, + baseURL, + secureSignature, + secureExpire, + store, + + signal, + onProgress: createProgressHandler(filesCount, index), + + source, + integration, + userAgent, + + retryThrottledRequestMaxTimes, + retryNetworkErrorMaxTimes, + + contentType, + multipartChunkSize, + + baseCDN + }) + ) + ).then((files) => { + const uuids = files.map((file) => file.uuid) + + return group(uuids, { + publicKey, + baseURL, + jsonpCallback, + secureSignature, + secureExpire, + signal, + source, + integration, + userAgent, + retryThrottledRequestMaxTimes, + retryNetworkErrorMaxTimes + }) + .then((groupInfo) => new UploadcareGroup(groupInfo, files)) + .then((group) => { + onProgress && onProgress({ isComputable: true, value: 1 }) + return group + }) + }) +} diff --git a/packages/upload-client/test/tools/getFileSize.test.ts b/packages/upload-client/test/tools/getFileSize.test.ts index 6b407e326..d0e358314 100644 --- a/packages/upload-client/test/tools/getFileSize.test.ts +++ b/packages/upload-client/test/tools/getFileSize.test.ts @@ -10,9 +10,7 @@ global.fetch = jest.fn(() => }) ) as unknown as typeof fetch -beforeEach(() => { - ;(fetch as jest.Mock).mockClear() -}) +beforeEach(() => (fetch as jest.Mock).mockClear()) describe('getFileSize', () => { it('should return size of File', async () => { diff --git a/packages/upload-client/test/tools/isFileData.test.ts b/packages/upload-client/test/tools/isFileData.test.ts index f28921b4d..859656913 100644 --- a/packages/upload-client/test/tools/isFileData.test.ts +++ b/packages/upload-client/test/tools/isFileData.test.ts @@ -1,12 +1,12 @@ /** * @jest-environment jsdom */ -import { expect, jest } from '@jest/globals' +import { expect } from '@jest/globals' import { - isFileData, isBlob, - isFile, isBuffer, + isFile, + isFileData, isReactNativeAsset } from '../../src/tools/isFileData' diff --git a/packages/upload-client/test/uploadFile/uploadFile.test.ts b/packages/upload-client/test/uploadFile/uploadFile.test.ts index 9945b5b18..4fee5bbb9 100644 --- a/packages/upload-client/test/uploadFile/uploadFile.test.ts +++ b/packages/upload-client/test/uploadFile/uploadFile.test.ts @@ -1,5 +1,5 @@ import { expect, jest } from '@jest/globals' -import { uploadFile } from '../../src/uploadFile' +import { uploadFile } from '../../src/uploadFile/uploadFile' import * as factory from '../_fixtureFactory' import { getSettingsForTesting } from '../_helpers' diff --git a/packages/upload-client/test/uploadFileGroup/groupFromObject.test.ts b/packages/upload-client/test/uploadFileGroup/groupFromObject.test.ts index f82b12021..61c74bea7 100644 --- a/packages/upload-client/test/uploadFileGroup/groupFromObject.test.ts +++ b/packages/upload-client/test/uploadFileGroup/groupFromObject.test.ts @@ -1,5 +1,5 @@ import * as factory from '../_fixtureFactory' -import uploadFileGroup from '../../src/uploadFileGroup' +import { uploadFileGroup } from '../../src/uploadFileGroup' import { getSettingsForTesting, assertComputableProgress } from '../_helpers' import { UploadClientError } from '../../src/tools/errors' import { jest, expect } from '@jest/globals' diff --git a/packages/upload-client/test/uploadFileGroup/groupFromUploaded.test.ts b/packages/upload-client/test/uploadFileGroup/groupFromUploaded.test.ts index 959328020..cf4bd706d 100644 --- a/packages/upload-client/test/uploadFileGroup/groupFromUploaded.test.ts +++ b/packages/upload-client/test/uploadFileGroup/groupFromUploaded.test.ts @@ -1,6 +1,6 @@ import * as factory from '../_fixtureFactory' import { getSettingsForTesting, assertComputableProgress } from '../_helpers' -import uploadFileGroup from '../../src/uploadFileGroup' +import { uploadFileGroup } from '../../src/uploadFileGroup' import { UploadClientError } from '../../src/tools/errors' import { jest, expect } from '@jest/globals' diff --git a/packages/upload-client/test/uploadFileGroup/groupFromUrl.test.ts b/packages/upload-client/test/uploadFileGroup/groupFromUrl.test.ts index 85082865e..c8103ddd1 100644 --- a/packages/upload-client/test/uploadFileGroup/groupFromUrl.test.ts +++ b/packages/upload-client/test/uploadFileGroup/groupFromUrl.test.ts @@ -4,7 +4,7 @@ import { assertComputableProgress, assertUnknownProgress } from '../_helpers' -import uploadFileGroup from '../../src/uploadFileGroup' +import { uploadFileGroup } from '../../src/uploadFileGroup' import { UploadClientError } from '../../src/tools/errors' import { jest, expect } from '@jest/globals' From 757b1b40aa877e930d50bfabed1dbb2121defa13 Mon Sep 17 00:00:00 2001 From: nd0ut Date: Thu, 29 Dec 2022 17:05:13 +0400 Subject: [PATCH 06/16] chore(upload-client): fix CodeQL warnings --- packages/upload-client/src/tools/buildFormData.ts | 2 +- packages/upload-client/src/tools/identity.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/upload-client/src/tools/buildFormData.ts b/packages/upload-client/src/tools/buildFormData.ts index 36bf264b7..f8c289dba 100644 --- a/packages/upload-client/src/tools/buildFormData.ts +++ b/packages/upload-client/src/tools/buildFormData.ts @@ -63,7 +63,7 @@ function collectParams( ): void { if (isFileValue(inputValue)) { const { name, contentType }: FileOptions = inputValue - const file = transformFile(inputValue.data, name, contentType) as Blob // lgtm [js/superfluous-trailing-arguments] + const file = transformFile(inputValue.data, name, contentType) const options = getFileOptions({ name, contentType }) params.push([inputKey, file, ...options]) } else if (isObjectValue(inputValue)) { diff --git a/packages/upload-client/src/tools/identity.ts b/packages/upload-client/src/tools/identity.ts index 5a3e228b8..08733bdfe 100644 --- a/packages/upload-client/src/tools/identity.ts +++ b/packages/upload-client/src/tools/identity.ts @@ -1,3 +1,4 @@ -export function identity(obj: T): T { +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function identity(obj: T, ..._args: never): T { return obj } From 11ee9914d0bd8f6ff7fb69cb6cfd37e0f924e59e Mon Sep 17 00:00:00 2001 From: nd0ut Date: Thu, 29 Dec 2022 17:25:51 +0400 Subject: [PATCH 07/16] chore(upload-client): fix imports --- packages/upload-client/test/uploadFile/uploadDirect.test.ts | 2 +- .../upload-client/test/uploadFile/uploadFromUploaded.test.ts | 2 +- packages/upload-client/test/uploadFile/uploadFromUrl.test.ts | 2 +- packages/upload-client/test/uploadFile/uploadMultipart.test.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/upload-client/test/uploadFile/uploadDirect.test.ts b/packages/upload-client/test/uploadFile/uploadDirect.test.ts index db5584c40..5d727863d 100644 --- a/packages/upload-client/test/uploadFile/uploadDirect.test.ts +++ b/packages/upload-client/test/uploadFile/uploadDirect.test.ts @@ -1,7 +1,7 @@ import * as factory from '../_fixtureFactory' import { getSettingsForTesting, assertComputableProgress } from '../_helpers' import { UploadClientError } from '../../src/tools/errors' -import uploadDirect from '../../src/uploadFile/uploadDirect' +import { uploadDirect } from '../../src/uploadFile/uploadDirect' import { jest, expect } from '@jest/globals' // TODO: add tests for metadata diff --git a/packages/upload-client/test/uploadFile/uploadFromUploaded.test.ts b/packages/upload-client/test/uploadFile/uploadFromUploaded.test.ts index f6437cad6..8471b5660 100644 --- a/packages/upload-client/test/uploadFile/uploadFromUploaded.test.ts +++ b/packages/upload-client/test/uploadFile/uploadFromUploaded.test.ts @@ -1,7 +1,7 @@ import * as factory from '../_fixtureFactory' import { getSettingsForTesting, assertComputableProgress } from '../_helpers' import { UploadClientError } from '../../src/tools/errors' -import uploadFromUploaded from '../../src/uploadFile/uploadFromUploaded' +import { uploadFromUploaded } from '../../src/uploadFile/uploadFromUploaded' import { jest, expect } from '@jest/globals' describe('uploadFromUploaded', () => { diff --git a/packages/upload-client/test/uploadFile/uploadFromUrl.test.ts b/packages/upload-client/test/uploadFile/uploadFromUrl.test.ts index c8b6b9646..205abaf43 100644 --- a/packages/upload-client/test/uploadFile/uploadFromUrl.test.ts +++ b/packages/upload-client/test/uploadFile/uploadFromUrl.test.ts @@ -7,7 +7,7 @@ import { import { UploadClientError } from '../../src/tools/errors' import http from 'http' import https, { RequestOptions } from 'https' -import uploadFromUrl from '../../src/uploadFile/uploadFromUrl' +import { uploadFromUrl } from '../../src/uploadFile/uploadFromUrl' import { jest, expect } from '@jest/globals' jest.setTimeout(60000) diff --git a/packages/upload-client/test/uploadFile/uploadMultipart.test.ts b/packages/upload-client/test/uploadFile/uploadMultipart.test.ts index dbfd93aab..4200f21b4 100644 --- a/packages/upload-client/test/uploadFile/uploadMultipart.test.ts +++ b/packages/upload-client/test/uploadFile/uploadMultipart.test.ts @@ -1,7 +1,7 @@ import * as factory from '../_fixtureFactory' import { getSettingsForTesting, assertComputableProgress } from '../_helpers' import { UploadClientError } from '../../src/tools/errors' -import uploadMultipart from '../../src/uploadFile/uploadMultipart' +import { uploadMultipart } from '../../src/uploadFile/uploadMultipart' import { jest, expect } from '@jest/globals' jest.setTimeout(60000) From 0843655eb494dab270956a5b626285d08cb8029a Mon Sep 17 00:00:00 2001 From: nd0ut Date: Thu, 29 Dec 2022 17:38:19 +0400 Subject: [PATCH 08/16] chore(upload-client): fix build --- packages/upload-client/src/tools/identity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/upload-client/src/tools/identity.ts b/packages/upload-client/src/tools/identity.ts index 08733bdfe..9ecd7ff38 100644 --- a/packages/upload-client/src/tools/identity.ts +++ b/packages/upload-client/src/tools/identity.ts @@ -1,4 +1,4 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars -export function identity(obj: T, ..._args: never): T { +export function identity(obj: T, ..._args: unknown[]): T { return obj } From 18ccb9503520a263f2930cae7b9cd5794b6c635f Mon Sep 17 00:00:00 2001 From: nd0ut Date: Thu, 29 Dec 2022 18:14:59 +0400 Subject: [PATCH 09/16] chore(upload-client): refactor type exports --- package-lock.json | 860 +++++++++++------- packages/upload-client/package.json | 4 +- packages/upload-client/src/index.ts | 102 ++- .../upload-client/src/uploadFile/index.ts | 2 +- .../src/uploadFileGroup/index.ts | 2 +- 5 files changed, 579 insertions(+), 391 deletions(-) diff --git a/package-lock.json b/package-lock.json index e5aca5ea8..1784c1de2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -997,12 +997,12 @@ } }, "node_modules/@jest/schemas": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.0.2.tgz", - "integrity": "sha512-YVDJZjd4izeTDkij00vHHAymNXQ6WWsdChFRK86qck6Jpr3DCL5W3Is3vslviRlP+bLuMYRLbdp98amMvqudhA==", + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", "dev": true, "dependencies": { - "@sinclair/typebox": "^0.23.3" + "@sinclair/typebox": "^0.24.1" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" @@ -1507,9 +1507,9 @@ "dev": true }, "node_modules/@sinclair/typebox": { - "version": "0.23.5", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.23.5.tgz", - "integrity": "sha512-AFBVi/iT4g20DHoujvMH1aEDn8fGJh4xsRGCP6d8RpLPMqsNPvW01Jcn0QysXTsg++/xj25NmJsGyH9xug/wKg==", + "version": "0.24.51", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", "dev": true }, "node_modules/@sinonjs/commons": { @@ -1785,14 +1785,14 @@ } }, "node_modules/@types/jsdom": { - "version": "16.2.14", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-16.2.14.tgz", - "integrity": "sha512-6BAy1xXEmMuHeAJ4Fv4yXKwBDTGTOseExKE3OaHiNycdHdZw59KfYzrt0DkDluvwmik1HRt6QS7bImxUmpSy+w==", + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", "dev": true, "dependencies": { "@types/node": "*", - "@types/parse5": "*", - "@types/tough-cookie": "*" + "@types/tough-cookie": "*", + "parse5": "^7.0.0" } }, "node_modules/@types/json-schema": { @@ -1868,12 +1868,6 @@ "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "dev": true }, - "node_modules/@types/parse5": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", - "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", - "dev": true - }, "node_modules/@types/prettier": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.6.3.tgz", @@ -2163,9 +2157,9 @@ } }, "node_modules/acorn": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", - "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -2175,25 +2169,13 @@ } }, "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", "dev": true, "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-globals/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" } }, "node_modules/acorn-jsx": { @@ -2206,9 +2188,9 @@ } }, "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true, "engines": { "node": ">=0.4.0" @@ -2569,12 +2551,6 @@ "node": ">=8" } }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, "node_modules/browserslist": { "version": "4.20.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.4.tgz", @@ -3319,19 +3295,6 @@ "node": ">=12" } }, - "node_modules/data-urls/node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dev": true, - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/dataurl-to-blob": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/dataurl-to-blob/-/dataurl-to-blob-0.0.1.tgz", @@ -3405,9 +3368,9 @@ } }, "node_modules/decimal.js": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", - "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", "dev": true }, "node_modules/dedent": { @@ -6285,24 +6248,176 @@ "dev": true }, "node_modules/jest-environment-jsdom": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-28.1.0.tgz", - "integrity": "sha512-8n6P4xiDjNVqTWv6W6vJPuQdLx+ZiA3dbYg7YJ+DPzR+9B61K6pMVJrSs2IxfGRG4J7pyAUA5shQ9G0KEun78w==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.3.1.tgz", + "integrity": "sha512-G46nKgiez2Gy4zvYNhayfMEAFlVHhWfncqvqS6yCd0i+a4NsSUD2WtrKSaYQrYiLQaupHXxCRi8xxVL2M9PbhA==", "dev": true, "dependencies": { - "@jest/environment": "^28.1.0", - "@jest/fake-timers": "^28.1.0", - "@jest/types": "^28.1.0", - "@types/jsdom": "^16.2.4", + "@jest/environment": "^29.3.1", + "@jest/fake-timers": "^29.3.1", + "@jest/types": "^29.3.1", + "@types/jsdom": "^20.0.0", "@types/node": "*", - "jest-mock": "^28.1.0", - "jest-util": "^28.1.0", - "jsdom": "^19.0.0" + "jest-mock": "^29.3.1", + "jest-util": "^29.3.1", + "jsdom": "^20.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jest-environment-jsdom/node_modules/@jest/environment": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.3.1.tgz", + "integrity": "sha512-pMmvfOPmoa1c1QpfFW0nXYtNLpofqo4BrCIk6f2kW4JFeNlHV2t3vd+3iDLf31e2ot2Mec0uqZfmI+U0K2CFag==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.3.1", + "@jest/types": "^29.3.1", + "@types/node": "*", + "jest-mock": "^29.3.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/@jest/fake-timers": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.3.1.tgz", + "integrity": "sha512-iHTL/XpnDlFki9Tq0Q1GGuVeQ8BHZGIYsvCO5eN/O/oJaRzofG9Xndd9HuSDBI/0ZS79pg0iwn07OMTQ7ngF2A==", + "dev": true, + "dependencies": { + "@jest/types": "^29.3.1", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^29.3.1", + "jest-mock": "^29.3.1", + "jest-util": "^29.3.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/@jest/schemas": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", + "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.24.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/@jest/types": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.3.1.tgz", + "integrity": "sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.0.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-environment-jsdom/node_modules/jest-message-util": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.3.1.tgz", + "integrity": "sha512-lMJTbgNcDm5z+6KDxWtqOFWlGQxD6XaYwBqHR8kmpkP+WWWG90I35kdtQHY67Ay5CSuydkTBbJG+tH9JShFCyA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.3.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.3.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/jest-mock": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.3.1.tgz", + "integrity": "sha512-H8/qFDtDVMFvFP4X8NuOT3XRDzOUTz+FeACjufHzsOIBAxivLqkB1PoLCaJx9iPPQ8dZThHPp/G3WRWyMgA3JA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.3.1", + "@types/node": "*", + "jest-util": "^29.3.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/jest-util": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.3.1.tgz", + "integrity": "sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.3.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/pretty-format": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.3.1.tgz", + "integrity": "sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-environment-jsdom/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "node_modules/jest-environment-node": { "version": "28.1.1", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.1.tgz", @@ -6348,12 +6463,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-extended/node_modules/@sinclair/typebox": { - "version": "0.24.51", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", - "dev": true - }, "node_modules/jest-extended/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -6951,12 +7060,12 @@ } }, "node_modules/jest-websocket-mock": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/jest-websocket-mock/-/jest-websocket-mock-2.3.0.tgz", - "integrity": "sha512-kXhRRApRdT4hLG/4rhsfcR0Ke0OzqIsDj0P5t0dl5aiAftShSgoRqp/0pyjS5bh+b9GrIzmfkrV2cn9LxxvSvA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jest-websocket-mock/-/jest-websocket-mock-2.4.0.tgz", + "integrity": "sha512-AOwyuRw6fgROXHxMOiTDl1/T4dh3fV4jDquha5N0csS/PNp742HeTZWPAuKppVRSQ8s3fUGgJHoyZT9JDO0hMA==", "dev": true, "dependencies": { - "jest-diff": "^27.0.2", + "jest-diff": "^28.0.2", "mock-socket": "^9.1.0" } }, @@ -6973,36 +7082,27 @@ } }, "node_modules/jest-websocket-mock/node_modules/diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", "dev": true, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, "node_modules/jest-websocket-mock/node_modules/jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", + "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-websocket-mock/node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, "node_modules/jest-websocket-mock/node_modules/mock-socket": { @@ -7015,19 +7115,26 @@ } }, "node_modules/jest-websocket-mock/node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", "dev": true, "dependencies": { + "@jest/schemas": "^28.1.3", "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" + "react-is": "^18.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, + "node_modules/jest-websocket-mock/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "node_modules/jest-worker": { "version": "28.1.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.1.tgz", @@ -7089,41 +7196,40 @@ } }, "node_modules/jsdom": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-19.0.0.tgz", - "integrity": "sha512-RYAyjCbxy/vri/CfnjUWJQQtZ3LKlLnDqj+9XLNnJPgEGeirZs3hllKR20re8LUZ6o1b1X4Jat+Qd26zmP41+A==", + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", "dev": true, "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.5.0", - "acorn-globals": "^6.0.0", + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", "cssom": "^0.5.0", "cssstyle": "^2.3.0", - "data-urls": "^3.0.1", - "decimal.js": "^10.3.1", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", "domexception": "^4.0.0", "escodegen": "^2.0.0", "form-data": "^4.0.0", "html-encoding-sniffer": "^3.0.0", "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^3.0.0", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^2.0.0", "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^10.0.0", - "ws": "^8.2.3", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", "xml-name-validator": "^4.0.0" }, "engines": { - "node": ">=12" + "node": ">=14" }, "peerDependencies": { "canvas": "^2.5.0" @@ -8465,9 +8571,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", + "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", "dev": true }, "node_modules/object-inspect": { @@ -8733,10 +8839,28 @@ } }, "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } }, "node_modules/parseurl": { "version": "1.3.3", @@ -8992,9 +9116,9 @@ } }, "node_modules/psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", "dev": true }, "node_modules/pump": { @@ -9624,15 +9748,15 @@ "dev": true }, "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", "dev": true, "dependencies": { "xmlchars": "^2.2.0" }, "engines": { - "node": ">=10" + "node": ">=v12.22.7" } }, "node_modules/semver": { @@ -10560,14 +10684,15 @@ } }, "node_modules/tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", "dev": true, "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", - "universalify": "^0.1.2" + "universalify": "^0.2.0", + "url-parse": "^1.5.3" }, "engines": { "node": ">=6" @@ -10680,15 +10805,6 @@ } } }, - "node_modules/ts-node/node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/tsconfig-paths": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", @@ -10914,9 +11030,9 @@ "dev": true }, "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "dev": true, "engines": { "node": ">= 4.0.0" @@ -11041,25 +11157,16 @@ "integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==", "dev": true }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, "node_modules/w3c-xmlserializer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", - "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", "dev": true, "dependencies": { "xml-name-validator": "^4.0.0" }, "engines": { - "node": ">=12" + "node": ">=14" } }, "node_modules/wait-on": { @@ -11150,9 +11257,9 @@ } }, "node_modules/whatwg-url": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-10.0.0.tgz", - "integrity": "sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", "dev": true, "dependencies": { "tr46": "^3.0.0", @@ -11378,9 +11485,9 @@ } }, "node_modules/ws": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.0.tgz", - "integrity": "sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", "engines": { "node": ">=10.0.0" }, @@ -11573,8 +11680,8 @@ "chalk": "^4.1.2", "data-uri-to-buffer": "3.0.1", "dataurl-to-blob": "0.0.1", - "jest-environment-jsdom": "28.1.0", - "jest-websocket-mock": "2.3.0", + "jest-environment-jsdom": "29.3.1", + "jest-websocket-mock": "2.4.0", "koa": "2.13.4", "koa-add-trailing-slashes": "2.0.1", "koa-body": "5.0.0", @@ -12327,12 +12434,12 @@ } }, "@jest/schemas": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.0.2.tgz", - "integrity": "sha512-YVDJZjd4izeTDkij00vHHAymNXQ6WWsdChFRK86qck6Jpr3DCL5W3Is3vslviRlP+bLuMYRLbdp98amMvqudhA==", + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", "dev": true, "requires": { - "@sinclair/typebox": "^0.23.3" + "@sinclair/typebox": "^0.24.1" } }, "@jest/source-map": { @@ -12756,9 +12863,9 @@ "dev": true }, "@sinclair/typebox": { - "version": "0.23.5", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.23.5.tgz", - "integrity": "sha512-AFBVi/iT4g20DHoujvMH1aEDn8fGJh4xsRGCP6d8RpLPMqsNPvW01Jcn0QysXTsg++/xj25NmJsGyH9xug/wKg==", + "version": "0.24.51", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", "dev": true }, "@sinonjs/commons": { @@ -13023,14 +13130,14 @@ } }, "@types/jsdom": { - "version": "16.2.14", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-16.2.14.tgz", - "integrity": "sha512-6BAy1xXEmMuHeAJ4Fv4yXKwBDTGTOseExKE3OaHiNycdHdZw59KfYzrt0DkDluvwmik1HRt6QS7bImxUmpSy+w==", + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", "dev": true, "requires": { "@types/node": "*", - "@types/parse5": "*", - "@types/tough-cookie": "*" + "@types/tough-cookie": "*", + "parse5": "^7.0.0" } }, "@types/json-schema": { @@ -13106,12 +13213,6 @@ "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "dev": true }, - "@types/parse5": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", - "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", - "dev": true - }, "@types/prettier": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.6.3.tgz", @@ -13323,8 +13424,8 @@ "data-uri-to-buffer": "3.0.1", "dataurl-to-blob": "0.0.1", "form-data": "^4.0.0", - "jest-environment-jsdom": "28.1.0", - "jest-websocket-mock": "2.3.0", + "jest-environment-jsdom": "29.3.1", + "jest-websocket-mock": "2.4.0", "koa": "2.13.4", "koa-add-trailing-slashes": "2.0.1", "koa-body": "5.0.0", @@ -13350,27 +13451,19 @@ } }, "acorn": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", - "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", "dev": true }, "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", "dev": true, "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - } + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" } }, "acorn-jsx": { @@ -13381,9 +13474,9 @@ "requires": {} }, "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true }, "add-stream": { @@ -13664,12 +13757,6 @@ "fill-range": "^7.0.1" } }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, "browserslist": { "version": "4.20.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.4.tgz", @@ -14242,18 +14329,6 @@ "abab": "^2.0.6", "whatwg-mimetype": "^3.0.0", "whatwg-url": "^11.0.0" - }, - "dependencies": { - "whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dev": true, - "requires": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - } - } } }, "dataurl-to-blob": { @@ -14311,9 +14386,9 @@ } }, "decimal.js": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", - "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", "dev": true }, "dedent": { @@ -16475,19 +16550,135 @@ } }, "jest-environment-jsdom": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-28.1.0.tgz", - "integrity": "sha512-8n6P4xiDjNVqTWv6W6vJPuQdLx+ZiA3dbYg7YJ+DPzR+9B61K6pMVJrSs2IxfGRG4J7pyAUA5shQ9G0KEun78w==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.3.1.tgz", + "integrity": "sha512-G46nKgiez2Gy4zvYNhayfMEAFlVHhWfncqvqS6yCd0i+a4NsSUD2WtrKSaYQrYiLQaupHXxCRi8xxVL2M9PbhA==", "dev": true, "requires": { - "@jest/environment": "^28.1.0", - "@jest/fake-timers": "^28.1.0", - "@jest/types": "^28.1.0", - "@types/jsdom": "^16.2.4", + "@jest/environment": "^29.3.1", + "@jest/fake-timers": "^29.3.1", + "@jest/types": "^29.3.1", + "@types/jsdom": "^20.0.0", "@types/node": "*", - "jest-mock": "^28.1.0", - "jest-util": "^28.1.0", - "jsdom": "^19.0.0" + "jest-mock": "^29.3.1", + "jest-util": "^29.3.1", + "jsdom": "^20.0.0" + }, + "dependencies": { + "@jest/environment": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.3.1.tgz", + "integrity": "sha512-pMmvfOPmoa1c1QpfFW0nXYtNLpofqo4BrCIk6f2kW4JFeNlHV2t3vd+3iDLf31e2ot2Mec0uqZfmI+U0K2CFag==", + "dev": true, + "requires": { + "@jest/fake-timers": "^29.3.1", + "@jest/types": "^29.3.1", + "@types/node": "*", + "jest-mock": "^29.3.1" + } + }, + "@jest/fake-timers": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.3.1.tgz", + "integrity": "sha512-iHTL/XpnDlFki9Tq0Q1GGuVeQ8BHZGIYsvCO5eN/O/oJaRzofG9Xndd9HuSDBI/0ZS79pg0iwn07OMTQ7ngF2A==", + "dev": true, + "requires": { + "@jest/types": "^29.3.1", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^29.3.1", + "jest-mock": "^29.3.1", + "jest-util": "^29.3.1" + } + }, + "@jest/schemas": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", + "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.24.1" + } + }, + "@jest/types": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.3.1.tgz", + "integrity": "sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==", + "dev": true, + "requires": { + "@jest/schemas": "^29.0.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "jest-message-util": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.3.1.tgz", + "integrity": "sha512-lMJTbgNcDm5z+6KDxWtqOFWlGQxD6XaYwBqHR8kmpkP+WWWG90I35kdtQHY67Ay5CSuydkTBbJG+tH9JShFCyA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.3.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.3.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-mock": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.3.1.tgz", + "integrity": "sha512-H8/qFDtDVMFvFP4X8NuOT3XRDzOUTz+FeACjufHzsOIBAxivLqkB1PoLCaJx9iPPQ8dZThHPp/G3WRWyMgA3JA==", + "dev": true, + "requires": { + "@jest/types": "^29.3.1", + "@types/node": "*", + "jest-util": "^29.3.1" + } + }, + "jest-util": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.3.1.tgz", + "integrity": "sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ==", + "dev": true, + "requires": { + "@jest/types": "^29.3.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, + "pretty-format": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.3.1.tgz", + "integrity": "sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==", + "dev": true, + "requires": { + "@jest/schemas": "^29.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + } } }, "jest-environment-node": { @@ -16523,12 +16714,6 @@ "@sinclair/typebox": "^0.24.1" } }, - "@sinclair/typebox": { - "version": "0.24.51", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", - "dev": true - }, "ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -17000,12 +17185,12 @@ } }, "jest-websocket-mock": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/jest-websocket-mock/-/jest-websocket-mock-2.3.0.tgz", - "integrity": "sha512-kXhRRApRdT4hLG/4rhsfcR0Ke0OzqIsDj0P5t0dl5aiAftShSgoRqp/0pyjS5bh+b9GrIzmfkrV2cn9LxxvSvA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jest-websocket-mock/-/jest-websocket-mock-2.4.0.tgz", + "integrity": "sha512-AOwyuRw6fgROXHxMOiTDl1/T4dh3fV4jDquha5N0csS/PNp742HeTZWPAuKppVRSQ8s3fUGgJHoyZT9JDO0hMA==", "dev": true, "requires": { - "jest-diff": "^27.0.2", + "jest-diff": "^28.0.2", "mock-socket": "^9.1.0" }, "dependencies": { @@ -17016,29 +17201,23 @@ "dev": true }, "diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", "dev": true }, "jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", + "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", "dev": true, "requires": { "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" } }, - "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "dev": true - }, "mock-socket": { "version": "9.1.5", "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.1.5.tgz", @@ -17046,15 +17225,22 @@ "dev": true }, "pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", "dev": true, "requires": { + "@jest/schemas": "^28.1.3", "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" + "react-is": "^18.0.0" } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true } } }, @@ -17109,37 +17295,36 @@ } }, "jsdom": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-19.0.0.tgz", - "integrity": "sha512-RYAyjCbxy/vri/CfnjUWJQQtZ3LKlLnDqj+9XLNnJPgEGeirZs3hllKR20re8LUZ6o1b1X4Jat+Qd26zmP41+A==", + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", "dev": true, "requires": { - "abab": "^2.0.5", - "acorn": "^8.5.0", - "acorn-globals": "^6.0.0", + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", "cssom": "^0.5.0", "cssstyle": "^2.3.0", - "data-urls": "^3.0.1", - "decimal.js": "^10.3.1", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", "domexception": "^4.0.0", "escodegen": "^2.0.0", "form-data": "^4.0.0", "html-encoding-sniffer": "^3.0.0", "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^3.0.0", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^2.0.0", "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^10.0.0", - "ws": "^8.2.3", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", "xml-name-validator": "^4.0.0" } }, @@ -18143,9 +18328,9 @@ } }, "nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", + "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", "dev": true }, "object-inspect": { @@ -18333,10 +18518,21 @@ } }, "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "requires": { + "entities": "^4.4.0" + }, + "dependencies": { + "entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "dev": true + } + } }, "parseurl": { "version": "1.3.3", @@ -18529,9 +18725,9 @@ } }, "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", "dev": true }, "pump": { @@ -18989,9 +19185,9 @@ "dev": true }, "saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", "dev": true, "requires": { "xmlchars": "^2.2.0" @@ -19711,14 +19907,15 @@ "dev": true }, "tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", "dev": true, "requires": { "psl": "^1.1.33", "punycode": "^2.1.1", - "universalify": "^0.1.2" + "universalify": "^0.2.0", + "url-parse": "^1.5.3" } }, "tr46": { @@ -19771,14 +19968,6 @@ "make-error": "^1.1.1", "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" - }, - "dependencies": { - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true - } } }, "tsconfig-paths": { @@ -19948,9 +20137,9 @@ "dev": true }, "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "dev": true }, "unpipe": { @@ -20059,19 +20248,10 @@ "integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==", "dev": true }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, "w3c-xmlserializer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", - "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", "dev": true, "requires": { "xml-name-validator": "^4.0.0" @@ -20148,9 +20328,9 @@ "dev": true }, "whatwg-url": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-10.0.0.tgz", - "integrity": "sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", "dev": true, "requires": { "tr46": "^3.0.0", @@ -20318,9 +20498,9 @@ } }, "ws": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.0.tgz", - "integrity": "sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", "requires": {} }, "xml-name-validator": { diff --git a/packages/upload-client/package.json b/packages/upload-client/package.json index 0e0bef067..3cc954b3d 100644 --- a/packages/upload-client/package.json +++ b/packages/upload-client/package.json @@ -71,8 +71,8 @@ "@types/ws": "8.5.3", "data-uri-to-buffer": "3.0.1", "dataurl-to-blob": "0.0.1", - "jest-environment-jsdom": "28.1.0", - "jest-websocket-mock": "2.3.0", + "jest-environment-jsdom": "29.3.1", + "jest-websocket-mock": "2.4.0", "koa": "2.13.4", "koa-add-trailing-slashes": "2.0.1", "koa-body": "5.0.0", diff --git a/packages/upload-client/src/index.ts b/packages/upload-client/src/index.ts index a2e3ef39e..1b7138010 100644 --- a/packages/upload-client/src/index.ts +++ b/packages/upload-client/src/index.ts @@ -1,24 +1,64 @@ /* Low-Level API */ -export { default as base } from './api/base' -export { default as fromUrl } from './api/fromUrl' -export { default as fromUrlStatus } from './api/fromUrlStatus' -export { default as group } from './api/group' -export { default as groupInfo } from './api/groupInfo' -export { default as info } from './api/info' -export { default as multipartStart } from './api/multipartStart' -export { default as multipartUpload } from './api/multipartUpload' -export { default as multipartComplete } from './api/multipartComplete' +export { + default as base, + type BaseOptions, + type BaseResponse +} from './api/base' +export { + default as fromUrl, + type FromUrlOptions, + type FromUrlResponse, + type FromUrlSuccessResponse, + type FileInfoResponse, + type TokenResponse, + type TypeEnum +} from './api/fromUrl' +export { + default as fromUrlStatus, + type FromUrlStatusOptions, + type FromUrlStatusResponse, + type StatusUnknownResponse, + type StatusWaitingResponse, + type StatusProgressResponse, + type StatusErrorResponse, + type StatusSuccessResponse, + type Status +} from './api/fromUrlStatus' +export { default as group, type GroupOptions } from './api/group' +export { default as groupInfo, type GroupInfoOptions } from './api/groupInfo' +export { default as info, type InfoOptions } from './api/info' +export { + default as multipartStart, + type MultipartStartOptions, + type MultipartStartResponse, + type MultipartPart +} from './api/multipartStart' +export { + default as multipartUpload, + type MultipartUploadOptions, + type MultipartUploadResponse +} from './api/multipartUpload' +export { + default as multipartComplete, + type MultipartCompleteOptions +} from './api/multipartComplete' /* High-Level API */ -export { uploadFile, FileFromOptions } from './uploadFile' -export { uploadDirect, DirectOptions } from './uploadFile/uploadDirect' +export { uploadFile, type FileFromOptions } from './uploadFile' +export { uploadDirect, type DirectOptions } from './uploadFile/uploadDirect' export { uploadFromUploaded, - FromUploadedOptions + type FromUploadedOptions } from './uploadFile/uploadFromUploaded' -export { uploadFromUrl, UploadFromUrlOptions } from './uploadFile/uploadFromUrl' -export { uploadMultipart, MultipartOptions } from './uploadFile/uploadMultipart' -export { uploadFileGroup, GroupFromOptions } from './uploadFileGroup' +export { + uploadFromUrl, + type UploadFromUrlOptions +} from './uploadFile/uploadFromUrl' +export { + uploadMultipart, + type MultipartOptions +} from './uploadFile/uploadMultipart' +export { uploadFileGroup, type GroupFromOptions } from './uploadFileGroup' /* Helpers */ export { default as UploadClient } from './UploadClient' @@ -45,7 +85,6 @@ export { UploadcareGroup } from './tools/UploadcareGroup' export { UploadClientError, ErrorResponseInfo } from './tools/errors' export { Settings, SupportedFileInput, ReactNativeFile } from './types' export { NodeFile, BrowserFile, ReactNativeAsset } from './types' -export { BaseOptions, BaseResponse } from './api/base' export { FileInfo, GroupId, @@ -57,34 +96,3 @@ export { ComputableProgressInfo, UnknownProgressInfo } from './api/types' -export { InfoOptions } from './api/info' -export { - FromUrlOptions, - FromUrlResponse, - FromUrlSuccessResponse, - FileInfoResponse, - TokenResponse, - TypeEnum -} from './api/fromUrl' -export { - FromUrlStatusOptions, - FromUrlStatusResponse, - StatusUnknownResponse, - StatusWaitingResponse, - StatusProgressResponse, - StatusErrorResponse, - StatusSuccessResponse, - Status -} from './api/fromUrlStatus' -export { GroupOptions } from './api/group' -export { GroupInfoOptions } from './api/groupInfo' -export { - MultipartStartOptions, - MultipartStartResponse, - MultipartPart -} from './api/multipartStart' -export { MultipartCompleteOptions } from './api/multipartComplete' -export { - MultipartUploadOptions, - MultipartUploadResponse -} from './api/multipartUpload' diff --git a/packages/upload-client/src/uploadFile/index.ts b/packages/upload-client/src/uploadFile/index.ts index 9367ecdb1..0a9750516 100644 --- a/packages/upload-client/src/uploadFile/index.ts +++ b/packages/upload-client/src/uploadFile/index.ts @@ -1 +1 @@ -export { uploadFile, FileFromOptions } from './uploadFile' +export { uploadFile, type FileFromOptions } from './uploadFile' diff --git a/packages/upload-client/src/uploadFileGroup/index.ts b/packages/upload-client/src/uploadFileGroup/index.ts index 75cbc22d0..f7306c9f1 100644 --- a/packages/upload-client/src/uploadFileGroup/index.ts +++ b/packages/upload-client/src/uploadFileGroup/index.ts @@ -1 +1 @@ -export { uploadFileGroup, GroupFromOptions } from './uploadFileGroup' +export { uploadFileGroup, type GroupFromOptions } from './uploadFileGroup' From 533a1fc0670c7a0e29d92298799ecd8dd2572132 Mon Sep 17 00:00:00 2001 From: nd0ut Date: Thu, 29 Dec 2022 19:08:01 +0400 Subject: [PATCH 10/16] refactor(upload-client): rename some types --- packages/upload-client/src/UploadClient.ts | 10 ++++++++-- packages/upload-client/src/index.ts | 8 ++++++-- packages/upload-client/src/types.ts | 5 ++--- packages/upload-client/src/uploadFile/sliceChunk.ts | 12 ++++++------ packages/upload-client/src/uploadFile/types.ts | 4 ++-- 5 files changed, 24 insertions(+), 15 deletions(-) diff --git a/packages/upload-client/src/UploadClient.ts b/packages/upload-client/src/UploadClient.ts index 4ff25ec62..345dd91e9 100644 --- a/packages/upload-client/src/UploadClient.ts +++ b/packages/upload-client/src/UploadClient.ts @@ -25,7 +25,13 @@ import { UploadcareGroup } from './tools/UploadcareGroup' import { FileFromOptions, uploadFile } from './uploadFile' import { FileInfo, GroupId, GroupInfo, Token, Url, Uuid } from './api/types' -import { SupportedFileInput, BrowserFile, NodeFile, Settings } from './types' +import { + SupportedFileInput, + BrowserFile, + NodeFile, + Settings, + Sliceable +} from './types' import { uploadFileGroup, GroupFromOptions } from './uploadFileGroup' /** @@ -115,7 +121,7 @@ export default class UploadClient { } multipartUpload( - part: BrowserFile | NodeFile, + part: Sliceable, url: MultipartPart, options: Partial = {} ): Promise { diff --git a/packages/upload-client/src/index.ts b/packages/upload-client/src/index.ts index 1b7138010..2dd00a534 100644 --- a/packages/upload-client/src/index.ts +++ b/packages/upload-client/src/index.ts @@ -83,8 +83,12 @@ export { Headers, ErrorRequestInfo } from './request/types' export { UploadcareFile } from './tools/UploadcareFile' export { UploadcareGroup } from './tools/UploadcareGroup' export { UploadClientError, ErrorResponseInfo } from './tools/errors' -export { Settings, SupportedFileInput, ReactNativeFile } from './types' -export { NodeFile, BrowserFile, ReactNativeAsset } from './types' +export { Settings, SupportedFileInput as SupportedFileInput } from './types' +export { + NodeFile as NodeFile, + BrowserFile as BrowserFile, + ReactNativeAsset +} from './types' export { FileInfo, GroupId, diff --git a/packages/upload-client/src/types.ts b/packages/upload-client/src/types.ts index a33df11e6..117efb55a 100644 --- a/packages/upload-client/src/types.ts +++ b/packages/upload-client/src/types.ts @@ -36,7 +36,6 @@ export type ReactNativeAsset = { uri: string name?: string } -export type ReactNativeFile = ReactNativeAsset | Blob -export type SupportedFileInput = BrowserFile | NodeFile | ReactNativeFile -export type AnySlicable = BrowserFile | NodeFile +export type SupportedFileInput = BrowserFile | NodeFile | ReactNativeAsset +export type Sliceable = BrowserFile | NodeFile diff --git a/packages/upload-client/src/uploadFile/sliceChunk.ts b/packages/upload-client/src/uploadFile/sliceChunk.ts index efdbd3a14..43188334c 100644 --- a/packages/upload-client/src/uploadFile/sliceChunk.ts +++ b/packages/upload-client/src/uploadFile/sliceChunk.ts @@ -1,15 +1,15 @@ -import { AnySlicable } from '../types' +import { Sliceable } from '../types' -type Slicable = T & { slice: (start: number, end: number) => Slicable } +type Sliceable = T & { slice: (start: number, end: number) => Sliceable } -export const sliceChunk = ( - file: Slicable, +export const sliceChunk = ( + file: Sliceable, index: number, fileSize: number, chunkSize: number -): Slicable => { +): Sliceable => { const start = chunkSize * index const end = Math.min(start + chunkSize, fileSize) - return file.slice(start, end) as Slicable + return file.slice(start, end) as Sliceable } diff --git a/packages/upload-client/src/uploadFile/types.ts b/packages/upload-client/src/uploadFile/types.ts index 56ec1dbbe..6ce49bdfd 100644 --- a/packages/upload-client/src/uploadFile/types.ts +++ b/packages/upload-client/src/uploadFile/types.ts @@ -1,5 +1,5 @@ import { Url, Uuid } from '..' -import { SupportedFileInput, AnySlicable } from '../types' +import { SupportedFileInput, Sliceable } from '../types' import { isFileData } from '../tools/isFileData' /** @@ -30,4 +30,4 @@ export type PrepareChunks = ( file: SupportedFileInput, fileSize: number, chunkSize: number -) => Promise<(index: number) => AnySlicable> +) => Promise<(index: number) => Sliceable> From da0f877eaf317fffde1cf2e80ce4444a847a8254 Mon Sep 17 00:00:00 2001 From: nd0ut Date: Thu, 29 Dec 2022 19:37:30 +0400 Subject: [PATCH 11/16] chore(upload-client): update readme --- packages/upload-client/README.md | 48 ++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/packages/upload-client/README.md b/packages/upload-client/README.md index dcfee5610..99287650e 100644 --- a/packages/upload-client/README.md +++ b/packages/upload-client/README.md @@ -23,6 +23,7 @@ Node.js and browser. - [High-Level API](#high-level-api) - [Low-Level API](#low-level-api) - [Settings](#settings) +- [React Native](#react-native) - [Testing](#testing) - [Security issues](#security-issues) - [Feedback](#feedback) @@ -124,7 +125,7 @@ interface UploadClient { getSettings(): Settings base( - file: NodeFile | BrowserFile, + file: Blob | File | Buffer | ReactNativeAsset, options: BaseOptions ): Promise @@ -158,12 +159,12 @@ interface UploadClient { ): Promise uploadFile( - data: NodeFile | BrowserFile | Url | Uuid, + data: Blob | File | Buffer | ReactNativeAsset | Url | Uuid, options: FileFromOptions ): Promise uploadFileGroup( - data: (NodeFile | BrowserFile)[] | Url[] | Uuid[], + data: (Blob | File | Buffer | ReactNativeAsset)[] | Url[] | Uuid[], options: FileFromOptions & GroupFromOptions ): Promise } @@ -208,7 +209,7 @@ List of all available API methods: ```typescript base( - file: NodeFile | BrowserFile, + file: Blob | File | Buffer | ReactNativeAsset, options: BaseOptions ): Promise ``` @@ -245,7 +246,7 @@ multipartStart( ```typescript multipartUpload( - part: Buffer | Blob, + part: Buffer | Blob | File, url: MultipartPart, options: MultipartUploadOptions ): Promise @@ -288,6 +289,7 @@ Defaults to `https://upload.uploadcare.com` #### `fileName: string` You can specify an original filename. +It could useful when file input does not contain filename. Defaults to `original`. @@ -408,7 +410,7 @@ Defaults to `4`. ### `contentType: string` -This setting is needed for correct multipart uploads. +This option is useful when file input does not contain content type. Defaults to `application/octet-stream`. @@ -426,6 +428,37 @@ Non-string values will be converted to `string`. `undefined` values will be igno See [docs][uc-file-metadata] and [REST API][uc-docs-metadata] for details. +## React Native + +To be able to use `@uploadcare/upload-client` with React Native, you need to +install [react-native-url-polyfill][react-native-url-polyfill]. + +To prevent [`Error: Cannot create URL for blob`][react-native-url-polyfill-issue] +errors you need to configure your Android app schema to accept blobs - +have a look at this pull request for an example: [5985d7e][react-native-url-polyfill-example]. + +You can use `ReactNativeAsset` as an input to the `@uploadcare/upload-client` like this: + +```ts +type ReactNativeAsset = { + uri: string + type: string + name?: string +} +``` + +```ts +const asset = { uri: 'URI_TO_FILE', name: 'file.txt', type: 'text/plain' } +uploadFile(asset, { publicKey: 'YOUR_PUBLIC_KEY' }) +``` + +Or `Blob` like this: + +```ts +const uri = 'URI_TO_FILE' +const blob = await fetch(uri).then((res) => res.blob()) +uploadFile(blob, { publicKey: 'YOUR_PUBLIC_KEY' }) +``` ## Testing @@ -490,3 +523,6 @@ request at [hello@uploadcare.com][uc-email-hello]. [uc-docs-upload-api]: https://uploadcare.com/docs/api_reference/upload/?utm_source=github&utm_campaign=uploadcare-js-api-clients [uc-docs-metadata]: https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File-Metadata [uc-file-metadata]: https://uploadcare.com/docs/file-metadata/ +[react-native-url-polyfill]: https://github.com/charpeni/react-native-url-polyfill +[react-native-url-polyfill-issue]: https://github.com/charpeni/react-native-url-polyfill/issues/284 +[react-native-url-polyfill-example]: https://github.com/charpeni/react-native-url-polyfill/commit/5985d7efc07b496b829883540d09c6f0be384387 From f9cd4cc840441860b0a74288fa9888affe29309d Mon Sep 17 00:00:00 2001 From: nd0ut Date: Thu, 29 Dec 2022 19:45:06 +0400 Subject: [PATCH 12/16] chore(upload-client): fix `sliceChunk` --- packages/upload-client/src/uploadFile/sliceChunk.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/upload-client/src/uploadFile/sliceChunk.ts b/packages/upload-client/src/uploadFile/sliceChunk.ts index 43188334c..663312bd8 100644 --- a/packages/upload-client/src/uploadFile/sliceChunk.ts +++ b/packages/upload-client/src/uploadFile/sliceChunk.ts @@ -1,15 +1,13 @@ import { Sliceable } from '../types' -type Sliceable = T & { slice: (start: number, end: number) => Sliceable } - export const sliceChunk = ( - file: Sliceable, + file: T, index: number, fileSize: number, chunkSize: number -): Sliceable => { +): T => { const start = chunkSize * index const end = Math.min(start + chunkSize, fileSize) - return file.slice(start, end) as Sliceable + return file.slice(start, end) as T } From b8ba1afa50c8352864801c0569e5126d773c8474 Mon Sep 17 00:00:00 2001 From: nd0ut Date: Thu, 29 Dec 2022 19:46:29 +0400 Subject: [PATCH 13/16] chore(upload-client): remove unused imports --- packages/upload-client/src/UploadClient.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/upload-client/src/UploadClient.ts b/packages/upload-client/src/UploadClient.ts index 345dd91e9..fae353002 100644 --- a/packages/upload-client/src/UploadClient.ts +++ b/packages/upload-client/src/UploadClient.ts @@ -25,14 +25,8 @@ import { UploadcareGroup } from './tools/UploadcareGroup' import { FileFromOptions, uploadFile } from './uploadFile' import { FileInfo, GroupId, GroupInfo, Token, Url, Uuid } from './api/types' -import { - SupportedFileInput, - BrowserFile, - NodeFile, - Settings, - Sliceable -} from './types' -import { uploadFileGroup, GroupFromOptions } from './uploadFileGroup' +import { Settings, Sliceable, SupportedFileInput } from './types' +import { GroupFromOptions, uploadFileGroup } from './uploadFileGroup' /** * Populate options with settings. From 94242cde842692acee5988f5953b9c8415b82c45 Mon Sep 17 00:00:00 2001 From: nd0ut Date: Thu, 29 Dec 2022 20:03:33 +0400 Subject: [PATCH 14/16] chore(upload-client): remove content type and filename warnings --- packages/upload-client/src/tools/getContentType.ts | 9 +-------- packages/upload-client/src/tools/getFileName.ts | 10 ++-------- packages/upload-client/test/tools/getFileName.test.ts | 4 ---- 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/packages/upload-client/src/tools/getContentType.ts b/packages/upload-client/src/tools/getContentType.ts index a3a2c164e..2c0118ca2 100644 --- a/packages/upload-client/src/tools/getContentType.ts +++ b/packages/upload-client/src/tools/getContentType.ts @@ -7,12 +7,5 @@ export const getContentType = (file: SupportedFileInput): string => { if (isBlob(file) || isFile(file) || isReactNativeAsset(file)) { contentType = file.type } - if (contentType) { - return contentType - } - console.warn( - `Cannot determine content type. Using default content type: ${defaultContentType}`, - file - ) - return defaultContentType + return contentType || defaultContentType } diff --git a/packages/upload-client/src/tools/getFileName.ts b/packages/upload-client/src/tools/getFileName.ts index c496dd985..a8de28686 100644 --- a/packages/upload-client/src/tools/getFileName.ts +++ b/packages/upload-client/src/tools/getFileName.ts @@ -12,12 +12,6 @@ export const getFileName = (file: SupportedFileInput): string => { } else if (isReactNativeAsset(file) && file.name) { filename = file.name } - if (filename) { - return filename - } - console.warn( - `Cannot determine filename. Using default filename: ${defaultFilename}`, - file - ) - return defaultFilename + + return filename || defaultFilename } diff --git a/packages/upload-client/test/tools/getFileName.test.ts b/packages/upload-client/test/tools/getFileName.test.ts index 0ae8c046e..44786d96d 100644 --- a/packages/upload-client/test/tools/getFileName.test.ts +++ b/packages/upload-client/test/tools/getFileName.test.ts @@ -16,8 +16,6 @@ describe('getFileName', () => { }) it('should return fallback value if no filename found', () => { - const spy = jest.spyOn(console, 'warn').mockImplementation(() => ({})) - const blob = new Blob(['']) expect(getFileName(blob)).toEqual('original') @@ -29,7 +27,5 @@ describe('getFileName', () => { const asset = { uri: 'file://data', name: '', type: 'text/plain' } expect(getFileName(asset)).toEqual('original') - - expect(spy).toBeCalledTimes(4) }) }) From 87efbb26abd5731d39f0aaa891dd09e9fd1bd7cb Mon Sep 17 00:00:00 2001 From: nd0ut Date: Thu, 29 Dec 2022 20:09:08 +0400 Subject: [PATCH 15/16] chore(upload-client): fix unused import --- packages/upload-client/test/tools/getFileName.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/upload-client/test/tools/getFileName.test.ts b/packages/upload-client/test/tools/getFileName.test.ts index 44786d96d..fcba2b33c 100644 --- a/packages/upload-client/test/tools/getFileName.test.ts +++ b/packages/upload-client/test/tools/getFileName.test.ts @@ -1,7 +1,7 @@ /** * @jest-environment jsdom */ -import { expect, jest } from '@jest/globals' +import { expect } from '@jest/globals' import { getFileName } from '../../src/tools/getFileName' describe('getFileName', () => { From ed04c9c991a3a832cef2263b98f40824bcbbe4d1 Mon Sep 17 00:00:00 2001 From: nd0ut Date: Thu, 29 Dec 2022 20:57:33 +0400 Subject: [PATCH 16/16] chore(upload-client): update readme --- packages/upload-client/README.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/upload-client/README.md b/packages/upload-client/README.md index 99287650e..16867acaf 100644 --- a/packages/upload-client/README.md +++ b/packages/upload-client/README.md @@ -430,6 +430,8 @@ See [docs][uc-file-metadata] and [REST API][uc-docs-metadata] for details. ## React Native +### Prepare + To be able to use `@uploadcare/upload-client` with React Native, you need to install [react-native-url-polyfill][react-native-url-polyfill]. @@ -437,6 +439,27 @@ To prevent [`Error: Cannot create URL for blob`][react-native-url-polyfill-issue errors you need to configure your Android app schema to accept blobs - have a look at this pull request for an example: [5985d7e][react-native-url-polyfill-example]. +1. Add the following code to the `application` section of your `AndroidManifest.xml`: + +```xml + +``` + +2. Add the following code to the `android/app/src/main/res/values/strings.xml`: + +```xml + + MY_REACT_NATIVE_APP_NAME + com.detox.blob + +``` + +### Usage + You can use `ReactNativeAsset` as an input to the `@uploadcare/upload-client` like this: ```ts @@ -457,7 +480,11 @@ Or `Blob` like this: ```ts const uri = 'URI_TO_FILE' const blob = await fetch(uri).then((res) => res.blob()) -uploadFile(blob, { publicKey: 'YOUR_PUBLIC_KEY' }) +uploadFile(blob, { + publicKey: 'YOUR_PUBLIC_KEY', + fileName: 'file.txt', + contentType: 'text/plain' +}) ``` ## Testing