@smarthivelabs-devs/storage-react-native
v0.2.1
Published
React Native and Expo SDK for SmartHive Storage.
Readme
@smarthivelabs-devs/storage-react-native
React Native and Expo SDK for SmartHive Storage. Uses URI-based file objects and platform APIs instead of browser File/Blob, making it compatible with React Native's networking layer.
Install
npm install @smarthivelabs-devs/storage-react-nativeNo peer dependency on the core SDK — this package is self-contained.
Setup
import { SmartHiveReactNativeStorageClient } from '@smarthivelabs-devs/storage-react-native';
const client = new SmartHiveReactNativeStorageClient({
baseUrl: 'https://storage.yourapp.com', // must start with http:// or https://
appCode: 'my-app',
apiKey: 'sk_live_...',
timeoutMs: 30_000, // optional, recommended for mobile networks
});Config options
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| baseUrl | string | Yes | Must be a valid http:// or https:// URL |
| appCode | string | Yes | App code from admin console |
| apiKey | string | Yes | API key credential |
| fetch | typeof fetch | No | Custom fetch implementation |
| timeoutMs | number | No | Global request timeout in ms |
The constructor throws immediately if baseUrl, appCode, or apiKey are missing, whitespace-only, or if baseUrl is not a valid HTTP/HTTPS URL.
Upload from Expo ImagePicker
import * as ImagePicker from 'expo-image-picker';
async function pickAndUpload() {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: 'Images',
quality: 0.8,
});
if (result.canceled) return;
const uploaded = await client.uploadImageAsset({
bucket: 'avatars',
asset: result.assets[0],
visibility: 'PRIVATE',
ownerUserId: currentUserId,
});
console.log('Uploaded:', uploaded.url);
return uploaded.fileId;
}uploadImageAsset accepts any asset from expo-image-picker. It falls back to image/jpeg and a timestamped filename when the asset has no mimeType or fileName.
Upload from Expo DocumentPicker
import * as DocumentPicker from 'expo-document-picker';
async function pickDocument() {
const result = await DocumentPicker.getDocumentAsync({ type: '*/*' });
if (result.canceled) return;
const uploaded = await client.uploadDocument({
bucket: 'attachments',
document: result.assets[0],
visibility: 'PRIVATE',
});
return uploaded.fileId;
}Upload from a Custom URI
Use uploadFromUri when you have a URI from any source (camera, filesystem, deep link, etc.):
const uploaded = await client.uploadFromUri({
bucket: 'photos',
uri: 'file:///var/mobile/Containers/Data/.../image.jpg',
filename: 'photo.jpg',
contentType: 'image/jpeg',
visibility: 'PRIVATE',
tenantId: 'tenant-123',
ownerUserId: 'user-456',
});Displaying Private Files
Your mobile app should never call getSignedUrl directly — that requires your API key, which must not be embedded in the app bundle. Instead, add a route on your own backend that generates the signed URL after verifying the user has permission, then fetch that route from mobile.
// 1. Your backend (e.g. Next.js route handler) generates the URL:
// POST /api/files/:id/signed-url → { url: "https://storage.…?token=shat_…" }
// 2. Mobile: fetch the signed URL from YOUR API endpoint
async function getPrivateFileUrl(fileId: string): Promise<string> {
const res = await fetch(`https://api.yourapp.com/files/${fileId}/signed-url`, {
method: 'POST',
headers: { Authorization: `Bearer ${await getAuthToken()}` },
});
const { url } = await res.json() as { url: string };
return url;
}
// 3. Use the URL directly — no SmartHive headers needed
const url = await getPrivateFileUrl(fileId);
// Display in Image component
<Image source={{ uri: url }} style={{ width: 300, height: 300 }} />
// Or download via expo-file-system
import * as FileSystem from 'expo-file-system';
const { uri } = await FileSystem.downloadAsync(url, FileSystem.documentDirectory + 'file.jpg');The signed URL is valid for the expiresInSeconds window your server configured (e.g. 5 minutes). Fetch a fresh one if it has expired.
Read Operations
// Get file metadata
const meta = await client.getMetadata(fileId);
console.log(meta.url); // absolute URL
// Get just the delivery URL
const { url } = await client.getUrl(fileId);
// Build a direct download URL (no network request)
const downloadUrl = client.downloadUrl(fileId);Delete
await client.delete(fileId);
// { fileId, status: 'DELETED' }Timeout Configuration
Mobile networks can be slow. Set timeoutMs at the client level or per-request basis is not yet exposed in the React Native client — set it globally in the constructor:
const client = new SmartHiveReactNativeStorageClient({
baseUrl: 'https://storage.yourapp.com',
appCode: 'my-app',
apiKey: 'sk_live_...',
timeoutMs: 60_000, // 60 seconds for large file uploads
});Timed-out requests throw a StorageReactNativeError with a message indicating the timeout.
Error Handling
import { StorageReactNativeError } from '@smarthivelabs-devs/storage-react-native';
try {
await client.uploadFromUri({ bucket: 'photos', uri, filename: 'img.jpg', contentType: 'image/jpeg' });
} catch (err) {
if (err instanceof StorageReactNativeError) {
console.error(`Upload failed (HTTP ${err.status}):`, err.message);
console.error('Payload:', err.payload);
}
}Security Notes
- Use
https://in production.http://is accepted for local development only. - Keep your
apiKeyin Expo'sEXPO_PUBLIC_*env vars for client-side apps, or better yet, fetch it from your own backend at login time. - Never hardcode API keys directly in source code — they end up in the app bundle.
