@uploadista/react-native-core 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/README.md ADDED
@@ -0,0 +1,426 @@
1
+ # @uploadista/react-native
2
+
3
+ React Native client for Uploadista with mobile-optimized upload patterns and support for both Expo-managed and bare React Native environments.
4
+
5
+ ## Overview
6
+
7
+ `@uploadista/react-native` provides a first-class React Native integration for Uploadista, enabling seamless file uploads and flow management on iOS and Android. The client abstracts platform differences and provides idiomatic React Native patterns for:
8
+
9
+ - **Single file uploads** - Upload one file with progress tracking
10
+ - **Multi-file uploads** - Concurrent uploads with batch management
11
+ - **Flow uploads** - Orchestrated uploads through processing pipelines
12
+ - **Camera uploads** - Direct camera capture and upload
13
+ - **Gallery uploads** - Photo/video library selection and upload
14
+ - **File picker uploads** - Generic document selection
15
+
16
+ ## Features
17
+
18
+ - ✅ **Expo and Bare RN Support** - Works with both Expo-managed and bare React Native workflows
19
+ - ✅ **iOS & Android** - Full support for iOS 14+ and Android 7+
20
+ - ✅ **File System Abstraction** - Pluggable providers for different environments
21
+ - ✅ **Mobile Patterns** - Camera, gallery, and file picker integrations
22
+ - ✅ **Progress Tracking** - Real-time upload progress and metrics
23
+ - ✅ **WebSocket Support** - Flow uploads with real-time events
24
+ - ✅ **Type Safe** - Full TypeScript support with strict typing
25
+ - ✅ **Zero External Dependencies** - Only optional peer dependencies
26
+
27
+ ## Installation
28
+
29
+ ### Prerequisites
30
+
31
+ - React Native 0.71+
32
+ - React 16.8+ (for hooks)
33
+ - Either Expo SDK 51+ or bare React Native environment
34
+
35
+ ### NPM Installation
36
+
37
+ ```bash
38
+ npm install @uploadista/react-native @uploadista/client @uploadista/core
39
+ ```
40
+
41
+ ### Yarn Installation
42
+
43
+ ```bash
44
+ yarn add @uploadista/react-native @uploadista/client @uploadista/core
45
+ ```
46
+
47
+ ### pnpm Installation
48
+
49
+ ```bash
50
+ pnpm add @uploadista/react-native @uploadista/client @uploadista/core
51
+ ```
52
+
53
+ ## Quick Start
54
+
55
+ ### Expo Setup
56
+
57
+ For Expo managed projects, install the required Expo modules:
58
+
59
+ ```bash
60
+ expo install expo-document-picker expo-image-picker expo-camera expo-file-system
61
+ ```
62
+
63
+ Then in your app:
64
+
65
+ ```tsx
66
+ import { UploadistaProvider } from '@uploadista/react-native';
67
+ import { UploadClient } from '@uploadista/client';
68
+
69
+ const client = new UploadClient({
70
+ apiUrl: 'https://api.example.com',
71
+ });
72
+
73
+ export default function App() {
74
+ return (
75
+ <UploadistaProvider client={client}>
76
+ <YourAppContent />
77
+ </UploadistaProvider>
78
+ );
79
+ }
80
+ ```
81
+
82
+ ### Bare React Native Setup
83
+
84
+ For bare React Native, install native dependencies:
85
+
86
+ ```bash
87
+ npm install react-native-document-picker react-native-image-picker rn-fetch-blob
88
+
89
+ # For iOS
90
+ cd ios && pod install && cd ..
91
+
92
+ # For Android (usually automatic)
93
+ ```
94
+
95
+ Then configure the provider:
96
+
97
+ ```tsx
98
+ import { UploadistaProvider } from '@uploadista/react-native';
99
+ import { NativeFileSystemProvider } from '@uploadista/react-native/providers';
100
+ import { UploadClient } from '@uploadista/client';
101
+
102
+ const client = new UploadClient({
103
+ apiUrl: 'https://api.example.com',
104
+ });
105
+
106
+ const fileSystemProvider = new NativeFileSystemProvider();
107
+
108
+ export default function App() {
109
+ return (
110
+ <UploadistaProvider
111
+ client={client}
112
+ fileSystemProvider={fileSystemProvider}
113
+ >
114
+ <YourAppContent />
115
+ </UploadistaProvider>
116
+ );
117
+ }
118
+ ```
119
+
120
+ ## Basic Usage
121
+
122
+ ### Single File Upload
123
+
124
+ ```tsx
125
+ import { useUploadistaClient } from '@uploadista/react-native';
126
+
127
+ export function SingleUploadScreen() {
128
+ const { fileSystemProvider } = useUploadistaClient();
129
+
130
+ const handlePickAndUpload = async () => {
131
+ try {
132
+ // Pick a file
133
+ const file = await fileSystemProvider.pickDocument();
134
+
135
+ // Read file content
136
+ const content = await fileSystemProvider.readFile(file.uri);
137
+
138
+ // Upload to server
139
+ const response = await fetch('https://api.example.com/upload', {
140
+ method: 'POST',
141
+ body: content,
142
+ headers: {
143
+ 'Content-Type': file.mimeType || 'application/octet-stream',
144
+ },
145
+ });
146
+
147
+ console.log('Upload successful:', await response.json());
148
+ } catch (error) {
149
+ console.error('Upload failed:', error);
150
+ }
151
+ };
152
+
153
+ return (
154
+ <Button
155
+ title="Pick and Upload File"
156
+ onPress={handlePickAndUpload}
157
+ />
158
+ );
159
+ }
160
+ ```
161
+
162
+ ### Camera Upload
163
+
164
+ ```tsx
165
+ import { useUploadistaClient } from '@uploadista/react-native';
166
+
167
+ export function CameraUploadScreen() {
168
+ const { fileSystemProvider } = useUploadistaClient();
169
+
170
+ const handleCaptureAndUpload = async () => {
171
+ try {
172
+ // Capture photo with camera
173
+ const photo = await fileSystemProvider.pickCamera();
174
+
175
+ // Upload to server
176
+ const formData = new FormData();
177
+ formData.append('file', {
178
+ uri: photo.uri,
179
+ name: photo.name,
180
+ type: photo.mimeType || 'image/jpeg',
181
+ } as any);
182
+
183
+ const response = await fetch('https://api.example.com/upload', {
184
+ method: 'POST',
185
+ body: formData,
186
+ });
187
+
188
+ console.log('Photo uploaded:', await response.json());
189
+ } catch (error) {
190
+ console.error('Upload failed:', error);
191
+ }
192
+ };
193
+
194
+ return (
195
+ <Button
196
+ title="Capture and Upload Photo"
197
+ onPress={handleCaptureAndUpload}
198
+ />
199
+ );
200
+ }
201
+ ```
202
+
203
+ ## File System Provider
204
+
205
+ The file system abstraction layer allows you to work with files uniformly across Expo and bare React Native:
206
+
207
+ ```tsx
208
+ interface FileSystemProvider {
209
+ // Select a document
210
+ pickDocument(options?: PickerOptions): Promise<FilePickResult>;
211
+
212
+ // Select an image from gallery
213
+ pickImage(options?: PickerOptions): Promise<FilePickResult>;
214
+
215
+ // Select a video from gallery
216
+ pickVideo(options?: PickerOptions): Promise<FilePickResult>;
217
+
218
+ // Capture a photo with camera
219
+ pickCamera(options?: CameraOptions): Promise<FilePickResult>;
220
+
221
+ // Read file as ArrayBuffer
222
+ readFile(uri: string): Promise<ArrayBuffer>;
223
+
224
+ // Get file information
225
+ getFileInfo(uri: string): Promise<FileInfo>;
226
+
227
+ // Convert file path to accessible URI
228
+ getDocumentUri(filePath: string): Promise<string>;
229
+ }
230
+ ```
231
+
232
+ ## Types
233
+
234
+ ### FilePickResult
235
+
236
+ Result from file picker operations:
237
+
238
+ ```tsx
239
+ interface FilePickResult {
240
+ uri: string; // File URI (platform-specific)
241
+ name: string; // File name with extension
242
+ size: number; // File size in bytes
243
+ mimeType?: string; // MIME type (if available)
244
+ localPath?: string; // Local file path (if available)
245
+ }
246
+ ```
247
+
248
+ ### FileInfo
249
+
250
+ Information about a file:
251
+
252
+ ```tsx
253
+ interface FileInfo {
254
+ uri: string; // File URI
255
+ name: string; // File name
256
+ size: number; // File size in bytes
257
+ mimeType?: string; // MIME type (if available)
258
+ modificationTime?: number; // Last modified timestamp
259
+ }
260
+ ```
261
+
262
+ ### PickerOptions
263
+
264
+ Options for file picker operations:
265
+
266
+ ```tsx
267
+ interface PickerOptions {
268
+ allowedTypes?: string[]; // MIME types to filter
269
+ allowMultiple?: boolean; // Allow multiple selection
270
+ maxSize?: number; // Maximum file size in bytes
271
+ }
272
+ ```
273
+
274
+ ## Permissions
275
+
276
+ ### iOS
277
+
278
+ Add to `Info.plist`:
279
+
280
+ ```xml
281
+ <key>NSCameraUsageDescription</key>
282
+ <string>We need camera access to upload photos</string>
283
+ <key>NSPhotoLibraryUsageDescription</key>
284
+ <string>We need photo library access to upload images</string>
285
+ ```
286
+
287
+ ### Android
288
+
289
+ Add to `AndroidManifest.xml`:
290
+
291
+ ```xml
292
+ <uses-permission android:name="android.permission.CAMERA" />
293
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
294
+ <uses-permission android:name="android.permission.INTERNET" />
295
+ ```
296
+
297
+ The file system providers handle runtime permission requests automatically.
298
+
299
+ ## Architecture
300
+
301
+ ### Expo Provider
302
+
303
+ Uses Expo's native modules for file system access:
304
+
305
+ - `expo-document-picker` - Document selection
306
+ - `expo-image-picker` - Image and video selection from gallery
307
+ - `expo-camera` - Camera integration
308
+ - `expo-file-system` - File reading
309
+
310
+ ### Native Provider
311
+
312
+ Uses third-party native modules for bare React Native:
313
+
314
+ - `react-native-document-picker` - Document selection
315
+ - `react-native-image-picker` - Image and video selection
316
+ - `rn-fetch-blob` - File system and network operations
317
+
318
+ Both providers implement the same `FileSystemProvider` interface, allowing you to write code once and run everywhere.
319
+
320
+ ## Examples
321
+
322
+ See the `examples/` directory for complete working examples:
323
+
324
+ - `react-native-expo-client/` - Expo managed workflow example
325
+ - `react-native-cli-client/` - Bare React Native example
326
+
327
+ ## Development
328
+
329
+ ### Building
330
+
331
+ ```bash
332
+ npm run build
333
+ ```
334
+
335
+ ### Type Checking
336
+
337
+ ```bash
338
+ npm run type-check
339
+ ```
340
+
341
+ ### Linting
342
+
343
+ ```bash
344
+ npm run lint
345
+ ```
346
+
347
+ ### Testing
348
+
349
+ ```bash
350
+ npm test
351
+ ```
352
+
353
+ ## API Reference
354
+
355
+ ### createFileSystemProvider()
356
+
357
+ Creates a file system provider based on configuration:
358
+
359
+ ```tsx
360
+ import { createFileSystemProvider } from '@uploadista/react-native/providers';
361
+
362
+ // Auto-detect environment
363
+ const provider = createFileSystemProvider();
364
+
365
+ // Specify Expo
366
+ const expoProvider = createFileSystemProvider({ type: 'expo' });
367
+
368
+ // Specify native
369
+ const nativeProvider = createFileSystemProvider({ type: 'native' });
370
+
371
+ // Use custom provider
372
+ const customProvider = createFileSystemProvider({
373
+ provider: myCustomProvider,
374
+ });
375
+ ```
376
+
377
+ ### getDefaultFileSystemProvider()
378
+
379
+ Gets the automatically detected file system provider for the current environment:
380
+
381
+ ```tsx
382
+ import { getDefaultFileSystemProvider } from '@uploadista/react-native/providers';
383
+
384
+ const provider = getDefaultFileSystemProvider();
385
+ ```
386
+
387
+ ## Troubleshooting
388
+
389
+ ### Module not found errors
390
+
391
+ Ensure you've installed the required modules for your environment:
392
+
393
+ **Expo:**
394
+ ```bash
395
+ expo install expo-document-picker expo-image-picker expo-camera expo-file-system
396
+ ```
397
+
398
+ **Bare RN:**
399
+ ```bash
400
+ npm install react-native-document-picker react-native-image-picker rn-fetch-blob
401
+ ```
402
+
403
+ ### Permission denied
404
+
405
+ Check that:
406
+ 1. Permissions are declared in `Info.plist` (iOS) or `AndroidManifest.xml` (Android)
407
+ 2. User has granted permissions at runtime (app will request automatically)
408
+ 3. For Android 13+, ensure `READ_MEDIA_IMAGES` and `READ_MEDIA_VIDEO` are also declared
409
+
410
+ ### File picker not opening
411
+
412
+ Verify that:
413
+ 1. You're inside a valid React component
414
+ 2. The provider is properly initialized
415
+ 3. You're calling from a user interaction (not from render)
416
+
417
+ ## Support
418
+
419
+ For issues and questions:
420
+
421
+ - GitHub Issues: https://github.com/uploadista/uploadista/issues
422
+ - Documentation: https://uploadista.dev/docs
423
+
424
+ ## License
425
+
426
+ MIT - See LICENSE file for details
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@uploadista/react-native-core",
3
+ "version": "0.0.3",
4
+ "type": "module",
5
+ "description": "Core React Native client for Uploadista",
6
+ "license": "MIT",
7
+ "author": "Uploadista",
8
+ "exports": {
9
+ ".": "./src/index.ts",
10
+ "./hooks": "./src/hooks/index.ts",
11
+ "./components": "./src/components/index.ts",
12
+ "./utils": "./src/utils/index.ts",
13
+ "./services": "./src/services/index.ts"
14
+ },
15
+ "dependencies": {
16
+ "uuid": "^10.0.0",
17
+ "js-base64": "^3.7.7",
18
+ "@uploadista/core": "0.0.3",
19
+ "@uploadista/client-core": "0.0.3"
20
+ },
21
+ "peerDependencies": {
22
+ "react": ">=16.8.0",
23
+ "react-native": ">=0.71.0",
24
+ "@react-native-async-storage/async-storage": ">=1.17.0"
25
+ },
26
+ "peerDependenciesMeta": {
27
+ "@react-native-async-storage/async-storage": {
28
+ "optional": true
29
+ }
30
+ },
31
+ "devDependencies": {
32
+ "@types/react": ">=18.0.0",
33
+ "@types/react-native": ">=0.71.0",
34
+ "@types/uuid": "^10.0.0",
35
+ "@uploadista/typescript-config": "0.0.3"
36
+ },
37
+ "scripts": {
38
+ "format": "biome format --write ./src",
39
+ "lint": "biome lint --write ./src",
40
+ "check": "biome check --write ./src"
41
+ }
42
+ }
@@ -0,0 +1,65 @@
1
+ import {
2
+ type ConnectionPoolConfig,
3
+ createClientStorage,
4
+ createLogger,
5
+ createUploadistaClient as createUploadistaClientCore,
6
+ type ServiceContainer,
7
+ type UploadistaClientOptions as UploadistaClientOptionsCore,
8
+ } from "@uploadista/client-core";
9
+ import type { ReactNativeUploadInput } from "../types/upload-input";
10
+
11
+ export interface UploadistaClientOptions
12
+ extends Omit<
13
+ UploadistaClientOptionsCore<ReactNativeUploadInput>,
14
+ | "webSocketFactory"
15
+ | "abortControllerFactory"
16
+ | "generateId"
17
+ | "clientStorage"
18
+ | "logger"
19
+ | "httpClient"
20
+ | "fileReader"
21
+ | "base64"
22
+ > {
23
+ connectionPooling?: ConnectionPoolConfig;
24
+
25
+ /**
26
+ * Whether to use AsyncStorage for persistence
27
+ * If false, uses in-memory storage
28
+ * @default true
29
+ */
30
+ useAsyncStorage?: boolean;
31
+ }
32
+
33
+ /**
34
+ * Creates an upload client instance with React Native-specific service implementations
35
+ *
36
+ * @param options - Client configuration options
37
+ * @returns Configured UploadistaClient instance
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * import { createUploadistaClient } from '@uploadista/react-native'
42
+ *
43
+ * const client = createUploadistaClient({
44
+ * baseUrl: 'https://api.example.com',
45
+ * storageId: 'my-storage',
46
+ * chunkSize: 1024 * 1024, // 1MB
47
+ * useAsyncStorage: true,
48
+ * });
49
+ * ```
50
+ */
51
+ export function createUploadistaClient(
52
+ options: UploadistaClientOptions,
53
+ services: ServiceContainer<ReactNativeUploadInput>,
54
+ ) {
55
+ return createUploadistaClientCore<ReactNativeUploadInput>({
56
+ ...options,
57
+ webSocketFactory: services.websocket,
58
+ abortControllerFactory: services.abortController,
59
+ httpClient: services.httpClient,
60
+ fileReader: services.fileReader,
61
+ generateId: services.idGeneration,
62
+ logger: createLogger(false, () => {}),
63
+ clientStorage: createClientStorage(services.storage),
64
+ });
65
+ }
@@ -0,0 +1,4 @@
1
+ export {
2
+ createUploadistaClient,
3
+ type UploadistaClientOptions,
4
+ } from "./create-uploadista-client";
@@ -0,0 +1,130 @@
1
+ import { type ReactNode, useEffect } from "react";
2
+ import {
3
+ ActivityIndicator,
4
+ Pressable,
5
+ StyleSheet,
6
+ Text,
7
+ View,
8
+ } from "react-native";
9
+ import { useCameraUpload } from "../hooks";
10
+ import type { UseCameraUploadOptions } from "../types";
11
+ import { UploadProgress } from "./UploadProgress";
12
+
13
+ export interface CameraUploadButtonProps {
14
+ /** Options for camera upload */
15
+ options?: UseCameraUploadOptions;
16
+ /** Button label text */
17
+ label?: string;
18
+ /** Custom button content */
19
+ children?: ReactNode;
20
+ /** Callback when upload completes successfully */
21
+ onSuccess?: (result: unknown) => void;
22
+ /** Callback when upload fails */
23
+ onError?: (error: Error) => void;
24
+ /** Callback when upload is cancelled */
25
+ onCancel?: () => void;
26
+ /** Whether to show progress inline */
27
+ showProgress?: boolean;
28
+ }
29
+
30
+ /**
31
+ * Button component for camera capture and upload
32
+ * Triggers camera on press and handles upload with progress display
33
+ */
34
+ export function CameraUploadButton({
35
+ options,
36
+ label = "Take Photo",
37
+ children,
38
+ onSuccess,
39
+ onError,
40
+ onCancel,
41
+ showProgress = true,
42
+ }: CameraUploadButtonProps) {
43
+ const { state, captureAndUpload } = useCameraUpload(options);
44
+
45
+ const handlePress = async () => {
46
+ try {
47
+ await captureAndUpload();
48
+ } catch (error) {
49
+ if (error instanceof Error) {
50
+ if (
51
+ error.message.includes("cancelled") ||
52
+ error.message.includes("aborted")
53
+ ) {
54
+ onCancel?.();
55
+ } else {
56
+ onError?.(error);
57
+ }
58
+ }
59
+ }
60
+ };
61
+
62
+ const isLoading = state.status === "uploading";
63
+ const isDisabled = isLoading || state.status === "aborted";
64
+
65
+ useEffect(() => {
66
+ if (state.status === "success" && state.result) {
67
+ onSuccess?.(state.result);
68
+ }
69
+ }, [state.status, state.result, onSuccess]);
70
+
71
+ useEffect(() => {
72
+ if (state.status === "error" && state.error) {
73
+ onError?.(state.error);
74
+ }
75
+ }, [state.status, state.error, onError]);
76
+
77
+ return (
78
+ <View style={styles.container}>
79
+ <Pressable
80
+ style={[styles.button, isDisabled && styles.buttonDisabled]}
81
+ onPress={handlePress}
82
+ disabled={isDisabled}
83
+ >
84
+ {isLoading && (
85
+ <ActivityIndicator
86
+ size="small"
87
+ color="#FFFFFF"
88
+ style={styles.spinner}
89
+ />
90
+ )}
91
+ <Text style={styles.buttonText}>{children || label}</Text>
92
+ </Pressable>
93
+ {showProgress && state.status !== "idle" && (
94
+ <View style={styles.progressContainer}>
95
+ <UploadProgress state={state} label="Camera upload" />
96
+ </View>
97
+ )}
98
+ </View>
99
+ );
100
+ }
101
+
102
+ const styles = StyleSheet.create({
103
+ container: {
104
+ gap: 8,
105
+ },
106
+ button: {
107
+ flexDirection: "row",
108
+ alignItems: "center",
109
+ justifyContent: "center",
110
+ paddingVertical: 12,
111
+ paddingHorizontal: 16,
112
+ backgroundColor: "#007AFF",
113
+ borderRadius: 8,
114
+ gap: 8,
115
+ },
116
+ buttonDisabled: {
117
+ opacity: 0.6,
118
+ },
119
+ buttonText: {
120
+ fontSize: 16,
121
+ fontWeight: "600",
122
+ color: "#FFFFFF",
123
+ },
124
+ spinner: {
125
+ marginRight: 4,
126
+ },
127
+ progressContainer: {
128
+ marginTop: 4,
129
+ },
130
+ });