React Native SDK

The Calljmp React Native SDK provides a comprehensive solution for mobile backend development, featuring secure authentication, direct SQLite database access, file storage, and custom service integration. Built with security-first principles, it uses App Attestation (iOS) and Play Integrity (Android) instead of traditional API keys.

Key Features

  • Secure Authentication - Email/password authentication with user tagging
  • Direct Database Access - Full SQLite database access with parameterized queries
  • File Storage - Secure cloud storage with bucket organization
  • Device Attestation - App Attestation (iOS) and Play Integrity (Android)
  • Custom Services - Integration with custom backend services
  • Project Management - Multi-project support with connection management

Installation

Install the Calljmp SDK in your React Native project:

npm install @calljmp/react-native

Platform Setup

For iOS, navigate to the ios directory and install pods:

cd ios && pod install && cd ..

For React Native versions below 0.60, manually link the SDK:

react-native link @calljmp/react-native

Quick Start

Initialize the SDK

Import and initialize Calljmp in your React Native app:

App.tsx

import { Calljmp } from '@calljmp/react-native'

const calljmp = new Calljmp({
  android: {
    cloudProjectNumber: GOOGLE_CLOUD_PROJECT_NUMBER,
  },
  // Optional: Development configuration
  development: {
    enabled: __DEV__,
    apiToken: CALLJMP_DEV_API_TOKEN,
  },
})

Authentication

Calljmp provides secure email/password authentication with user tagging and flexible policies.

Email Authentication

Authenticate users with email and password:

import { UserAuthenticationPolicy } from '@calljmp/react-native'

const result = await calljmp.users.auth.email.authenticate({
  email: 'user@example.com',
  password: 'securePassword123',
  policy: UserAuthenticationPolicy.SignInOrCreate,
  tags: ['role:user', 'department:sales'],
})

if (result.error) {
  console.error('Authentication failed:', result.error)
  return
}

const user = result.data.user
console.log('User authenticated:', user.email)
console.log('User tags:', user.tags)

Authentication Policies

Control authentication behavior with different policies:

// Only create new accounts, fail if account exists
await calljmp.users.auth.email.authenticate({
  email: 'newuser@example.com',
  password: 'password',
  policy: UserAuthenticationPolicy.CreateNewOnly,
})

// Only sign into existing accounts, fail if account doesn't exist
await calljmp.users.auth.email.authenticate({
  email: 'existinguser@example.com',
  password: 'password',
  policy: UserAuthenticationPolicy.SignInExistingOnly,
})

// Try to sign in, create account if it doesn't exist (default)
await calljmp.users.auth.email.authenticate({
  email: 'user@example.com',
  password: 'password',
  policy: UserAuthenticationPolicy.SignInOrCreate,
})

Database Operations

Calljmp provides direct SQLite database access with full SQL support and row-level security.

Basic Queries

Execute SQL queries directly:

// Simple SELECT query
const result = await calljmp.database.query({
  sql: 'SELECT id, email, tags, created_at FROM users',
  params: [],
})

if (result.error) {
  console.error('Query failed:', result.error)
  return
}

console.log(`Found ${result.data.results.length} users`)
result.data.results.forEach(row => {
  console.log(`User: ${row.email} (${row.tags})`)
})

Parameterized Queries

Use parameterized queries for security:

// Query with parameters
const result = await calljmp.database.query({
  sql: 'SELECT * FROM posts WHERE author_id = ? AND status = ?',
  params: [userId, 'published'],
})

Using SQLite

You can also integrate custom ORMs or wrapper libraries to enhance your database experience. For example, you can use Kysely or Drizzle to create a type-safe database layer on top of the Calljmp SQLite database.

File Storage

Calljmp provides secure cloud storage with bucket organization and access controls.

Upload Files

Upload various types of content:

// Upload text content
const textFile = await calljmp.storage.upload({
  bucketId: 'documents',
  key: 'messages/hello.txt',
  content: 'Hello, world! This is my first text file.',
  type: 'text/plain',
  description: 'A simple greeting',
  tags: ['text', 'greeting'],
})

if (textFile.error) {
  console.error('Upload failed:', textFile.error)
  return
}

console.log('File uploaded:', textFile.data.key)
console.log('File size:', textFile.data.size, 'bytes')

Upload Binary Files

// Upload image from blob
const imageBlob = new Blob([imageData], { type: 'image/jpeg' })
const imageFile = await calljmp.storage.upload({
  bucketId: 'user-uploads',
  key: `profiles/user_${userId}/avatar.jpg`,
  content: imageBlob,
  type: 'image/jpeg',
  description: 'User profile picture',
  tags: ['profile', 'avatar', `user:${userId}`],
})

List Files

Browse files in a bucket:

const result = await calljmp.storage.list({
  bucketId: 'user-uploads',
  limit: 50,
  offset: 0,
})

