@uploadista/expo 0.0.8 → 0.0.10
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/dist/{index.d.ts → index.d.mts} +217 -117
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +7 -6
- package/src/client/create-uploadista-client.ts +6 -0
- package/src/components/uploadista-provider.tsx +188 -0
- package/src/hooks/use-uploadista-client.ts +147 -0
- package/src/index.ts +19 -9
- package/src/services/expo-file-system-provider.ts +100 -76
- package/src/services/file-reader-service.ts +1 -1
- package/src/services/id-generation-service.ts +2 -2
- package/src/types/upload-input.ts +1 -0
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -1
- package/src/types.ts +0 -116
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import type { UploadistaEvent } from "@uploadista/client-core";
|
|
3
|
+
import { UploadistaContext } from "@uploadista/react-native-core";
|
|
4
|
+
import type { UploadistaContextType } from "@uploadista/react-native-core/hooks";
|
|
5
|
+
import type React from "react";
|
|
6
|
+
import { useCallback, useContext, useMemo, useRef } from "react";
|
|
7
|
+
import {
|
|
8
|
+
type UseUploadistaClientOptions,
|
|
9
|
+
useUploadistaClient,
|
|
10
|
+
} from "../hooks/use-uploadista-client";
|
|
11
|
+
import { ExpoFileSystemProvider } from "../services/expo-file-system-provider";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Props for the UploadistaProvider component.
|
|
15
|
+
* Combines client configuration options with React children.
|
|
16
|
+
*
|
|
17
|
+
* @property children - React components that will have access to the upload client context
|
|
18
|
+
* @property baseUrl - API base URL for uploads
|
|
19
|
+
* @property storageId - Default storage identifier
|
|
20
|
+
* @property chunkSize - Upload chunk size in bytes
|
|
21
|
+
* @property onEvent - Global event handler for all upload events
|
|
22
|
+
* @property ... - All other UploadistaClientOptions
|
|
23
|
+
*/
|
|
24
|
+
export interface UploadistaProviderProps extends UseUploadistaClientOptions {
|
|
25
|
+
/**
|
|
26
|
+
* Children components that will have access to the upload client
|
|
27
|
+
*/
|
|
28
|
+
children: React.ReactNode;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Context provider that provides uploadista client functionality to child components.
|
|
33
|
+
* This eliminates the need to pass upload client configuration down through props
|
|
34
|
+
* and ensures a single, shared upload client instance across your application.
|
|
35
|
+
*
|
|
36
|
+
* @param props - Upload client options and children
|
|
37
|
+
* @returns Provider component with upload client context
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```tsx
|
|
41
|
+
* // Wrap your app with the upload provider
|
|
42
|
+
* function App() {
|
|
43
|
+
* return (
|
|
44
|
+
* <UploadistaProvider
|
|
45
|
+
* baseUrl="https://api.example.com"
|
|
46
|
+
* storageId="my-storage"
|
|
47
|
+
* chunkSize={1024 * 1024} // 1MB chunks
|
|
48
|
+
* onEvent={(event) => {
|
|
49
|
+
* console.log('Global upload event:', event);
|
|
50
|
+
* }}
|
|
51
|
+
* >
|
|
52
|
+
* <UploadInterface />
|
|
53
|
+
* </UploadistaProvider>
|
|
54
|
+
* );
|
|
55
|
+
* }
|
|
56
|
+
*
|
|
57
|
+
* // Use the upload client in any child component
|
|
58
|
+
* function UploadInterface() {
|
|
59
|
+
* const uploadClient = useUploadistaContext();
|
|
60
|
+
* const upload = useUpload(uploadClient);
|
|
61
|
+
* const dragDrop = useDragDrop({
|
|
62
|
+
* onFilesReceived: (files) => {
|
|
63
|
+
* files.forEach(file => upload.upload(file));
|
|
64
|
+
* }
|
|
65
|
+
* });
|
|
66
|
+
*
|
|
67
|
+
* return (
|
|
68
|
+
* <div {...dragDrop.dragHandlers}>
|
|
69
|
+
* <p>Drop files here to upload</p>
|
|
70
|
+
* {upload.isUploading && <p>Progress: {upload.state.progress}%</p>}
|
|
71
|
+
* </div>
|
|
72
|
+
* );
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export function UploadistaProvider({
|
|
77
|
+
children,
|
|
78
|
+
...options
|
|
79
|
+
}: UploadistaProviderProps) {
|
|
80
|
+
const eventSubscribersRef = useRef<Set<(event: UploadistaEvent) => void>>(
|
|
81
|
+
new Set(),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Create file system provider instance (memoized to avoid recreation)
|
|
85
|
+
const fileSystemProvider = useMemo(() => new ExpoFileSystemProvider(), []);
|
|
86
|
+
|
|
87
|
+
// Wrap the original onEvent to broadcast to subscribers
|
|
88
|
+
const wrappedOnEvent = useCallback(
|
|
89
|
+
(event: UploadistaEvent) => {
|
|
90
|
+
console.log("[UploadistaProvider] Received event:", event);
|
|
91
|
+
|
|
92
|
+
// Call original handler if provided
|
|
93
|
+
options.onEvent?.(event);
|
|
94
|
+
|
|
95
|
+
// Broadcast to all subscribers
|
|
96
|
+
console.log(
|
|
97
|
+
"[UploadistaProvider] Broadcasting to",
|
|
98
|
+
eventSubscribersRef.current.size,
|
|
99
|
+
"subscribers",
|
|
100
|
+
);
|
|
101
|
+
eventSubscribersRef.current.forEach((handler) => {
|
|
102
|
+
try {
|
|
103
|
+
handler(event);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
console.error("Error in event subscriber:", err);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
[options.onEvent],
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const uploadClient = useUploadistaClient({
|
|
113
|
+
...options,
|
|
114
|
+
onEvent: wrappedOnEvent,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const subscribeToEvents = useCallback(
|
|
118
|
+
(handler: (event: UploadistaEvent) => void) => {
|
|
119
|
+
eventSubscribersRef.current.add(handler);
|
|
120
|
+
return () => {
|
|
121
|
+
eventSubscribersRef.current.delete(handler);
|
|
122
|
+
};
|
|
123
|
+
},
|
|
124
|
+
[],
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// Memoize the context value to prevent unnecessary re-renders
|
|
128
|
+
const contextValue: UploadistaContextType = useMemo(
|
|
129
|
+
() => ({
|
|
130
|
+
...uploadClient,
|
|
131
|
+
fileSystemProvider,
|
|
132
|
+
subscribeToEvents,
|
|
133
|
+
// Cast config to match react-native-core expectations (Expo options are compatible)
|
|
134
|
+
// biome-ignore lint/suspicious/noExplicitAny: Type compatibility between Expo and RN Core client options
|
|
135
|
+
config: uploadClient.config as any,
|
|
136
|
+
}),
|
|
137
|
+
[uploadClient, fileSystemProvider, subscribeToEvents],
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<UploadistaContext.Provider value={contextValue}>
|
|
142
|
+
{children}
|
|
143
|
+
</UploadistaContext.Provider>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Hook to access the uploadista client from the UploadistaProvider context.
|
|
149
|
+
* Must be used within an UploadistaProvider component.
|
|
150
|
+
*
|
|
151
|
+
* @returns Upload client instance from context including file system provider
|
|
152
|
+
* @throws Error if used outside of UploadistaProvider
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```tsx
|
|
156
|
+
* function FileUploader() {
|
|
157
|
+
* const uploadContext = useUploadistaContext();
|
|
158
|
+
* const { client, fileSystemProvider } = uploadContext;
|
|
159
|
+
*
|
|
160
|
+
* const handleFilePick = async () => {
|
|
161
|
+
* try {
|
|
162
|
+
* const result = await fileSystemProvider.pickDocument();
|
|
163
|
+
* await client.upload(result.uri);
|
|
164
|
+
* } catch (error) {
|
|
165
|
+
* console.error('Upload failed:', error);
|
|
166
|
+
* }
|
|
167
|
+
* };
|
|
168
|
+
*
|
|
169
|
+
* return (
|
|
170
|
+
* <button onClick={handleFilePick}>
|
|
171
|
+
* Upload File
|
|
172
|
+
* </button>
|
|
173
|
+
* );
|
|
174
|
+
* }
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
export function useUploadistaContext(): UploadistaContextType {
|
|
178
|
+
const context = useContext(UploadistaContext);
|
|
179
|
+
|
|
180
|
+
if (context === undefined) {
|
|
181
|
+
throw new Error(
|
|
182
|
+
"useUploadistaContext must be used within an UploadistaProvider. " +
|
|
183
|
+
"Make sure to wrap your component tree with <UploadistaProvider>.",
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return context;
|
|
188
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { useMemo, useRef } from "react";
|
|
2
|
+
import {
|
|
3
|
+
createUploadistaClient,
|
|
4
|
+
type UploadistaClientOptions,
|
|
5
|
+
} from "../client/create-uploadista-client";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Configuration options for the uploadista client hook.
|
|
9
|
+
* Extends the base client options with React-specific behavior.
|
|
10
|
+
*
|
|
11
|
+
* @property onEvent - Global event handler for all upload and flow events
|
|
12
|
+
* @property baseUrl - API base URL for uploads
|
|
13
|
+
* @property storageId - Default storage identifier
|
|
14
|
+
* @property chunkSize - Size of upload chunks in bytes
|
|
15
|
+
* @property storeFingerprintForResuming - Enable resumable uploads
|
|
16
|
+
* @property retryDelays - Array of retry delays in milliseconds
|
|
17
|
+
* @property parallelUploads - Maximum number of parallel uploads
|
|
18
|
+
* @property uploadStrategy - Upload strategy (sequential, parallel, adaptive)
|
|
19
|
+
* @property smartChunking - Enable dynamic chunk size adjustment
|
|
20
|
+
* @property networkMonitoring - Enable network condition monitoring
|
|
21
|
+
* @property useAsyncStorage - Whether to use AsyncStorage for persistence (default: true)
|
|
22
|
+
*/
|
|
23
|
+
export interface UseUploadistaClientOptions extends UploadistaClientOptions {
|
|
24
|
+
/**
|
|
25
|
+
* Global event handler for all upload and flow events from this client
|
|
26
|
+
*/
|
|
27
|
+
onEvent?: UploadistaClientOptions["onEvent"];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Return value from the useUploadistaClient hook.
|
|
32
|
+
*
|
|
33
|
+
* @property client - Configured uploadista client instance (stable across re-renders)
|
|
34
|
+
* @property config - Current client configuration options
|
|
35
|
+
*/
|
|
36
|
+
export interface UseUploadistaClientReturn {
|
|
37
|
+
/**
|
|
38
|
+
* The uploadista client instance
|
|
39
|
+
*/
|
|
40
|
+
client: ReturnType<typeof createUploadistaClient>;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Current configuration of the client
|
|
44
|
+
*/
|
|
45
|
+
config: UseUploadistaClientOptions;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* React hook for creating and managing an uploadista client instance for Expo.
|
|
50
|
+
* The client instance is memoized and stable across re-renders, only being
|
|
51
|
+
* recreated when configuration options change.
|
|
52
|
+
*
|
|
53
|
+
* This hook is typically used internally by UploadistaProvider, but can be
|
|
54
|
+
* used directly for advanced use cases requiring multiple client instances.
|
|
55
|
+
*
|
|
56
|
+
* @param options - Upload client configuration options
|
|
57
|
+
* @returns Object containing the stable client instance and current configuration
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```tsx
|
|
61
|
+
* // Basic client setup
|
|
62
|
+
* function MyUploadComponent() {
|
|
63
|
+
* const { client, config } = useUploadistaClient({
|
|
64
|
+
* baseUrl: 'https://api.example.com',
|
|
65
|
+
* storageId: 'default-storage',
|
|
66
|
+
* chunkSize: 1024 * 1024, // 1MB chunks
|
|
67
|
+
* storeFingerprintForResuming: true,
|
|
68
|
+
* useAsyncStorage: true, // Use AsyncStorage for persistence
|
|
69
|
+
* onEvent: (event) => {
|
|
70
|
+
* console.log('Upload event:', event);
|
|
71
|
+
* }
|
|
72
|
+
* });
|
|
73
|
+
*
|
|
74
|
+
* // Use client directly
|
|
75
|
+
* const handleUpload = async (uri: string) => {
|
|
76
|
+
* await client.upload(uri, {
|
|
77
|
+
* onSuccess: (result) => console.log('Uploaded:', result),
|
|
78
|
+
* onError: (error) => console.error('Failed:', error),
|
|
79
|
+
* });
|
|
80
|
+
* };
|
|
81
|
+
*
|
|
82
|
+
* return <FileUploader onUpload={handleUpload} />;
|
|
83
|
+
* }
|
|
84
|
+
*
|
|
85
|
+
* // Advanced: Multiple clients with different configurations
|
|
86
|
+
* function MultiClientComponent() {
|
|
87
|
+
* // Client for image uploads
|
|
88
|
+
* const imageClient = useUploadistaClient({
|
|
89
|
+
* baseUrl: 'https://images.example.com',
|
|
90
|
+
* storageId: 'images',
|
|
91
|
+
* chunkSize: 2 * 1024 * 1024, // 2MB for images
|
|
92
|
+
* });
|
|
93
|
+
*
|
|
94
|
+
* // Client for document uploads
|
|
95
|
+
* const docClient = useUploadistaClient({
|
|
96
|
+
* baseUrl: 'https://docs.example.com',
|
|
97
|
+
* storageId: 'documents',
|
|
98
|
+
* chunkSize: 512 * 1024, // 512KB for documents
|
|
99
|
+
* });
|
|
100
|
+
*
|
|
101
|
+
* return (
|
|
102
|
+
* <View>
|
|
103
|
+
* <ImageUploader client={imageClient.client} />
|
|
104
|
+
* <DocumentUploader client={docClient.client} />
|
|
105
|
+
* </View>
|
|
106
|
+
* );
|
|
107
|
+
* }
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* @see {@link UploadistaProvider} for the recommended way to provide client context
|
|
111
|
+
*/
|
|
112
|
+
export function useUploadistaClient(
|
|
113
|
+
options: UseUploadistaClientOptions,
|
|
114
|
+
): UseUploadistaClientReturn {
|
|
115
|
+
// Store the options in a ref to enable stable dependency checking
|
|
116
|
+
const optionsRef = useRef<UseUploadistaClientOptions>(options);
|
|
117
|
+
|
|
118
|
+
// Update ref on each render but only create new client when essential deps change
|
|
119
|
+
optionsRef.current = options;
|
|
120
|
+
|
|
121
|
+
// Create client instance with stable identity
|
|
122
|
+
const client = useMemo(() => {
|
|
123
|
+
return createUploadistaClient({
|
|
124
|
+
baseUrl: options.baseUrl,
|
|
125
|
+
storageId: options.storageId,
|
|
126
|
+
uploadistaBasePath: options.uploadistaBasePath,
|
|
127
|
+
chunkSize: options.chunkSize,
|
|
128
|
+
storeFingerprintForResuming: options.storeFingerprintForResuming,
|
|
129
|
+
retryDelays: options.retryDelays,
|
|
130
|
+
parallelUploads: options.parallelUploads,
|
|
131
|
+
parallelChunkSize: options.parallelChunkSize,
|
|
132
|
+
uploadStrategy: options.uploadStrategy,
|
|
133
|
+
smartChunking: options.smartChunking,
|
|
134
|
+
networkMonitoring: options.networkMonitoring,
|
|
135
|
+
uploadMetrics: options.uploadMetrics,
|
|
136
|
+
connectionPooling: options.connectionPooling,
|
|
137
|
+
useAsyncStorage: options.useAsyncStorage,
|
|
138
|
+
auth: options.auth,
|
|
139
|
+
onEvent: options.onEvent,
|
|
140
|
+
});
|
|
141
|
+
}, [options]);
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
client,
|
|
145
|
+
config: options,
|
|
146
|
+
};
|
|
147
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -50,11 +50,30 @@ export type {
|
|
|
50
50
|
SliceResult,
|
|
51
51
|
StorageService,
|
|
52
52
|
} from "@uploadista/client-core";
|
|
53
|
+
// Export Expo-specific types
|
|
54
|
+
export type {
|
|
55
|
+
CameraOptions,
|
|
56
|
+
FileInfo,
|
|
57
|
+
FilePickResult,
|
|
58
|
+
FileSystemProvider,
|
|
59
|
+
PickerOptions,
|
|
60
|
+
} from "@uploadista/react-native-core";
|
|
53
61
|
// Export client factory
|
|
54
62
|
export {
|
|
55
63
|
createUploadistaClient,
|
|
56
64
|
type UploadistaClientOptions,
|
|
57
65
|
} from "./client";
|
|
66
|
+
// Export provider and hooks
|
|
67
|
+
export {
|
|
68
|
+
UploadistaProvider,
|
|
69
|
+
type UploadistaProviderProps,
|
|
70
|
+
useUploadistaContext,
|
|
71
|
+
} from "./components/uploadista-provider";
|
|
72
|
+
export {
|
|
73
|
+
type UseUploadistaClientOptions,
|
|
74
|
+
type UseUploadistaClientReturn,
|
|
75
|
+
useUploadistaClient,
|
|
76
|
+
} from "./hooks/use-uploadista-client";
|
|
58
77
|
// Re-export service implementations and factories
|
|
59
78
|
export {
|
|
60
79
|
createAsyncStorageService,
|
|
@@ -65,13 +84,4 @@ export {
|
|
|
65
84
|
createExpoServices,
|
|
66
85
|
type ExpoServiceOptions,
|
|
67
86
|
} from "./services";
|
|
68
|
-
|
|
69
87
|
export { ExpoFileSystemProvider } from "./services/expo-file-system-provider";
|
|
70
|
-
// Export Expo-specific types
|
|
71
|
-
export type {
|
|
72
|
-
CameraOptions,
|
|
73
|
-
FileInfo,
|
|
74
|
-
FilePickResult,
|
|
75
|
-
FileSystemProvider,
|
|
76
|
-
PickerOptions,
|
|
77
|
-
} from "./types";
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import * as DocumentPicker from "expo-document-picker";
|
|
2
|
-
import * as FileSystem from "expo-file-system";
|
|
3
|
-
import * as ImagePicker from "expo-image-picker";
|
|
4
1
|
import type {
|
|
5
2
|
CameraOptions,
|
|
6
3
|
FileInfo,
|
|
7
4
|
FilePickResult,
|
|
8
5
|
FileSystemProvider,
|
|
9
6
|
PickerOptions,
|
|
10
|
-
} from "
|
|
7
|
+
} from "@uploadista/react-native-core";
|
|
8
|
+
import * as DocumentPicker from "expo-document-picker";
|
|
9
|
+
import * as FileSystem from "expo-file-system";
|
|
10
|
+
import * as ImagePicker from "expo-image-picker";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* File system provider implementation for Expo managed environment
|
|
@@ -30,27 +30,33 @@ export class ExpoFileSystemProvider implements FileSystemProvider {
|
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
if (result.canceled) {
|
|
33
|
-
|
|
33
|
+
return { status: "cancelled" };
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
const asset = result.assets?.[0];
|
|
37
37
|
if (!asset) {
|
|
38
|
-
|
|
38
|
+
return { status: "cancelled" };
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
return {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
status: "success",
|
|
43
|
+
data: {
|
|
44
|
+
uri: asset.uri,
|
|
45
|
+
name: asset.name,
|
|
46
|
+
size: asset.size || 0,
|
|
47
|
+
mimeType: asset.mimeType,
|
|
48
|
+
},
|
|
46
49
|
};
|
|
47
50
|
} catch (error) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
return {
|
|
52
|
+
status: "error",
|
|
53
|
+
error:
|
|
54
|
+
error instanceof Error
|
|
55
|
+
? error
|
|
56
|
+
: new Error(
|
|
57
|
+
`Failed to pick document: ${error instanceof Error ? error.message : String(error)}`,
|
|
58
|
+
),
|
|
59
|
+
};
|
|
54
60
|
}
|
|
55
61
|
}
|
|
56
62
|
|
|
@@ -60,38 +66,46 @@ export class ExpoFileSystemProvider implements FileSystemProvider {
|
|
|
60
66
|
const { status } =
|
|
61
67
|
await ImagePicker.requestMediaLibraryPermissionsAsync();
|
|
62
68
|
if (status !== "granted") {
|
|
63
|
-
|
|
69
|
+
return {
|
|
70
|
+
status: "error",
|
|
71
|
+
error: new Error("Camera roll permission not granted"),
|
|
72
|
+
};
|
|
64
73
|
}
|
|
65
74
|
|
|
66
75
|
const result = await ImagePicker.launchImageLibraryAsync({
|
|
67
|
-
|
|
68
|
-
mediaTypes: "Images" as any,
|
|
76
|
+
mediaTypes: "images",
|
|
69
77
|
selectionLimit: options?.allowMultiple ? 0 : 1,
|
|
70
78
|
quality: 1,
|
|
71
79
|
});
|
|
72
80
|
|
|
73
81
|
if (result.canceled) {
|
|
74
|
-
|
|
82
|
+
return { status: "cancelled" };
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
const asset = result.assets?.[0];
|
|
78
86
|
if (!asset) {
|
|
79
|
-
|
|
87
|
+
return { status: "cancelled" };
|
|
80
88
|
}
|
|
81
89
|
|
|
82
90
|
return {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
status: "success",
|
|
92
|
+
data: {
|
|
93
|
+
uri: asset.uri,
|
|
94
|
+
name: asset.fileName || `image-${Date.now()}.jpg`,
|
|
95
|
+
size: asset.fileSize || 0,
|
|
96
|
+
mimeType: "image/jpeg",
|
|
97
|
+
},
|
|
87
98
|
};
|
|
88
99
|
} catch (error) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
100
|
+
return {
|
|
101
|
+
status: "error",
|
|
102
|
+
error:
|
|
103
|
+
error instanceof Error
|
|
104
|
+
? error
|
|
105
|
+
: new Error(
|
|
106
|
+
`Failed to pick image: ${error instanceof Error ? error.message : String(error)}`,
|
|
107
|
+
),
|
|
108
|
+
};
|
|
95
109
|
}
|
|
96
110
|
}
|
|
97
111
|
|
|
@@ -101,37 +115,45 @@ export class ExpoFileSystemProvider implements FileSystemProvider {
|
|
|
101
115
|
const { status } =
|
|
102
116
|
await ImagePicker.requestMediaLibraryPermissionsAsync();
|
|
103
117
|
if (status !== "granted") {
|
|
104
|
-
|
|
118
|
+
return {
|
|
119
|
+
status: "error",
|
|
120
|
+
error: new Error("Camera roll permission not granted"),
|
|
121
|
+
};
|
|
105
122
|
}
|
|
106
123
|
|
|
107
124
|
const result = await ImagePicker.launchImageLibraryAsync({
|
|
108
|
-
|
|
109
|
-
mediaTypes: "Videos" as any,
|
|
125
|
+
mediaTypes: "videos",
|
|
110
126
|
selectionLimit: options?.allowMultiple ? 0 : 1,
|
|
111
127
|
});
|
|
112
128
|
|
|
113
129
|
if (result.canceled) {
|
|
114
|
-
|
|
130
|
+
return { status: "cancelled" };
|
|
115
131
|
}
|
|
116
132
|
|
|
117
133
|
const asset = result.assets?.[0];
|
|
118
134
|
if (!asset) {
|
|
119
|
-
|
|
135
|
+
return { status: "cancelled" };
|
|
120
136
|
}
|
|
121
137
|
|
|
122
138
|
return {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
139
|
+
status: "success",
|
|
140
|
+
data: {
|
|
141
|
+
uri: asset.uri,
|
|
142
|
+
name: asset.fileName || `video-${Date.now()}.mp4`,
|
|
143
|
+
size: asset.fileSize || 0,
|
|
144
|
+
mimeType: "video/mp4",
|
|
145
|
+
},
|
|
127
146
|
};
|
|
128
147
|
} catch (error) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
148
|
+
return {
|
|
149
|
+
status: "error",
|
|
150
|
+
error:
|
|
151
|
+
error instanceof Error
|
|
152
|
+
? error
|
|
153
|
+
: new Error(
|
|
154
|
+
`Failed to pick video: ${error instanceof Error ? error.message : String(error)}`,
|
|
155
|
+
),
|
|
156
|
+
};
|
|
135
157
|
}
|
|
136
158
|
}
|
|
137
159
|
|
|
@@ -140,7 +162,10 @@ export class ExpoFileSystemProvider implements FileSystemProvider {
|
|
|
140
162
|
// Request camera permissions
|
|
141
163
|
const { status } = await ImagePicker.requestCameraPermissionsAsync();
|
|
142
164
|
if (status !== "granted") {
|
|
143
|
-
|
|
165
|
+
return {
|
|
166
|
+
status: "error",
|
|
167
|
+
error: new Error("Camera permission not granted"),
|
|
168
|
+
};
|
|
144
169
|
}
|
|
145
170
|
|
|
146
171
|
const result = await ImagePicker.launchCameraAsync({
|
|
@@ -150,45 +175,44 @@ export class ExpoFileSystemProvider implements FileSystemProvider {
|
|
|
150
175
|
});
|
|
151
176
|
|
|
152
177
|
if (result.canceled) {
|
|
153
|
-
|
|
178
|
+
return { status: "cancelled" };
|
|
154
179
|
}
|
|
155
180
|
|
|
156
181
|
const asset = result.assets?.[0];
|
|
157
182
|
if (!asset) {
|
|
158
|
-
|
|
183
|
+
return { status: "cancelled" };
|
|
159
184
|
}
|
|
160
185
|
|
|
161
186
|
return {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
187
|
+
status: "success",
|
|
188
|
+
data: {
|
|
189
|
+
uri: asset.uri,
|
|
190
|
+
name: asset.fileName || `photo-${Date.now()}.jpg`,
|
|
191
|
+
size: asset.fileSize || 0,
|
|
192
|
+
mimeType: "image/jpeg",
|
|
193
|
+
},
|
|
166
194
|
};
|
|
167
195
|
} catch (error) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
196
|
+
return {
|
|
197
|
+
status: "error",
|
|
198
|
+
error:
|
|
199
|
+
error instanceof Error
|
|
200
|
+
? error
|
|
201
|
+
: new Error(
|
|
202
|
+
`Failed to capture photo: ${error instanceof Error ? error.message : String(error)}`,
|
|
203
|
+
),
|
|
204
|
+
};
|
|
174
205
|
}
|
|
175
206
|
}
|
|
176
207
|
|
|
177
208
|
async readFile(uri: string): Promise<ArrayBuffer> {
|
|
178
209
|
try {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
// Convert base64 to ArrayBuffer
|
|
185
|
-
// Use js-base64 for decoding since atob is not available in all RN environments
|
|
186
|
-
const { fromBase64 } = await import("js-base64");
|
|
187
|
-
const binaryString = fromBase64(base64String);
|
|
188
|
-
const bytes = new Uint8Array(binaryString.length);
|
|
189
|
-
for (let i = 0; i < binaryString.length; i++) {
|
|
190
|
-
bytes[i] = binaryString.charCodeAt(i);
|
|
210
|
+
const file = new FileSystem.File(uri);
|
|
211
|
+
if (!file.exists) {
|
|
212
|
+
throw new Error("File does not exist");
|
|
191
213
|
}
|
|
214
|
+
const bytes = await file.bytes();
|
|
215
|
+
|
|
192
216
|
return bytes.buffer;
|
|
193
217
|
} catch (error) {
|
|
194
218
|
throw new Error(
|
|
@@ -204,18 +228,18 @@ export class ExpoFileSystemProvider implements FileSystemProvider {
|
|
|
204
228
|
|
|
205
229
|
async getFileInfo(uri: string): Promise<FileInfo> {
|
|
206
230
|
try {
|
|
207
|
-
const
|
|
231
|
+
const file = new FileSystem.File(uri);
|
|
208
232
|
|
|
209
|
-
if (!
|
|
233
|
+
if (!file.exists) {
|
|
210
234
|
throw new Error("File does not exist");
|
|
211
235
|
}
|
|
212
236
|
|
|
213
237
|
return {
|
|
214
238
|
uri,
|
|
215
239
|
name: uri.split("/").pop() || "unknown",
|
|
216
|
-
size:
|
|
217
|
-
modificationTime:
|
|
218
|
-
?
|
|
240
|
+
size: file.size ?? 0,
|
|
241
|
+
modificationTime: file.modificationTime
|
|
242
|
+
? file.modificationTime * 1000
|
|
219
243
|
: undefined,
|
|
220
244
|
};
|
|
221
245
|
} catch (error) {
|
|
@@ -3,8 +3,8 @@ import type {
|
|
|
3
3
|
FileSource,
|
|
4
4
|
SliceResult,
|
|
5
5
|
} from "@uploadista/client-core";
|
|
6
|
+
// import { Blob } from "expo-blob";
|
|
6
7
|
import type { ExpoUploadInput } from "@/types/upload-input";
|
|
7
|
-
|
|
8
8
|
/**
|
|
9
9
|
* Expo-specific implementation of FileReaderService
|
|
10
10
|
* Handles Blob, File, and URI-based file inputs using Expo FileSystem APIs
|