Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better react-native support #455

Merged
merged 16 commits into from
Jan 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
860 changes: 520 additions & 340 deletions package-lock.json

Large diffs are not rendered by default.

75 changes: 69 additions & 6 deletions packages/upload-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -124,7 +125,7 @@ interface UploadClient {
getSettings(): Settings

base(
file: NodeFile | BrowserFile,
file: Blob | File | Buffer | ReactNativeAsset,
options: BaseOptions
): Promise<BaseResponse>

Expand Down Expand Up @@ -158,12 +159,12 @@ interface UploadClient {
): Promise<FileInfo>

uploadFile(
data: NodeFile | BrowserFile | Url | Uuid,
data: Blob | File | Buffer | ReactNativeAsset | Url | Uuid,
options: FileFromOptions
): Promise<UploadcareFile>

uploadFileGroup(
data: (NodeFile | BrowserFile)[] | Url[] | Uuid[],
data: (Blob | File | Buffer | ReactNativeAsset)[] | Url[] | Uuid[],
options: FileFromOptions & GroupFromOptions
): Promise<UploadcareGroup>
}
Expand Down Expand Up @@ -208,7 +209,7 @@ List of all available API methods:

```typescript
base(
file: NodeFile | BrowserFile,
file: Blob | File | Buffer | ReactNativeAsset,
options: BaseOptions
): Promise<BaseResponse>
```
Expand Down Expand Up @@ -245,7 +246,7 @@ multipartStart(

```typescript
multipartUpload(
part: Buffer | Blob,
part: Buffer | Blob | File,
url: MultipartPart,
options: MultipartUploadOptions
): Promise<MultipartUploadResponse>
Expand Down Expand Up @@ -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`.

Expand Down Expand Up @@ -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`.

Expand All @@ -426,6 +428,64 @@ 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

### Prepare

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].

1. Add the following code to the `application` section of your `AndroidManifest.xml`:

```xml
<provider
android:name="com.facebook.react.modules.blob.BlobProvider"
android:authorities="@string/blob_provider_authority"
android:exported="false"
/>
```

2. Add the following code to the `android/app/src/main/res/values/strings.xml`:

```xml
<resources>
<string name="app_name">MY_REACT_NATIVE_APP_NAME</string>
<string name="blob_provider_authority">com.detox.blob</string>
</resources>
```

### Usage

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',
fileName: 'file.txt',
contentType: 'text/plain'
})
```

## Testing

Expand Down Expand Up @@ -490,3 +550,6 @@ request at [[email protected]][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
4 changes: 2 additions & 2 deletions packages/upload-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
13 changes: 6 additions & 7 deletions packages/upload-client/src/UploadClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ 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 uploadFileGroup, { GroupFromOptions } from './uploadFileGroup'
import { Settings, Sliceable, SupportedFileInput } from './types'
import { GroupFromOptions, uploadFileGroup } from './uploadFileGroup'

/**
* Populate options with settings.
Expand Down Expand Up @@ -56,7 +55,7 @@ export default class UploadClient {
}

base(
file: NodeFile | BrowserFile,
file: SupportedFileInput,
options: Partial<BaseOptions> = {}
): Promise<BaseResponse> {
const settings = this.getSettings()
Expand Down Expand Up @@ -116,7 +115,7 @@ export default class UploadClient {
}

multipartUpload(
part: Buffer | Blob,
part: Sliceable,
url: MultipartPart,
options: Partial<MultipartUploadOptions> = {}
): Promise<MultipartUploadResponse> {
Expand All @@ -142,7 +141,7 @@ export default class UploadClient {
}

uploadFile(
data: NodeFile | BrowserFile | Url | Uuid,
data: SupportedFileInput | Url | Uuid,
options: Partial<FileFromOptions> = {}
): Promise<UploadcareFile> {
const settings = this.getSettings()
Expand All @@ -151,7 +150,7 @@ export default class UploadClient {
}

uploadFileGroup(
data: (NodeFile | BrowserFile)[] | Url[] | Uuid[],
data: SupportedFileInput[] | Url[] | Uuid[],
options: Partial<FileFromOptions & GroupFromOptions> = {}
): Promise<UploadcareGroup> {
const settings = this.getSettings()
Expand Down
23 changes: 15 additions & 8 deletions packages/upload-client/src/api/base.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import {
camelizeKeys,
CustomUserAgent,
Metadata
} 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 } 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, NodeFile, BrowserFile } from '../request/types'
import { FailedResponse } from '../request/types'
import { getContentType } from '../tools/getContentType'
import { getFileName } from '../tools/getFileName'
import { getStoreValue } from '../tools/getStoreValue'
import { SupportedFileInput } from '../types'
import { ProgressCallback, Uuid } from './types'

export type BaseResponse = {
file: Uuid
Expand Down Expand Up @@ -45,7 +52,7 @@ export type BaseOptions = {
* Can be canceled and has progress.
*/
export default function base(
file: NodeFile | BrowserFile,
file: SupportedFileInput,
{
publicKey,
fileName,
Expand Down Expand Up @@ -77,8 +84,8 @@ export default function base(
data: buildFormData({
file: {
data: file,
name: fileName ?? (file as File).name ?? defaultFilename,
contentType
name: fileName || getFileName(file),
contentType: contentType || getContentType(file)
},
UPLOADCARE_PUB_KEY: publicKey,
UPLOADCARE_STORE: getStoreValue(store),
Expand Down
14 changes: 9 additions & 5 deletions packages/upload-client/src/api/fromUrl.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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

Expand Down
10 changes: 5 additions & 5 deletions packages/upload-client/src/api/fromUrlStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 8 additions & 4 deletions packages/upload-client/src/api/multipartStart.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -75,9 +79,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,
Expand Down
8 changes: 4 additions & 4 deletions packages/upload-client/src/api/multipartUpload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { MultipartPart } from './multipartStart'

import request from '../request/request.node'

import { ComputableProgressInfo, ProgressCallback } from './types'
import { NodeFile, BrowserFile } from '../request/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
Expand All @@ -25,7 +25,7 @@ export type MultipartUploadResponse = {
*/

export default function multipartUpload(
part: NodeFile | BrowserFile,
part: SupportedFileInput,
url: MultipartPart,
{
signal,
Expand Down
11 changes: 6 additions & 5 deletions packages/upload-client/src/api/types.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -49,7 +54,3 @@ export type UnknownProgressInfo = {

export type ProgressCallback<T = ComputableProgressInfo | UnknownProgressInfo> =
(arg: T) => void

export type Metadata = {
[key: string]: string
}
Loading