Skip to main content

Expo SDK Integration Guide

This guide provides comprehensive instructions for integrating the Passage Expo SDK into your React Native/Expo application and implementing the required backend infrastructure.

Table of Contents

  1. Installation
  2. SDK Integration
  3. Backend Implementation
  4. API Reference

Installation

Requirements

  • Expo SDK 48+
  • React Native 0.70+
  • Node.js 16+

Expo Installation

Add the package to your Expo project:

npx expo install @getpassage/expo

SDK Integration

Overview

To integrate the Passage Expo SDK, you need to complete these steps:

  1. Configure SDK → Initialize once in your app
  2. Backend Setup → Create intent token endpoint
  3. Get Token → Call your backend for intent token
  4. Open Passage → Launch connection flow with callbacks
  5. Handle Results → Process success/error responses

Quick Reference

Step 1: Configure SDK

import { configure, PassageConfig } from '@getpassage/expo';

// Configure once (typically in App.tsx)
const config: PassageConfig = { debug: true };
configure(config);

Step 2: Get Intent Token

// Call your backend to get intent token
const response = await fetch('https://your-api.com/intent-token');
const { intentToken } = await response.json();

Step 3: Open Passage Flow

import { open, PassageSuccessData, PassageErrorData } from '@getpassage/expo';

await open({
token: intentToken,
onConnectionComplete: (data: PassageSuccessData) => {
console.log('Success:', data.connectionId);
},
onConnectionError: (error: PassageErrorData) => {
console.log('Error:', error.error);
}
});

Detailed Implementation

Complete Expo Integration

import React, { useState } from 'react';
import { View, Text, TouchableOpacity, Alert } from 'react-native';
import { configure, open, PassageSuccessData, PassageErrorData, PassageDataResult } from '@getpassage/expo';

// Configure SDK at app startup
configure({ debug: true });

export default function App() {
const [isLoading, setIsLoading] = useState(false);
const [result, setResult] = useState('');

const connectAccount = async () => {
setIsLoading(true);

try {
// 1. Get intent token from your backend
const intentToken = await getIntentToken();

// 2. Open Passage with callbacks
await open({
token: intentToken,
onConnectionComplete: (data: PassageSuccessData) => {
setResult(`✅ Connected! ID: ${data.connectionId}`);
},
onConnectionError: (error: PassageErrorData) => {
setResult(`❌ Error: ${error.error}`);
},
onDataComplete: (data: PassageDataResult) => {
console.log('History items:', data.data?.history?.length || 0);

// Process the captured data
if (data.data?.history) {
data.data.history.forEach(item => {
if (item.structuredData) {
console.log('Data:', item.structuredData);
}
});
}
},
onExit: (reason) => {
console.log('User exited:', reason);
}
});

} catch (error) {
setResult(`Error: ${error.message}`);
} finally {
setIsLoading(false);
}
};

const getIntentToken = async (): Promise<string> => {
const response = await fetch('https://your-api.com/api/intent-token');
const data: { intentToken: string } = await response.json();
return data.intentToken;
};

return (
<View style={{ flex: 1, justifyContent: 'center', padding: 20 }}>
<TouchableOpacity
onPress={connectAccount}
disabled={isLoading}
style={{
backgroundColor: isLoading ? '#ccc' : '#007AFF',
padding: 15,
borderRadius: 8,
marginBottom: 20
}}
>
<Text style={{ color: 'white', textAlign: 'center', fontSize: 16 }}>
{isLoading ? 'Connecting...' : 'Connect Account'}
</Text>
</TouchableOpacity>

{result ? (
<Text style={{ fontSize: 14, textAlign: 'center' }}>
{result}
</Text>
) : null}
</View>
);
}

Using React Context (Alternative)

import React from 'react';
import { PassageProvider, usePassage, PassageSuccessData, PassageErrorData } from '@getpassage/expo';

export default function App() {
return (
<PassageProvider config={{ debug: true }}>
<MyComponent />
</PassageProvider>
);
}