if (result.error) {
  console.error('Failed to list files:', result.error)
  return
}

console.log(`Found ${result.data.files.length} files`)
result.data.files.forEach(file => {
  console.log(`${file.key}: ${file.size} bytes`)
  console.log(`  Description: ${file.description}`)
  console.log(`  Tags: ${file.tags}`)
})

Download Files

Retrieve file content:

// Get file metadata without downloading content
const fileInfo = await calljmp.storage.peek({
  bucketId: 'user-uploads',
  key: 'profiles/user_123/avatar.jpg',
})

if (fileInfo.error) {
  console.error('Peek failed:', fileInfo.error)
  return
}

console.log('File size:', fileInfo.data.size, 'bytes')
console.log('Content type:', fileInfo.data.type)

// Download file content
const fileContent = await calljmp.storage.retrieve({
  bucketId: 'user-uploads',
  key: 'profiles/user_123/avatar.jpg',
})

Update File Metadata

await calljmp.storage.update({
  bucketId: 'user-uploads',
  key: 'profiles/user_123/avatar.jpg',
  description: 'Updated profile picture',
  tags: ['profile', 'avatar', 'updated'],
})

Delete Files

await calljmp.storage.delete({
  bucketId: 'user-uploads',
  key: 'profiles/user_123/old_avatar.jpg',
})

Custom Services

Calljmp provides a simple way to access your backend services. You can make HTTP requests to your service endpoints using the calljmp.service.

Basic Service Calls

// Simple GET request
const result = await calljmp.service
  .request('/')
  .get()
  .json<{ message: string }>()

if (result.error) {
  console.error('Service call failed:', result.error)
  return
}

console.log('Response:', result.data)

POST Requests with Data

// POST request with JSON body
const response = await calljmp.service
  .request('/api/posts')
  .post()
  .json({
    title: 'My First Post',
    content: 'This is the content of my first post.',
    tags: ['tutorial', 'beginner'],
  })
  .json<{ id: number; title: string }>()

if (response.error) {
  console.error('Failed to create post:', response.error)
  return
}

console.log('Created post:', response.data)

Custom Headers and Query Parameters

// Request with custom headers and query parameters
const response = await calljmp.service
  .request('/api/search')
  .headers({
    'X-Custom-Header': 'value',
    'Accept-Language': 'en-US',
  })
  .params({
    q: 'react native development',
    limit: '20',
    offset: '0',
  })
  .get()
  .json<SearchResults>()

Error Handling

Calljmp uses a consistent error handling pattern across all operations:

// All operations return Result<T, CalljmpError>
const result = await calljmp.database.query({
  sql: 'SELECT * FROM users',
  params: [],
})

if (result.error) {
  const error = result.error

  switch (error.code) {
    case 'unauthorized':
      console.log('User not authenticated')
      // Redirect to login screen
      break
    case 'forbidden':
      console.log('User lacks permission for this operation')
      break
    case 'usage_exceeded':
      console.log('Usage limit exceeded')
      // Show upgrade prompt
      break
    case 'network_error':
      console.log('Network connection failed')
      // Show retry option
      break
    default:
      console.log('Unexpected error:', error.message)
  }
} else {
  // Success - use result.data
  const data = result.data
  // Process successful result
}

Common Error Codes

  • unauthorized: User is not authenticated
  • forbidden: User lacks required permissions
  • bad_request: Invalid request parameters
  • not_found: Requested resource doesn't exist
  • usage_exceeded: Account usage limits exceeded
  • network_error: Network connectivity issues
  • internal: Server-side error occurred

Best Practices

Security

  1. Use User Tags: Implement role-based access control with user tags
  2. Parameterized Queries: Always use parameters to prevent SQL injection
  3. Validate Input: Validate all user input before database operations
  4. Least Privilege: Give users minimum required permissions

Performance

  1. Limit Query Results: Use LIMIT and OFFSET for pagination
  2. Index Database Columns: Create indexes for frequently queried columns
  3. Cache Results: Cache frequently accessed data locally
  4. Optimize File Uploads: Compress images and limit file sizes

Development

  1. Handle Errors: Always check for errors in result objects
  2. Test Offline: Ensure graceful handling of network failures
  3. Monitor Usage: Track API usage to avoid limits

Security

Calljmp provides robust security features to ensure that your app and its data are protected.

App Attestation (iOS)

Calljmp utilizes Apple's App Attestation for verifying the integrity of your iOS app. This ensures that only legitimate apps are communicating with your backend.

For more information, refer to Apple's App Attestation documentation.

Play Integrity (Android)

For Android apps, Google Play Integrity helps ensure that your app is genuine and hasn't been tampered with.

For more details, check out Google Play Integrity documentation.

These features enhance the security of your app, ensuring that your backend communications are from trusted sources.

Next Steps

For more advanced topics and examples, check out: