React Native SDK

@seenn/react-native — TypeScript-first SDK with React hooks and iOS Live Activity support

npm version GitHub stars MIT License

#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

Seenn.init(config)

Initialize the SDK once at app startup (inside your root App component or provider).

Parameters
appId string Your Seenn app IDrequired
userToken string User token from your backendrequired
config SeennConfig? Optional configuration options
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()

useSeennJob(jobId) → { job, isLoading, error }

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()

useSeennJobs(options?) → { jobs, isLoading, error, refetch }

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:

  1. Open your iOS project in Xcode
  2. File → New → Target → Widget Extension
  3. Name it SeennWidgetExtension
  4. 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:

Dynamic Island
S
Christmas Pack
3/5 completed
60%
Lock Screen
S
Christmas Pack
Generating images...
✓ Snowflake ✓ Tree ✓ Santa 3/5
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>
  );
}
Auto-sync: When using 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',
  },
});
Open Source: The SDK is MIT licensed. Works with Seenn Cloud or any compatible backend. Your backend must implement the REST API spec and SSE endpoints.

#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();