@uploadista/react-native-bare 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +440 -0
- package/package.json +31 -0
- package/src/index.ts +16 -0
- package/src/services/abort-controller-factory.ts +33 -0
- package/src/services/base64-service.ts +29 -0
- package/src/services/create-react-native-services.ts +76 -0
- package/src/services/file-reader-service.ts +144 -0
- package/src/services/http-client.ts +242 -0
- package/src/services/id-generation-service.ts +14 -0
- package/src/services/index.ts +15 -0
- package/src/services/native-file-system-provider.ts +247 -0
- package/src/services/platform-service.ts +71 -0
- package/src/services/storage-service.ts +62 -0
- package/src/services/websocket-factory.ts +62 -0
- package/tsconfig.json +21 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 uploadista
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
# @uploadista/react-native-bare
|
|
2
|
+
|
|
3
|
+
Bare React Native file upload client for Uploadista - for projects without Expo.
|
|
4
|
+
|
|
5
|
+
This package provides Uploadista integration for bare (non-Expo) React Native projects. It uses native libraries for file system access, camera, and gallery operations.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Bare React Native Support** - Works in non-Expo React Native projects
|
|
10
|
+
- **Native Libraries** - Uses industry-standard React Native libraries
|
|
11
|
+
- **iOS & Android** - Full support for iOS and Android platforms
|
|
12
|
+
- **Progress Tracking** - Real-time upload progress and metrics
|
|
13
|
+
- **Camera & Gallery** - Native camera and photo library integration
|
|
14
|
+
- **File Picking** - Document and file selection
|
|
15
|
+
- **TypeScript** - Full type safety and IDE support
|
|
16
|
+
- **Resumable Uploads** - Automatic resume on network interruption
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
### 1. Install Dependencies
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @uploadista/react-native-bare @uploadista/client-core
|
|
24
|
+
npm install react-native-document-picker react-native-image-picker rn-fetch-blob
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 2. Link Native Modules
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
cd ios && pod install && cd ..
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
For Android, usually no additional linking is needed with modern React Native versions.
|
|
34
|
+
|
|
35
|
+
### 3. Request Permissions
|
|
36
|
+
|
|
37
|
+
#### iOS (Info.plist)
|
|
38
|
+
|
|
39
|
+
```xml
|
|
40
|
+
<dict>
|
|
41
|
+
<key>NSCameraUsageDescription</key>
|
|
42
|
+
<string>Allow app to access camera for photo uploads</string>
|
|
43
|
+
<key>NSPhotoLibraryUsageDescription</key>
|
|
44
|
+
<string>Allow app to access photo library for uploads</string>
|
|
45
|
+
<key>NSDocumentsUsageDescription</key>
|
|
46
|
+
<string>Allow app to access documents for uploads</string>
|
|
47
|
+
</dict>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
#### Android (AndroidManifest.xml)
|
|
51
|
+
|
|
52
|
+
```xml
|
|
53
|
+
<uses-permission android:name="android.permission.CAMERA" />
|
|
54
|
+
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
|
55
|
+
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
|
56
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Also request permissions at runtime:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { PermissionsAndroid } from 'react-native'
|
|
63
|
+
|
|
64
|
+
const requestPermissions = async () => {
|
|
65
|
+
try {
|
|
66
|
+
const granted = await PermissionsAndroid.requestMultiple([
|
|
67
|
+
PermissionsAndroid.PERMISSIONS.CAMERA,
|
|
68
|
+
PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
|
|
69
|
+
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
|
|
70
|
+
])
|
|
71
|
+
return granted
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.warn(err)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Quick Start
|
|
79
|
+
|
|
80
|
+
### 1. Create Client
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import { createUploadistaClient } from '@uploadista/react-native-bare'
|
|
84
|
+
|
|
85
|
+
const client = createUploadistaClient({
|
|
86
|
+
baseUrl: 'https://api.example.com',
|
|
87
|
+
storageId: 'my-storage',
|
|
88
|
+
chunkSize: 1024 * 1024, // 1MB chunks
|
|
89
|
+
})
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 2. Setup Provider
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import React from 'react'
|
|
96
|
+
import { BareRNUploadistaProvider } from '@uploadista/react-native-bare'
|
|
97
|
+
|
|
98
|
+
export default function App() {
|
|
99
|
+
return (
|
|
100
|
+
<BareRNUploadistaProvider client={client}>
|
|
101
|
+
<YourApp />
|
|
102
|
+
</BareRNUploadistaProvider>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 3. Use Upload Hooks
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { useUpload } from '@uploadista/react-native-bare'
|
|
111
|
+
import { View, Button, Text } from 'react-native'
|
|
112
|
+
import DocumentPicker from 'react-native-document-picker'
|
|
113
|
+
|
|
114
|
+
export function FileUploadScreen() {
|
|
115
|
+
const { state, upload } = useUpload()
|
|
116
|
+
|
|
117
|
+
const handlePickFile = async () => {
|
|
118
|
+
try {
|
|
119
|
+
const result = await DocumentPicker.pick({
|
|
120
|
+
type: [DocumentPicker.types.allFiles],
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
await upload(result[0])
|
|
124
|
+
} catch (err) {
|
|
125
|
+
console.error('Error picking file:', err)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<View>
|
|
131
|
+
<Button title="Pick File" onPress={handlePickFile} />
|
|
132
|
+
|
|
133
|
+
{state.status === 'uploading' && (
|
|
134
|
+
<Text>Uploading: {Math.round(state.progress)}%</Text>
|
|
135
|
+
)}
|
|
136
|
+
{state.status === 'success' && (
|
|
137
|
+
<Text>Upload complete! File ID: {state.result?.id}</Text>
|
|
138
|
+
)}
|
|
139
|
+
{state.status === 'error' && (
|
|
140
|
+
<Text>Error: {state.error?.message}</Text>
|
|
141
|
+
)}
|
|
142
|
+
</View>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Camera Upload Example
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { useUpload } from '@uploadista/react-native-bare'
|
|
151
|
+
import { View, Button, Text, Image } from 'react-native'
|
|
152
|
+
import ImagePicker from 'react-native-image-picker'
|
|
153
|
+
|
|
154
|
+
export function CameraUploadScreen() {
|
|
155
|
+
const { state, upload } = useUpload()
|
|
156
|
+
const [photoUri, setPhotoUri] = React.useState<string | null>(null)
|
|
157
|
+
|
|
158
|
+
const handleTakePhoto = () => {
|
|
159
|
+
ImagePicker.launchCamera(
|
|
160
|
+
{
|
|
161
|
+
mediaType: 'photo',
|
|
162
|
+
includeBase64: false,
|
|
163
|
+
maxWidth: 1920,
|
|
164
|
+
maxHeight: 1920,
|
|
165
|
+
quality: 0.8,
|
|
166
|
+
},
|
|
167
|
+
async (response) => {
|
|
168
|
+
if (response.didCancel) {
|
|
169
|
+
console.log('User cancelled image picker')
|
|
170
|
+
} else if (response.errorMessage) {
|
|
171
|
+
console.log('ImagePicker Error:', response.errorMessage)
|
|
172
|
+
} else if (response.assets && response.assets[0]) {
|
|
173
|
+
const asset = response.assets[0]
|
|
174
|
+
setPhotoUri(asset.uri)
|
|
175
|
+
|
|
176
|
+
// Upload the photo
|
|
177
|
+
await upload({
|
|
178
|
+
uri: asset.uri!,
|
|
179
|
+
name: asset.fileName || 'photo.jpg',
|
|
180
|
+
type: asset.type || 'image/jpeg',
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return (
|
|
188
|
+
<View>
|
|
189
|
+
<Button title="Take Photo" onPress={handleTakePhoto} />
|
|
190
|
+
|
|
191
|
+
{photoUri && !state.status.uploading && <Image source={{ uri: photoUri }} />}
|
|
192
|
+
|
|
193
|
+
{state.status === 'uploading' && (
|
|
194
|
+
<Text>Uploading: {Math.round(state.progress)}%</Text>
|
|
195
|
+
)}
|
|
196
|
+
{state.status === 'success' && <Text>Photo uploaded successfully!</Text>}
|
|
197
|
+
{state.status === 'error' && (
|
|
198
|
+
<Text>Upload failed: {state.error?.message}</Text>
|
|
199
|
+
)}
|
|
200
|
+
</View>
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Photo Library Upload Example
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import { useUpload } from '@uploadista/react-native-bare'
|
|
209
|
+
import { View, Button, Text } from 'react-native'
|
|
210
|
+
import ImagePicker from 'react-native-image-picker'
|
|
211
|
+
|
|
212
|
+
export function PhotoLibraryUploadScreen() {
|
|
213
|
+
const { state, upload } = useUpload()
|
|
214
|
+
|
|
215
|
+
const handlePickPhoto = () => {
|
|
216
|
+
ImagePicker.launchImageLibrary(
|
|
217
|
+
{
|
|
218
|
+
mediaType: 'photo',
|
|
219
|
+
selectionLimit: 1,
|
|
220
|
+
maxWidth: 1920,
|
|
221
|
+
maxHeight: 1920,
|
|
222
|
+
},
|
|
223
|
+
async (response) => {
|
|
224
|
+
if (!response.didCancel && response.assets && response.assets[0]) {
|
|
225
|
+
const asset = response.assets[0]
|
|
226
|
+
await upload({
|
|
227
|
+
uri: asset.uri!,
|
|
228
|
+
name: asset.fileName || 'photo.jpg',
|
|
229
|
+
type: asset.type || 'image/jpeg',
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<View>
|
|
238
|
+
<Button title="Pick from Library" onPress={handlePickPhoto} />
|
|
239
|
+
{state.status === 'uploading' && (
|
|
240
|
+
<Text>Progress: {Math.round(state.progress)}%</Text>
|
|
241
|
+
)}
|
|
242
|
+
</View>
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## API Reference
|
|
248
|
+
|
|
249
|
+
### Client Factory
|
|
250
|
+
|
|
251
|
+
#### `createUploadistaClient(options)`
|
|
252
|
+
|
|
253
|
+
Creates a bare React Native Uploadista client.
|
|
254
|
+
|
|
255
|
+
**Options:**
|
|
256
|
+
- `baseUrl` (string) - API server URL
|
|
257
|
+
- `storageId` (string) - Storage backend identifier
|
|
258
|
+
- `chunkSize` (number, optional) - Chunk size in bytes (default: 1MB)
|
|
259
|
+
- `concurrency` (number, optional) - Concurrent chunks (default: 3)
|
|
260
|
+
- `maxRetries` (number, optional) - Max retries per chunk (default: 3)
|
|
261
|
+
- `timeout` (number, optional) - Request timeout (default: 30s)
|
|
262
|
+
|
|
263
|
+
### Hooks
|
|
264
|
+
|
|
265
|
+
#### `useUpload(options?)`
|
|
266
|
+
|
|
267
|
+
Single file upload with progress tracking.
|
|
268
|
+
|
|
269
|
+
**Returns:**
|
|
270
|
+
- `state` - Upload state (readonly)
|
|
271
|
+
- `status` - 'idle' | 'uploading' | 'success' | 'error' | 'aborted'
|
|
272
|
+
- `progress` - Progress 0-100
|
|
273
|
+
- `bytesUploaded` - Bytes uploaded
|
|
274
|
+
- `totalBytes` - Total file size
|
|
275
|
+
- `result` - Upload result on success
|
|
276
|
+
- `error` - Error on failure
|
|
277
|
+
- `upload(file, options?)` - Start upload
|
|
278
|
+
- `abort()` - Cancel upload
|
|
279
|
+
- `reset()` - Reset to idle state
|
|
280
|
+
- `retry()` - Retry failed upload
|
|
281
|
+
|
|
282
|
+
**Options:**
|
|
283
|
+
- `onProgress(event)` - Progress callback
|
|
284
|
+
- `onComplete(result)` - Success callback
|
|
285
|
+
- `onError(error)` - Error callback
|
|
286
|
+
|
|
287
|
+
#### `useMultiUpload(options?)`
|
|
288
|
+
|
|
289
|
+
Multiple concurrent uploads.
|
|
290
|
+
|
|
291
|
+
**Returns:**
|
|
292
|
+
- `uploads` - Array of upload items
|
|
293
|
+
- `stats` - Aggregate statistics
|
|
294
|
+
- `totalFiles` - Total files
|
|
295
|
+
- `completedFiles` - Successfully uploaded
|
|
296
|
+
- `failedFiles` - Failed uploads
|
|
297
|
+
- `totalBytes` - Total size
|
|
298
|
+
- `uploadedBytes` - Bytes uploaded
|
|
299
|
+
- `totalProgress` - Overall progress 0-100
|
|
300
|
+
- `allComplete` - All finished
|
|
301
|
+
- `hasErrors` - Any failures
|
|
302
|
+
- `add(files)` - Add files to queue
|
|
303
|
+
- `remove(uploadId)` - Remove upload
|
|
304
|
+
- `clear()` - Clear all uploads
|
|
305
|
+
- `retryFailed()` - Retry failures
|
|
306
|
+
|
|
307
|
+
### Supported File Types
|
|
308
|
+
|
|
309
|
+
Works with file objects from:
|
|
310
|
+
- **react-native-document-picker** - Documents
|
|
311
|
+
- **react-native-image-picker** - Photos and videos
|
|
312
|
+
- **Native file URIs** - File system paths
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
// All these formats are supported:
|
|
316
|
+
{
|
|
317
|
+
uri: 'file:///path/to/file',
|
|
318
|
+
name: 'document.pdf',
|
|
319
|
+
type: 'application/pdf'
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Troubleshooting
|
|
324
|
+
|
|
325
|
+
### Permission Denied on Android
|
|
326
|
+
|
|
327
|
+
Request runtime permissions:
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
import { PermissionsAndroid } from 'react-native'
|
|
331
|
+
|
|
332
|
+
const requestPermissions = async () => {
|
|
333
|
+
try {
|
|
334
|
+
const permissions = [
|
|
335
|
+
PermissionsAndroid.PERMISSIONS.CAMERA,
|
|
336
|
+
PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
|
|
337
|
+
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
|
|
338
|
+
]
|
|
339
|
+
const granted = await PermissionsAndroid.requestMultiple(permissions)
|
|
340
|
+
return Object.values(granted).every(
|
|
341
|
+
(p) => p === PermissionsAndroid.RESULTS.GRANTED,
|
|
342
|
+
)
|
|
343
|
+
} catch (err) {
|
|
344
|
+
console.warn(err)
|
|
345
|
+
return false
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Use in component
|
|
350
|
+
const hasPermissions = await requestPermissions()
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Native Module Not Linked
|
|
354
|
+
|
|
355
|
+
If you see "Native module not linked" errors:
|
|
356
|
+
|
|
357
|
+
1. **iOS:** Ensure pods are installed:
|
|
358
|
+
```bash
|
|
359
|
+
cd ios && pod install && cd ..
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
2. **Android:** Check gradle sync is complete:
|
|
363
|
+
```bash
|
|
364
|
+
./gradlew clean build
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Upload Fails on Large Files
|
|
368
|
+
|
|
369
|
+
Use smaller chunks for large files:
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
const client = createUploadistaClient({
|
|
373
|
+
baseUrl: 'https://api.example.com',
|
|
374
|
+
storageId: 'my-storage',
|
|
375
|
+
chunkSize: 512 * 1024, // 512KB chunks for large files
|
|
376
|
+
concurrency: 2,
|
|
377
|
+
})
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Memory Issues
|
|
381
|
+
|
|
382
|
+
1. Compress images before upload:
|
|
383
|
+
```typescript
|
|
384
|
+
// Use react-native-image-crop-picker or similar
|
|
385
|
+
const compressedImage = await compress(image)
|
|
386
|
+
await upload(compressedImage)
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
2. Limit concurrent uploads:
|
|
390
|
+
```typescript
|
|
391
|
+
const client = createUploadistaClient({
|
|
392
|
+
concurrency: 2, // Lower concurrency for low-RAM devices
|
|
393
|
+
})
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## Performance Tips
|
|
397
|
+
|
|
398
|
+
1. **Chunk Size** - Use 2-5MB for fast networks, 512KB-1MB for slow networks
|
|
399
|
+
2. **Concurrency** - Balance between 2-4 based on device capabilities
|
|
400
|
+
3. **Image Compression** - Pre-compress images to reduce upload size
|
|
401
|
+
4. **Resumption** - Automatic, but test on slow networks
|
|
402
|
+
5. **Background Tasks** - Consider using native background task libraries
|
|
403
|
+
|
|
404
|
+
## Platform-Specific Notes
|
|
405
|
+
|
|
406
|
+
### iOS
|
|
407
|
+
|
|
408
|
+
- Minimum iOS 11
|
|
409
|
+
- Requires camera and photo library permissions
|
|
410
|
+
- Large file uploads should use smaller chunks due to memory constraints
|
|
411
|
+
|
|
412
|
+
### Android
|
|
413
|
+
|
|
414
|
+
- Minimum Android 6 (API 23)
|
|
415
|
+
- Request runtime permissions at app start
|
|
416
|
+
- Scoped storage (Android 10+) automatically handled
|
|
417
|
+
- Large files may need smaller chunks on low-RAM devices
|
|
418
|
+
|
|
419
|
+
## Related Packages
|
|
420
|
+
|
|
421
|
+
- **[@uploadista/react-native-core](../react-native-core/)** - Core React Native types
|
|
422
|
+
- **[@uploadista/expo](../expo/)** - For Expo-managed projects
|
|
423
|
+
- **[@uploadista/client-core](../../core/)** - Core client types
|
|
424
|
+
|
|
425
|
+
## TypeScript Support
|
|
426
|
+
|
|
427
|
+
Full TypeScript support with strict typing:
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
import type {
|
|
431
|
+
UploadState,
|
|
432
|
+
UseUploadOptions,
|
|
433
|
+
UseMultiUploadOptions,
|
|
434
|
+
BareRNUploadInput,
|
|
435
|
+
} from '@uploadista/react-native-bare'
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
## License
|
|
439
|
+
|
|
440
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@uploadista/react-native-bare",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"description": "Bare React Native Client for Uploadista",
|
|
5
|
+
"version": "0.0.3",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Uploadista",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@uploadista/react-native-core": "0.0.3"
|
|
13
|
+
},
|
|
14
|
+
"peerDependencies": {
|
|
15
|
+
"react": ">=16.8.0",
|
|
16
|
+
"react-native": ">=0.71.0",
|
|
17
|
+
"react-native-document-picker": ">=9.0.0",
|
|
18
|
+
"react-native-image-picker": ">=7.0.0",
|
|
19
|
+
"rn-fetch-blob": ">=0.12.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/react": ">=18.0.0",
|
|
23
|
+
"@types/react-native": ">=0.71.0",
|
|
24
|
+
"@uploadista/typescript-config": "0.0.3"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"format": "biome format --write ./src",
|
|
28
|
+
"lint": "biome lint --write ./src",
|
|
29
|
+
"check": "biome check --write ./src"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bare React Native file system provider for Uploadista
|
|
3
|
+
*
|
|
4
|
+
* This package provides the bare React Native implementation of the FileSystemProvider interface,
|
|
5
|
+
* using native modules for file access in non-Expo React Native environments.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { NativeFileSystemProvider } from '@uploadista/clients-react-native-bare'
|
|
10
|
+
*
|
|
11
|
+
* const provider = new NativeFileSystemProvider()
|
|
12
|
+
* const file = await provider.pickImage()
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export { NativeFileSystemProvider } from "./services/native-file-system-provider";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AbortControllerFactory,
|
|
3
|
+
AbortControllerLike,
|
|
4
|
+
AbortSignalLike,
|
|
5
|
+
} from "@uploadista/client-core";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* React Native AbortController implementation that wraps native AbortController
|
|
9
|
+
* React Native provides an AbortController API that is compatible with the browser AbortController API
|
|
10
|
+
*/
|
|
11
|
+
class ReactNativeAbortController implements AbortControllerLike {
|
|
12
|
+
private native: AbortController;
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
this.native = new AbortController();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get signal(): AbortSignalLike {
|
|
19
|
+
return this.native.signal;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
abort(_reason?: unknown): void {
|
|
23
|
+
this.native.abort();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Factory for creating React Native AbortController instances
|
|
29
|
+
*/
|
|
30
|
+
export const createReactNativeAbortControllerFactory =
|
|
31
|
+
(): AbortControllerFactory => ({
|
|
32
|
+
create: (): AbortControllerLike => new ReactNativeAbortController(),
|
|
33
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Base64Service } from "@uploadista/client-core";
|
|
2
|
+
import { fromBase64 as decode, toBase64 as encode } from "js-base64";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* React Native-specific implementation of Base64Service using js-base64 library
|
|
6
|
+
* React Native doesn't have native btoa/atob functions, so we use js-base64
|
|
7
|
+
*/
|
|
8
|
+
export function createReactNativeBase64Service(): Base64Service {
|
|
9
|
+
return {
|
|
10
|
+
toBase64(data: ArrayBuffer): string {
|
|
11
|
+
// Convert ArrayBuffer to Uint8Array
|
|
12
|
+
const uint8Array = new Uint8Array(data);
|
|
13
|
+
// Convert Uint8Array to string
|
|
14
|
+
const binary = Array.from(uint8Array)
|
|
15
|
+
.map((byte) => String.fromCharCode(byte))
|
|
16
|
+
.join("");
|
|
17
|
+
return encode(binary);
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
fromBase64(data: string): ArrayBuffer {
|
|
21
|
+
const binary = decode(data);
|
|
22
|
+
const uint8Array = new Uint8Array(binary.length);
|
|
23
|
+
for (let i = 0; i < binary.length; i++) {
|
|
24
|
+
uint8Array[i] = binary.charCodeAt(i);
|
|
25
|
+
}
|
|
26
|
+
return uint8Array.buffer;
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ConnectionPoolConfig,
|
|
3
|
+
createInMemoryStorageService,
|
|
4
|
+
type ServiceContainer,
|
|
5
|
+
} from "@uploadista/client-core";
|
|
6
|
+
import type { ReactNativeUploadInput } from "@/types/upload-input";
|
|
7
|
+
import { createReactNativeAbortControllerFactory } from "./abort-controller-factory";
|
|
8
|
+
import { createReactNativeBase64Service } from "./base64-service";
|
|
9
|
+
import { createReactNativeFileReaderService } from "./file-reader-service";
|
|
10
|
+
import { createReactNativeHttpClient } from "./http-client";
|
|
11
|
+
import { createReactNativeIdGenerationService } from "./id-generation-service";
|
|
12
|
+
import { createAsyncStorageService } from "./storage-service";
|
|
13
|
+
import { createReactNativeWebSocketFactory } from "./websocket-factory";
|
|
14
|
+
|
|
15
|
+
export interface ReactNativeServiceOptions {
|
|
16
|
+
/**
|
|
17
|
+
* HTTP client configuration for connection pooling
|
|
18
|
+
*/
|
|
19
|
+
connectionPooling?: ConnectionPoolConfig;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Whether to use AsyncStorage for persistence
|
|
23
|
+
* If false, uses in-memory storage
|
|
24
|
+
* @default true
|
|
25
|
+
*/
|
|
26
|
+
useAsyncStorage?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Creates a service container with React Native-specific implementations
|
|
31
|
+
* of all required services for the upload client
|
|
32
|
+
*
|
|
33
|
+
* @param options - Configuration options for React Native services
|
|
34
|
+
* @returns ServiceContainer with React Native implementations
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* import { createReactNativeServices } from '@uploadista/react-native/services';
|
|
39
|
+
*
|
|
40
|
+
* const services = createReactNativeServices({
|
|
41
|
+
* useAsyncStorage: true,
|
|
42
|
+
* connectionPooling: {
|
|
43
|
+
* maxConnectionsPerHost: 6,
|
|
44
|
+
* connectionTimeout: 30000,
|
|
45
|
+
* }
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export function createReactNativeServices(
|
|
50
|
+
options: ReactNativeServiceOptions = {},
|
|
51
|
+
): ServiceContainer<ReactNativeUploadInput> {
|
|
52
|
+
const { connectionPooling, useAsyncStorage = true } = options;
|
|
53
|
+
|
|
54
|
+
// Create storage service (AsyncStorage or in-memory fallback)
|
|
55
|
+
const storage = useAsyncStorage
|
|
56
|
+
? createAsyncStorageService()
|
|
57
|
+
: createInMemoryStorageService();
|
|
58
|
+
|
|
59
|
+
// Create other services
|
|
60
|
+
const idGeneration = createReactNativeIdGenerationService();
|
|
61
|
+
const httpClient = createReactNativeHttpClient(connectionPooling);
|
|
62
|
+
const fileReader = createReactNativeFileReaderService();
|
|
63
|
+
const base64 = createReactNativeBase64Service();
|
|
64
|
+
const websocket = createReactNativeWebSocketFactory();
|
|
65
|
+
const abortController = createReactNativeAbortControllerFactory();
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
storage,
|
|
69
|
+
idGeneration,
|
|
70
|
+
httpClient,
|
|
71
|
+
fileReader,
|
|
72
|
+
base64,
|
|
73
|
+
websocket,
|
|
74
|
+
abortController,
|
|
75
|
+
};
|
|
76
|
+
}
|