function MyComponent() {
const { open } = usePassage();

const handleConnect = async () => {
const intentToken: string = await getIntentToken();

await open({
token: intentToken,
onConnectionComplete: (data: PassageSuccessData) => {
console.log('Success:', data.connectionId);
},
onConnectionError: (error: PassageErrorData) => {
console.error('Error:', error.error);
}
});
};

// ... rest of component
}

Backend Implementation

Your backend needs to create intent tokens for the Expo app to use. Here's a typical implementation:

Node.js Example

app.post('/api/intent-token', async (req, res) => {
try {
// 1. Get access token using OAuth 2.0 Client Credentials
const accessToken = await getPassageAccessToken();

// 2. Create intent token
const response = await fetch('https://api.getpassage.ai/intent-token', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
integrationId: 'netflix', // or other integrations
resources: {
genericHistory: {
read: {}
}
}
})
});

const { intentToken } = await response.json();

res.json({ intentToken });

} catch (error) {
res.status(500).json({ error: error.message });
}
});

async function getPassageAccessToken() {
const response = await fetch('https://api.getpassage.ai/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: process.env.PASSAGE_CLIENT_ID,
client_secret: process.env.PASSAGE_CLIENT_SECRET
})
});

const { access_token } = await response.json();
return access_token;
}

API Reference

Configuration

import { configure } from '@getpassage/expo';

interface PassageConfig {
debug?: boolean; // Enable debug logging (default: false)
baseUrl?: string; // Custom base URL (optional)
socketUrl?: string; // Custom socket URL (optional)
}

configure(config: PassageConfig): void;

Core Methods

import { open, close } from '@getpassage/expo';

// Open connection flow
await open({
token: string;
onConnectionComplete?: (data: PassageSuccessData) => void;
onConnectionError?: (error: PassageErrorData) => void;
onDataComplete?: (data: PassageDataResult) => void;
onExit?: (reason?: string) => void;
});

// Close connection programmatically
close(): void;

Data Types

PassageSuccessData

interface PassageSuccessData {
history: PassageHistoryItem[]; // Captured data items
connectionId: string; // Unique connection identifier
}

interface PassageHistoryItem {
vendorId: string; // Unique identifier for this item
structuredData?: any; // Integration-specific data (see Integration Data Types)
rawData?: any; // Original raw data from the service
enrichedData?: any; // Additional enriched data (where available)
}

📚 For detailed data structures returned by each integration, see Integration Data Types →

PassageErrorData

interface PassageErrorData {
error: string; // Error message
data?: any; // Additional error data
}

PassageDataResult

interface PassageDataResult {
data?: any; // Raw data payload
prompts?: Record<string, any>[]; // Associated prompts
}

Performance

The Passage Expo SDK is designed to be lightweight with minimal impact on your app's performance.

Benchmarking Results

Performance measurements conducted on iPhone 15 Pro with iOS 17 using real-time monitoring during typical SDK usage:

Kroger.com (Complex E-commerce Website)

MetricBaselineWith Passage SDKOverheadMaximum Spikes
Memory9.4 MB23.3 MB+13.9 MB25.5 MB during page load
CPU0.0%0.0% avgMinimal18.4% during page load
App StartupN/AN/ANo impactN/A

Audible.com (Media/Content Website)

MetricBaselineWith Passage SDKOverheadMaximum Spikes
Memory8.9 MB23.9 MB+15.0 MB25.1 MB during page load
CPU0.0%0.0% avgMinimal7.1% during page load
App StartupN/AN/ANo impactN/A

Performance Characteristics

  • Memory Usage: ~14-15 MB overhead for full SDK functionality including WebView rendering
  • CPU Impact: Minimal sustained usage with brief spikes during web page loading operations
    • Complex e-commerce sites: Spikes up to 18.4% during heavy page loads
    • Media content sites: Spikes up to 7.1% during typical page operations
  • Web Page Complexity: Performance scales with website complexity and JavaScript usage
  • UI Responsiveness: Maintains smooth 120/60+ FPS during normal usage
  • Startup Time: No measurable impact on app launch performance
  • Memory Management: WebView instances are automatically released after sessions to free memory while preserving cookies/localStorage for user convenience

Integration Data Types

  • Browse integrations and resources in the explorer

Support

If you encounter issues: