Skip to content

Commit d07dda0

Browse files
authored
Merge pull request #455 from uploadcare/fix/react-native
Better react-native support
2 parents b8a6b18 + ed04c9c commit d07dda0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1509
-903
lines changed

package-lock.json

+520-340
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/upload-client/README.md

+69-6
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Node.js and browser.
2323
- [High-Level API](#high-level-api)
2424
- [Low-Level API](#low-level-api)
2525
- [Settings](#settings)
26+
- [React Native](#react-native)
2627
- [Testing](#testing)
2728
- [Security issues](#security-issues)
2829
- [Feedback](#feedback)
@@ -124,7 +125,7 @@ interface UploadClient {
124125
getSettings(): Settings
125126

126127
base(
127-
file: NodeFile | BrowserFile,
128+
file: Blob | File | Buffer | ReactNativeAsset,
128129
options: BaseOptions
129130
): Promise<BaseResponse>
130131

@@ -158,12 +159,12 @@ interface UploadClient {
158159
): Promise<FileInfo>
159160

160161
uploadFile(
161-
data: NodeFile | BrowserFile | Url | Uuid,
162+
data: Blob | File | Buffer | ReactNativeAsset | Url | Uuid,
162163
options: FileFromOptions
163164
): Promise<UploadcareFile>
164165

165166
uploadFileGroup(
166-
data: (NodeFile | BrowserFile)[] | Url[] | Uuid[],
167+
data: (Blob | File | Buffer | ReactNativeAsset)[] | Url[] | Uuid[],
167168
options: FileFromOptions & GroupFromOptions
168169
): Promise<UploadcareGroup>
169170
}
@@ -208,7 +209,7 @@ List of all available API methods:
208209

209210
```typescript
210211
base(
211-
file: NodeFile | BrowserFile,
212+
file: Blob | File | Buffer | ReactNativeAsset,
212213
options: BaseOptions
213214
): Promise<BaseResponse>
214215
```
@@ -245,7 +246,7 @@ multipartStart(
245246

246247
```typescript
247248
multipartUpload(
248-
part: Buffer | Blob,
249+
part: Buffer | Blob | File,
249250
url: MultipartPart,
250251
options: MultipartUploadOptions
251252
): Promise<MultipartUploadResponse>
@@ -288,6 +289,7 @@ Defaults to `https://upload.uploadcare.com`
288289
#### `fileName: string`
289290

290291
You can specify an original filename.
292+
It could useful when file input does not contain filename.
291293

292294
Defaults to `original`.
293295

@@ -408,7 +410,7 @@ Defaults to `4`.
408410
409411
### `contentType: string`
410412
411-
This setting is needed for correct multipart uploads.
413+
This option is useful when file input does not contain content type.
412414
413415
Defaults to `application/octet-stream`.
414416
@@ -426,6 +428,64 @@ Non-string values will be converted to `string`. `undefined` values will be igno
426428
427429
See [docs][uc-file-metadata] and [REST API][uc-docs-metadata] for details.
428430
431+
## React Native
432+
433+
### Prepare
434+
435+
To be able to use `@uploadcare/upload-client` with React Native, you need to
436+
install [react-native-url-polyfill][react-native-url-polyfill].
437+
438+
To prevent [`Error: Cannot create URL for blob`][react-native-url-polyfill-issue]
439+
errors you need to configure your Android app schema to accept blobs -
440+
have a look at this pull request for an example: [5985d7e][react-native-url-polyfill-example].
441+
442+
1. Add the following code to the `application` section of your `AndroidManifest.xml`:
443+
444+
```xml
445+
<provider
446+
android:name="com.facebook.react.modules.blob.BlobProvider"
447+
android:authorities="@string/blob_provider_authority"
448+
android:exported="false"
449+
/>
450+
```
451+
452+
2. Add the following code to the `android/app/src/main/res/values/strings.xml`:
453+
454+
```xml
455+
<resources>
456+
<string name="app_name">MY_REACT_NATIVE_APP_NAME</string>
457+
<string name="blob_provider_authority">com.detox.blob</string>
458+
</resources>
459+
```
460+
461+
### Usage
462+
463+
You can use `ReactNativeAsset` as an input to the `@uploadcare/upload-client` like this:
464+
465+
```ts
466+
type ReactNativeAsset = {
467+
uri: string
468+
type: string
469+
name?: string
470+
}
471+
```
472+
473+
```ts
474+
const asset = { uri: 'URI_TO_FILE', name: 'file.txt', type: 'text/plain' }
475+
uploadFile(asset, { publicKey: 'YOUR_PUBLIC_KEY' })
476+
```
477+
478+
Or `Blob` like this:
479+
480+
```ts
481+
const uri = 'URI_TO_FILE'
482+
const blob = await fetch(uri).then((res) => res.blob())
483+
uploadFile(blob, {
484+
publicKey: 'YOUR_PUBLIC_KEY',
485+
fileName: 'file.txt',
486+
contentType: 'text/plain'
487+
})
488+
```
429489

430490
## Testing
431491

@@ -490,3 +550,6 @@ request at [[email protected]][uc-email-hello].
490550
[uc-docs-upload-api]: https://uploadcare.com/docs/api_reference/upload/?utm_source=github&utm_campaign=uploadcare-js-api-clients
491551
[uc-docs-metadata]: https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File-Metadata
492552
[uc-file-metadata]: https://uploadcare.com/docs/file-metadata/
553+
[react-native-url-polyfill]: https://github.com/charpeni/react-native-url-polyfill
554+
[react-native-url-polyfill-issue]: https://github.com/charpeni/react-native-url-polyfill/issues/284
555+
[react-native-url-polyfill-example]: https://github.com/charpeni/react-native-url-polyfill/commit/5985d7efc07b496b829883540d09c6f0be384387

packages/upload-client/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@
7171
"@types/ws": "8.5.3",
7272
"data-uri-to-buffer": "3.0.1",
7373
"dataurl-to-blob": "0.0.1",
74-
"jest-environment-jsdom": "28.1.0",
75-
"jest-websocket-mock": "2.3.0",
74+
"jest-environment-jsdom": "29.3.1",
75+
"jest-websocket-mock": "2.4.0",
7676
"koa": "2.13.4",
7777
"koa-add-trailing-slashes": "2.0.1",
7878
"koa-body": "5.0.0",

packages/upload-client/src/UploadClient.ts

+6-7
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@ import { UploadcareGroup } from './tools/UploadcareGroup'
2525
import { FileFromOptions, uploadFile } from './uploadFile'
2626

2727
import { FileInfo, GroupId, GroupInfo, Token, Url, Uuid } from './api/types'
28-
import { BrowserFile, NodeFile } from './request/types'
29-
import { Settings } from './types'
30-
import uploadFileGroup, { GroupFromOptions } from './uploadFileGroup'
28+
import { Settings, Sliceable, SupportedFileInput } from './types'
29+
import { GroupFromOptions, uploadFileGroup } from './uploadFileGroup'
3130

3231
/**
3332
* Populate options with settings.
@@ -56,7 +55,7 @@ export default class UploadClient {
5655
}
5756

5857
base(
59-
file: NodeFile | BrowserFile,
58+
file: SupportedFileInput,
6059
options: Partial<BaseOptions> = {}
6160
): Promise<BaseResponse> {
6261
const settings = this.getSettings()
@@ -116,7 +115,7 @@ export default class UploadClient {
116115
}
117116

118117
multipartUpload(
119-
part: Buffer | Blob,
118+
part: Sliceable,
120119
url: MultipartPart,
121120
options: Partial<MultipartUploadOptions> = {}
122121
): Promise<MultipartUploadResponse> {
@@ -142,7 +141,7 @@ export default class UploadClient {
142141
}
143142

144143
uploadFile(
145-
data: NodeFile | BrowserFile | Url | Uuid,
144+
data: SupportedFileInput | Url | Uuid,
146145
options: Partial<FileFromOptions> = {}
147146
): Promise<UploadcareFile> {
148147
const settings = this.getSettings()
@@ -151,7 +150,7 @@ export default class UploadClient {
151150
}
152151

153152
uploadFileGroup(
154-
data: (NodeFile | BrowserFile)[] | Url[] | Uuid[],
153+
data: SupportedFileInput[] | Url[] | Uuid[],
155154
options: Partial<FileFromOptions & GroupFromOptions> = {}
156155
): Promise<UploadcareGroup> {
157156
const settings = this.getSettings()

packages/upload-client/src/api/base.ts

+15-8
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1+
import {
2+
camelizeKeys,
3+
CustomUserAgent,
4+
Metadata
5+
} from '@uploadcare/api-client-utils'
6+
import { defaultSettings } from '../defaultSettings'
17
import request from '../request/request.node'
28
import buildFormData from '../tools/buildFormData'
9+
import { UploadClientError } from '../tools/errors'
310
import getUrl from '../tools/getUrl'
4-
import { defaultSettings, defaultFilename } from '../defaultSettings'
511
import { getUserAgent } from '../tools/getUserAgent'
6-
import { camelizeKeys, CustomUserAgent } from '@uploadcare/api-client-utils'
7-
import { UploadClientError } from '../tools/errors'
812
import { retryIfFailed } from '../tools/retryIfFailed'
913

1014
/* Types */
11-
import { Uuid, ProgressCallback, Metadata } from './types'
12-
import { FailedResponse, NodeFile, BrowserFile } from '../request/types'
15+
import { FailedResponse } from '../request/types'
16+
import { getContentType } from '../tools/getContentType'
17+
import { getFileName } from '../tools/getFileName'
1318
import { getStoreValue } from '../tools/getStoreValue'
19+
import { SupportedFileInput } from '../types'
20+
import { ProgressCallback, Uuid } from './types'
1421

1522
export type BaseResponse = {
1623
file: Uuid
@@ -45,7 +52,7 @@ export type BaseOptions = {
4552
* Can be canceled and has progress.
4653
*/
4754
export default function base(
48-
file: NodeFile | BrowserFile,
55+
file: SupportedFileInput,
4956
{
5057
publicKey,
5158
fileName,
@@ -77,8 +84,8 @@ export default function base(
7784
data: buildFormData({
7885
file: {
7986
data: file,
80-
name: fileName ?? (file as File).name ?? defaultFilename,
81-
contentType
87+
name: fileName || getFileName(file),
88+
contentType: contentType || getContentType(file)
8289
},
8390
UPLOADCARE_PUB_KEY: publicKey,
8491
UPLOADCARE_STORE: getStoreValue(store),

packages/upload-client/src/api/fromUrl.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
import { FileInfo, Metadata, Url } from './types'
1+
import { FileInfo, Url } from './types'
22
import { FailedResponse } from '../request/types'
3-
import { CustomUserAgent, camelizeKeys } from '@uploadcare/api-client-utils'
3+
import {
4+
CustomUserAgent,
5+
camelizeKeys,
6+
Metadata
7+
} from '@uploadcare/api-client-utils'
48

59
import request from '../request/request.node'
610
import getUrl from '../tools/getUrl'
@@ -16,16 +20,16 @@ export enum TypeEnum {
1620
FileInfo = 'file_info'
1721
}
1822

19-
type TokenResponse = {
23+
export type TokenResponse = {
2024
type: TypeEnum.Token
2125
token: string
2226
}
2327

24-
type FileInfoResponse = {
28+
export type FileInfoResponse = {
2529
type: TypeEnum.FileInfo
2630
} & FileInfo
2731

28-
type FromUrlSuccessResponse = FileInfoResponse | TokenResponse
32+
export type FromUrlSuccessResponse = FileInfoResponse | TokenResponse
2933

3034
type Response = FailedResponse | FromUrlSuccessResponse
3135

packages/upload-client/src/api/fromUrlStatus.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -18,28 +18,28 @@ export enum Status {
1818
Success = 'success'
1919
}
2020

21-
type StatusUnknownResponse = {
21+
export type StatusUnknownResponse = {
2222
status: Status.Unknown
2323
}
2424

25-
type StatusWaitingResponse = {
25+
export type StatusWaitingResponse = {
2626
status: Status.Waiting
2727
}
2828

29-
type StatusProgressResponse = {
29+
export type StatusProgressResponse = {
3030
status: Status.Progress
3131
size: number
3232
done: number
3333
total: number | 'unknown'
3434
}
3535

36-
type StatusErrorResponse = {
36+
export type StatusErrorResponse = {
3737
status: Status.Error
3838
error: string
3939
errorCode: string
4040
}
4141

42-
type StatusSuccessResponse = {
42+
export type StatusSuccessResponse = {
4343
status: Status.Success
4444
} & FileInfo
4545

packages/upload-client/src/api/multipartStart.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { FailedResponse } from '../request/types'
2-
import { Metadata, Uuid } from './types'
3-
import { CustomUserAgent, camelizeKeys } from '@uploadcare/api-client-utils'
2+
import { Uuid } from './types'
3+
import {
4+
CustomUserAgent,
5+
camelizeKeys,
6+
Metadata
7+
} from '@uploadcare/api-client-utils'
48

59
import request from '../request/request.node'
610
import buildFormData from '../tools/buildFormData'
@@ -75,9 +79,9 @@ export default function multipartStart(
7579
'X-UC-User-Agent': getUserAgent({ publicKey, integration, userAgent })
7680
},
7781
data: buildFormData({
78-
filename: fileName ?? defaultFilename,
82+
filename: fileName || defaultFilename,
7983
size: size,
80-
content_type: contentType ?? defaultContentType,
84+
content_type: contentType || defaultContentType,
8185
part_size: multipartChunkSize,
8286
UPLOADCARE_STORE: getStoreValue(store),
8387
UPLOADCARE_PUB_KEY: publicKey,

packages/upload-client/src/api/multipartUpload.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { MultipartPart } from './multipartStart'
22

33
import request from '../request/request.node'
44

5-
import { ComputableProgressInfo, ProgressCallback } from './types'
6-
import { NodeFile, BrowserFile } from '../request/types'
7-
import { retryIfFailed } from '../tools/retryIfFailed'
85
import defaultSettings from '../defaultSettings'
6+
import { retryIfFailed } from '../tools/retryIfFailed'
7+
import { SupportedFileInput } from '../types'
8+
import { ComputableProgressInfo, ProgressCallback } from './types'
99

1010
export type MultipartUploadOptions = {
1111
publicKey?: string
@@ -25,7 +25,7 @@ export type MultipartUploadResponse = {
2525
*/
2626

2727
export default function multipartUpload(
28-
part: NodeFile | BrowserFile,
28+
part: SupportedFileInput,
2929
url: MultipartPart,
3030
{
3131
signal,

packages/upload-client/src/api/types.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { ContentInfo, ImageInfo, VideoInfo } from '@uploadcare/api-client-utils'
1+
import {
2+
ContentInfo,
3+
ImageInfo,
4+
VideoInfo,
5+
Metadata
6+
} from '@uploadcare/api-client-utils'
27

38
export type FileInfo = {
49
size: number
@@ -49,7 +54,3 @@ export type UnknownProgressInfo = {
4954

5055
export type ProgressCallback<T = ComputableProgressInfo | UnknownProgressInfo> =
5156
(arg: T) => void
52-
53-
export type Metadata = {
54-
[key: string]: string
55-
}

0 commit comments

Comments
 (0)