@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 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
+ }