React Native SDK
@seenn/react-native — TypeScript-first SDK with React hooks and iOS Live Activity support
#Installation
npm install @seenn/react-native
# or
yarn add @seenn/react-native
#iOS Setup (Live Activity)
For iOS Live Activity support, additional setup is required:
cd ios && pod install
Then add NSSupportsLiveActivities to your Info.plist:
<key>NSSupportsLiveActivities</key>
<true/>
#Initialization
Initialize the SDK once at app startup (inside your root App component or provider).
import { Seenn } from '@seenn/react-native';
import { useEffect } from 'react';
export default function App() {
useEffect(() => {
// Initialize once on mount
Seenn.init({
appId: 'app_xxx',
userToken: userToken, // From your backend
});
return () => {
// Cleanup on unmount
Seenn.dispose();
};
}, []);
return <AppContent />;
}
#Configuration Options
Seenn.init({
appId: 'app_xxx',
userToken: userToken,
config: {
apiUrl: 'https://api.seenn.io',
sseUrl: 'https://sse.seenn.io',
timeout: 30000, // 30 seconds
debug: __DEV__, // Enable debug logs in development
},
});
#useSeennJob()
React hook to subscribe to real-time updates for a specific job. Automatically manages subscription lifecycle.
import { useSeennJob } from '@seenn/react-native';
import { View, Text, ActivityIndicator } from 'react-native';
function JobProgress({ jobId }: { jobId: string }) {
const { job, isLoading, error } = useSeennJob(jobId);
if (isLoading) {
return <ActivityIndicator />;
}
if (error) {
return <Text>Error: {error.message}</Text>;
}
return (
<View>
<Text>{job.title}</Text>
<Text>Progress: {job.progress}%</Text>
{job.message && <Text>{job.message}</Text>}
{job.stage && (
<Text>
Stage: {job.stage.label} ({job.stage.index}/{job.stage.total})
</Text>
)}
{job.eta && (
<Text>ETA: {job.eta}s</Text>
)}
{job.status === 'completed' && (
<Button title="Download" onPress={() => openUrl(job.resultUrl)} />
)}
</View>
);
}
#Return Values
| Property | Type | Description |
|---|---|---|
job |
SeennJob | null |
Current job state (null while loading) |
isLoading |
boolean |
True while fetching initial job data |
error |
Error | null |
Error if job fetch/subscription fails |
#useSeennJobs()
React hook to list all jobs for the current user with optional filtering.
import { useSeennJobs } from '@seenn/react-native';
import { FlatList, Text } from 'react-native';
function JobsList() {
const { jobs, isLoading, refetch } = useSeennJobs({
status: ['running', 'queued'],
limit: 20,
});
if (isLoading) {
return <ActivityIndicator />;
}
return (
<FlatList
data={jobs}
keyExtractor={(item) => item.jobId}
onRefresh={refetch}
refreshing={isLoading}
renderItem={({ item }) => (
<View>
<Text>{item.title}</Text>
<Text>{item.progress}% - {item.status}</Text>
</View>
)}
/>
);
}
#Options
| Property | Type | Description |
|---|---|---|
status |
JobStatus[]? |
Filter by status (e.g., ['running', 'queued']) |
limit |
number? |
Max number of jobs to return (default: 50) |
#Live Activity (iOS)
The SDK provides native iOS Live Activity support (v0.2.0+), showing job progress on Dynamic Island and Lock Screen. Supports up to 5 concurrent Live Activities per app.
#Setup
1. Add Widget Extension to your Xcode project:
- Open your iOS project in Xcode
- File → New → Target → Widget Extension
- Name it
SeennWidgetExtension - Copy files from
node_modules/@seenn/react-native/templates/SeennWidgetExtension/
2. Add NSSupportsLiveActivities to Info.plist:
<key>NSSupportsLiveActivities</key>
<true/>
3. Run pod install:
cd ios && pod install
#useLiveActivity() Hook (Recommended)
Auto-sync job state with Live Activity — the easiest way to integrate:
import { useSeennJob, useLiveActivity } from '@seenn/react-native';
function JobScreen({ jobId }: { jobId: string }) {
const job = useSeennJob(seenn, jobId);
// Auto-sync job state with Live Activity
const { isActive, isSupported } = useLiveActivity(job, {
autoStart: true, // Start when job begins running
autoEnd: true, // End when job completes/fails
dismissAfter: 300, // Keep on screen 5 min after completion
});
return (
<View>
<Text>{job?.title}</Text>
<Text>Progress: {job?.progress}%</Text>
{isSupported && <Text>Live Activity: {isActive ? 'On' : 'Off'}</Text>}
</View>
);
}
#Manual Control API
For full control over Live Activity lifecycle:
import { LiveActivity } from '@seenn/react-native';
// Check support
const supported = await LiveActivity.isSupported();
// Start activity
const result = await LiveActivity.start({
jobId: 'job_123',
title: 'Generating video...',
jobType: 'video-generation',
initialProgress: 0,
});
// Update progress
await LiveActivity.update({
jobId: 'job_123',
progress: 50,
status: 'running',
message: 'Encoding frames...',
stageName: 'Encoding',
stageIndex: 2,
stageTotal: 3,
});
// End activity
await LiveActivity.end({
jobId: 'job_123',
finalStatus: 'completed',
message: 'Video ready!',
resultUrl: 'https://example.com/video.mp4',
dismissAfter: 300,
});
// Get active activities
const activeIds = await LiveActivity.getActiveIds();
// ['job_123', 'job_456']
// Cancel all
await LiveActivity.cancelAll();
#Multi-Job Support
iOS allows up to 5 concurrent Live Activities per app:
// Start multiple activities
await LiveActivity.start({ jobId: 'job_1', title: 'Video 1', ... });
await LiveActivity.start({ jobId: 'job_2', title: 'Video 2', ... });
await LiveActivity.start({ jobId: 'job_3', title: 'Image Pack', ... });
// Each updates independently
await LiveActivity.update({ jobId: 'job_1', progress: 50, ... });
await LiveActivity.update({ jobId: 'job_2', progress: 75, ... });
// Check which are active
const activeIds = await LiveActivity.getActiveIds();
console.log(activeIds); // ['job_1', 'job_2', 'job_3']
#Push Token for Background Updates
import { LiveActivity } from '@seenn/react-native';
// Listen for push tokens
const unsubscribe = LiveActivity.onPushToken((event) => {
console.log(`Token for ${event.jobId}: ${event.token}`);
// Send to your backend for APNs push updates
sendTokenToBackend(event.jobId, event.token);
});
// Later: unsubscribe()
Auto-sync with SSE
When using useLiveActivity() hook with autoStart: true, Live Activity updates are automatically synced via SSE. You don't need to manually call update() — the SDK handles it for you!
#Parent-Child Jobs
Track batch processing jobs with automatic progress aggregation. Perfect for AI image packs, multi-stage pipelines, and bulk operations.
#Basic Batch UI
function BatchJobProgress({ jobId }: { jobId: string }) {
const { job } = useSeennJob(jobId);
return (
<View>
<Text style={styles.title}>{job.title}</Text>
{/* Progress bar */}
<View style={styles.progressBar}>
<View style={[styles.progressFill, { width: `${job.progress}%` }]} />
</View>
{/* Batch counter: "3/5 images completed" */}
{job.childrenTotal && (
<Text style={styles.counter}>
{job.childrenCompleted}/{job.childrenTotal} images completed
</Text>
)}
{/* Individual child status */}
{job.children?.map((child) => (
<View key={child.childId} style={styles.childRow}>
<StatusIcon status={child.status} />
<Text>{child.title}</Text>
{child.status === 'running' && <Text>{child.progress}%</Text>}
</View>
))}
</View>
);
}
#Live Activity for Batch Jobs (iOS)
Show batch progress in Dynamic Island and Lock Screen:
import { Seenn, useSeennJob } from '@seenn/react-native';
function BatchWithLiveActivity({ parentJobId }: { parentJobId: string }) {
const { job } = useSeennJob(parentJobId);
// Start Live Activity when job begins
useEffect(() => {
const startActivity = async () => {
if (job?.status === 'running') {
await Seenn.liveActivity.start({
jobId: parentJobId,
title: job.title,
// Pass batch info for custom UI
metadata: {
childrenTotal: job.childrenTotal,
childrenCompleted: job.childrenCompleted,
},
});
}
};
startActivity();
}, [job?.status]);
// Live Activity auto-updates via SSE!
// When job.childrenCompleted changes, widget refreshes
return (
<View>
<Text>{job.childrenCompleted}/{job.childrenTotal} completed</Text>
</View>
);
}
useSeennJob(), the Live Activity UI automatically updates as children complete. No manual update() calls needed!
#TypeScript Types
All types are fully typed with TypeScript:
interface SeennJob {
jobId: string;
userId: string;
status: JobStatus; // 'pending' | 'queued' | 'running' | 'completed' | 'failed'
title: string;
progress: number; // 0-100
message?: string;
stage?: StageInfo;
queue?: QueueInfo;
eta?: number; // Seconds to completion
resultUrl?: string;
errorMessage?: string;
createdAt: string;
updatedAt: string;
// Parent-child fields
parentJobId?: string;
childProgressMode?: 'average' | 'weighted' | 'sequential';
children?: ChildJob[];
childrenCompleted?: number;
childrenTotal?: number;
}
interface StageInfo {
id: string;
label: string;
index: number;
total: number;
}
interface QueueInfo {
position: number;
total: number;
estimatedWaitSeconds?: number;
}
#Self-Hosted Backend
Using your own backend? Configure the SDK to point to your API and SSE endpoints:
Seenn.init({
appId: 'your_app_id',
userToken: userToken,
config: {
// Point to your own backend
apiUrl: 'https://api.yourcompany.com',
sseUrl: 'https://sse.yourcompany.com',
},
});
#Error Handling
The SDK provides typed error handling:
import { useSeennJob, SeennError } from '@seenn/react-native';
function JobProgress({ jobId }: { jobId: string }) {
const { job, error } = useSeennJob(jobId);
if (error) {
if (error instanceof SeennError) {
switch (error.code) {
case 'JOB_NOT_FOUND':
return <Text>Job not found</Text>;
case 'UNAUTHORIZED':
// Refresh user token
refreshToken();
break;
case 'RATE_LIMITED':
return <Text>Too many requests, please wait</Text>;
}
}
return <Text>Error: {error.message}</Text>;
}
// Render job...
}
#Error Codes
| Code | Description | Action |
|---|---|---|
JOB_NOT_FOUND |
Job doesn't exist | Check jobId, show error message |
UNAUTHORIZED |
Invalid/expired token | Refresh user token |
RATE_LIMITED |
Too many requests | Retry after delay |
CONNECTION_ERROR |
SSE connection failed | Auto-reconnect in progress |
#Advanced Usage
#Update User Token
// When token expires, update it
await Seenn.setUserToken(newToken);
#Manual Reconnect
// Force reconnect SSE connection
await Seenn.reconnect();
#Cleanup
// Call when user logs out
await Seenn.dispose();