Documentation

View Examples →

Quick Start

Step 1: Install the SDK

npm install @aisahub/app-builder-sdk

Or import directly in your JavaScript modules

Step 2: Check Environment

import AppBridge from '@aisahub/app-builder-sdk';

if (AppBridge.isInApp()) {
    console.log('Running in app environment');
    // Use native features
} else {
    console.log('Running in web browser');
    // Use web alternatives
}

Step 3: Use the Promise-based API

// All methods return Promises with Result structure
async function setupNotifications() {
    try {
        const result = await AppBridge.generateFcmToken();
        if (result.success && result.data) {
            console.log('FCM Token:', result.data.token);
            // Send to your backend
            await saveTokenToBackend(result.data.token);
        }
    } catch (error) {
        console.error('Error:', error.error);
    }
}

Installation

NPM Package (Recommended)

npm install @aisahub/app-builder-sdk

Then import in your JavaScript/TypeScript files:

import AppBridge from '@aisahub/app-builder-sdk';

// All methods return Promises
const result = await AppBridge.generateFcmToken();

Understanding the Result Interface

All SDK methods return a Promise that resolves to a standardized Result<T> interface. This provides consistent error handling and type safety across all operations.

Result Interface Structure

interface Result<T> {
  success: boolean;  // true if operation succeeded, false otherwise
  error?: string;    // Error message(present when success is false)
  data?: T;          // Response data(present when success is true)
}

Usage Pattern

// Method 1: Check success flag
const result = await AppBridge.generateFcmToken();
if (result.success && result.data) {
  console.log('Token:', result.data.token);
  // Use result.data safely
} else if (result.error) {
  console.error('Error:', result.error);
}

// Method 2: Try-catch for error handling
try {
  const result = await AppBridge.generateFcmToken();
  if (result.success && result.data) {
    await sendTokenToBackend(result.data.token);
  }
} catch (error) {
  console.error('Failed:', error.error);
}

TypeScript Support

The SDK includes full TypeScript definitions. The generic T type represents the shape of the data returned for each specific method:

// FCM Token returns {token: string}
const fcmResult: Result<{token: string}> = await AppBridge.generateFcmToken();

// Device info returns detailed device data
const deviceResult: Result<{
  model: string;
  manufacturer: string;
  osVersion: string;
  platform: string;
}> = await AppBridge.getDeviceInfo();

// Secure storage returns {key: string, value: string}
const storageResult: Result<{key: string, value: string}> =
  await AppBridge.getSecure('myKey');

API Reference

AppBridge.isInApp()

Check if the web app is running inside any supported app environment (native app or PWA). Use this method for most environment checks.

Returns: boolean

if (AppBridge.isInApp()) {
    // Enable app features
    await enablePushNotifications();
} else {
    // Use web alternatives
    enableEmailNotifications();
}

// For more specific checks:
AppBridge.isNativeApp() // true if Flutter WebView
AppBridge.isPWA()       // true if PWA iframe

AppBridge.generateFcmToken()

Request an FCM token for push notifications. Call this after user login/registration.

Returns: Promise<Result<{token: string}>>

try {
    const result = await AppBridge.generateFcmToken();
    if (result.success && result.data) {
        console.log('FCM Token:', result.data.token);
        // Send to backend
        await fetch('/api/save-token', {
            method: 'POST',
            body: JSON.stringify({ fcm_token: result.data.token })
        });
    }
} catch (error) {
    console.error('Error:', error.error);
}

Authentication

AppBridge.notifySuccessLogin()

Notify the native app of successful authentication. Call this after user successfully logs in on the client side. The native app will handle navigation after receiving this notification.

Returns: Promise<Result<{message: string}>>

async function handleLogin(email, password) {
    try {
        // 1. Perform client-side login first
        const response = await fetch('/api/auth/login', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ email, password })
        });

        if (!response.ok) {
            throw new Error('Login failed');
        }

        const { token, user } = await response.json();
        console.log('Login successful:', user);

        // 2. Handle based on environment
        if (AppBridge.isInApp()) {
            // Save token and user data to secure storage
            await AppBridge.saveSecure('auth_token', token);
            await AppBridge.saveSecure('user_data', JSON.stringify(user));

            // Notify app - it will handle navigation
            await AppBridge.notifySuccessLogin();
        } else {
            // Web browser: store token and redirect manually
            localStorage.setItem('auth_token', token);
            window.location.href = '/dashboard';
        }
    } catch (error) {
        console.error('Login failed:', error);
    }
}

AppBridge.logout()

Logout the user and clear all session data including FCM token. The native app will handle navigation after logout.

Returns: Promise<Result<{message: string}>>

async function handleLogout() {
    // 1. Call backend logout
    await fetch('/api/auth/logout', { method: 'POST' });

    // 2. Handle based on environment
    if (AppBridge.isInApp()) {
        // Clear app session - app handles navigation
        await AppBridge.logout();
    } else {
        // Web browser: clear storage and redirect
        localStorage.clear();
        sessionStorage.clear();
        window.location.href = '/login';
    }
}

AppBridge.getAppVersion()

Get the current app version and build number.

Returns: Promise<Result<{version: string, buildNumber: string}>>

const result = await AppBridge.getAppVersion();
if (result.success && result.data) {
    console.log('Version:', result.data.version);
    console.log('Build:', result.data.buildNumber);
}

Secure Storage

AppBridge.saveSecure(key, value)

Save data to secure encrypted storage on the device.

Parameters:

  • key (string): Storage key
  • value (any): Value to store

Returns: Promise<Result<{key: string}>>

try {
    const result = await AppBridge.saveSecure('user_preferences', JSON.stringify(prefs));
    if (result.success) {
        console.log('Saved successfully');
    }
} catch (error) {
    console.error('Save failed:', error.error);
}

AppBridge.getSecure(key)

Retrieve data from secure storage.

Parameters:

  • key (string): Storage key

Returns: Promise<Result<{key: string, value: string}>>

try {
    const result = await AppBridge.getSecure('user_preferences');
    if (result.success && result.data) {
        const prefs = JSON.parse(result.data.value);
        console.log('Preferences:', prefs);
    }
} catch (error) {
    console.error('Failed to get data:', error.error);
}

AppBridge.showToast(message, duration)

Show a native toast message. This method is synchronous and returns immediately.

Parameters:

  • message (string): Message to display
  • duration ('short' | 'long'): Duration (default: 'short')

Returns: boolean

AppBridge.showToast('Login successful!', 'short');
AppBridge.showToast('Processing your request...', 'long');

AppBridge.share(text, url)

Open the native share dialog.

Parameters:

  • text (string): Text to share
  • url (string): URL to share (optional)

Returns: Promise<Result<{shared: boolean}>>

try {
    const result = await AppBridge.share(
        'Check out this app!',
        'https://yourapp.com'
    );
    if (result.success) {
        console.log('Shared successfully');
    }
} catch (error) {
    console.error('Share failed:', error.error);
}

Text-to-Speech

The Text-to-Speech API allows you to convert text to speech using the native TTS engine.

Supported Languages

Language CodeLanguage
en-USEnglish (US)
en-GBEnglish (UK)
es-ESSpanish (Spain)
es-MXSpanish (Mexico)
fr-FRFrench
de-DEGerman
it-ITItalian
pt-BRPortuguese (Brazil)
ja-JPJapanese
ko-KRKorean
zh-CNChinese (Simplified)
zh-TWChinese (Traditional)
ru-RURussian
ar-SAArabic
hi-INHindi

AppBridge.speak(text, options)

Speak text using native Text-to-Speech engine. Returns a ttsId that can be used to track speech completion. Only available in app environment.

Parameters:

  • text (string): Text to speak
  • options.language (string): Language code (default: 'en-US')
  • options.rate (number): Speech rate 0.0-1.0 (default: 0.5)
  • options.pitch (number): Voice pitch 0.5-2.0 (default: 1.0)
  • options.volume (number): Volume 0.0-1.0 (default: 1.0)

Returns: Promise<Result<{message: string, ttsId: string}>>

// Basic usage with completion tracking
const result = await AppBridge.speak('Hello, world!');
if (result.success) {
    console.log('Speech started, ttsId:', result.data.ttsId);

    // Wait for completion(non-blocking)
    // Call in background, don't await in UI thread
    AppBridge.isTtsComplete(result.data.ttsId).then(() => {
        console.log('Speech finished!');
    });
}

// With options
await AppBridge.speak('Welcome to the app!', {
    language: 'en-US',
    rate: 0.5,
    pitch: 1.0,
    volume: 1.0
});

AppBridge.isTtsComplete(ttsId)

Wait for a specific TTS utterance to complete. Returns a promise that resolves when speech finishes. Call this in the background (don't await in UI thread) to avoid blocking the UI.

Parameters:

  • ttsId (string): The TTS ID returned from speak()

Returns: Promise<Result<{ttsId: string, completed: boolean}>>

// Start speech and track completion
const result = await AppBridge.speak('Hello, world!');
if (result.success && result.data) {
    const ttsId = result.data.ttsId;
    console.log('Speech started, ttsId:', ttsId);

    // Wait for completion(non-blocking)
    // IMPORTANT: Don't await this in UI thread
    AppBridge.isTtsComplete(ttsId).then(() => {
        console.log('Speech finished!');
        // Update UI, enable buttons, etc.
    }).catch((error) => {
        console.error('TTS completion check failed:', error.error);
    });
}

// React hook handles this automatically
// The useTTS() hook tracks completion internally

AppBridge.cancelSpeech()

Cancel any ongoing Text-to-Speech playback.

Returns: Promise<Result<{message: string}>>

try {
    const result = await AppBridge.cancelSpeech();
    if (result.success) {
        console.log('Speech cancelled');
    }
} catch (error) {
    console.error('Cancel failed:', error.error);
}

AppBridge.getTTSLanguages()

Get a list of installed/available TTS languages on the device.

Returns: Promise<Result<{languages: string[]}>>

try {
    const result = await AppBridge.getTTSLanguages();
    if (result.success && result.data) {
        console.log('Available languages:', result.data.languages);
        // ['en-US', 'es-ES', 'fr-FR', ...]
    }
} catch (error) {
    console.error('Failed to get languages:', error.error);
}

AppBridge.isTTSLanguageAvailable(language)

Check if a specific TTS language is available on the device. Works on both iOS and Android.

Parameters:

  • language (string): Language code (e.g., 'en-US', 'es-ES')

Returns: Promise<Result<{language: string, available: boolean}>>

try {
    const result = await AppBridge.isTTSLanguageAvailable('es-ES');
    if (result.success && result.data) {
        if (result.data.available) {
            console.log('Spanish is available!');
        } else {
            console.log('Spanish is not available');
        }
    }
} catch (error) {
    console.error('Check failed:', error.error);
}

AppBridge.isTTSLanguageInstalled(language)

Check if a specific TTS language is installed on the device. Android only - on iOS, use isTTSLanguageAvailable() instead.

Parameters:

  • language (string): Language code (e.g., 'ja-JP', 'ko-KR')

Returns: Promise<Result<{language: string, installed: boolean}>>

// Android only
try {
    const result = await AppBridge.isTTSLanguageInstalled('ja-JP');
    if (result.success && result.data) {
        if (result.data.installed) {
            console.log('Japanese is installed!');
        } else {
            console.log('Japanese needs to be downloaded');
            // Prompt user to open settings
            await AppBridge.openTTSSettings();
        }
    }
} catch (error) {
    console.error('Check failed:', error.error);
}

AppBridge.openTTSSettings()

Open the device's TTS settings where users can download and install additional language packs.

Returns: Promise<Result<{message: string}>>

try {
    const result = await AppBridge.openTTSSettings();
    if (result.success) {
        console.log('Settings opened');
    }
} catch (error) {
    console.error('Failed to open settings:', error.error);
}

Using with Next.js

1. Install the Package

npm install @aisahub/app-builder-sdk

2. Use with Async/Await in Components

'use client';

import { useAppBridge } from '@aisahub/app-builder-sdk/hooks';
import { useEffect } from 'react';

export default function MyComponent() {
  const { bridge, isNativeApp, isReady } = useAppBridge();

  useEffect(() => {
    if (isReady && isNativeApp && bridge) {
      async function setupToken() {
        try {
          const result = await bridge.generateFcmToken();
          if (result.success && result.data) {
            console.log('Token:', result.data.token);
          }
        } catch (error) {
          console.error('Error:', error.error);
        }
      }
      setupToken();
    }
  }, [isReady, isNativeApp, bridge]);

  return (
    <div>
      {isReady ? (
        <p>Running in: {isNativeApp ? 'Native App' : 'Browser'}</p>
      ) : (
        <p>Loading...</p>
      )}
    </div>
  );
}

3. Using Specialized Hooks

import { useGenerateFcmToken, useDeviceInfo } from '@aisahub/app-builder-sdk/hooks';

function MyComponent() {
  const { generateToken, loading, token, error } = useGenerateFcmToken();
  const { deviceInfo } = useDeviceInfo();

  return (
    <div>
      <p>Device: {deviceInfo?.model}</p>
      <button onClick={generateToken} disabled={loading}>
        {loading ? 'Generating...' : 'Get FCM Token'}
      </button>
      {token && <p>Token: {token}</p>}
      {error && <p className="text-red-600">Error: {error}</p>}
    </div>
  );
}

4. Direct SDK Usage

import AppBridge from '@aisahub/app-builder-sdk';

async function handleLogin() {
  if (!AppBridge.isInApp()) {
    console.log('Not in app environment');
    return;
  }

  try {
    // Generate FCM token
    const tokenResult = await AppBridge.generateFcmToken();
    if (tokenResult.success && tokenResult.data) {
      // Save to backend
      await saveToken(tokenResult.data.token);
    }

    // Save user data securely
    const saveResult = await AppBridge.saveSecure('user_id', userId);
    if (saveResult.success) {
      console.log('User data saved');
    }
  } catch (error) {
    console.error('Setup failed:', error.error);
  }
}

Best Practices

Always Check App Environment

Good
if (AppBridge.isInApp()) {
    const result = await AppBridge.generateFcmToken();
    // Use app features
} else {
    // Web alternative
    setupEmailNotifications();
}
Bad
// Will cause errors in browser
await AppBridge.generateFcmToken();

Implement Timeouts for Async Operations

Good
async function withTimeout(promise, timeoutMs = 5000) {
    const timeout = new Promise((_, reject) =>
        setTimeout(() => reject({
            success: false,
            error: 'Operation timeout'
        }), timeoutMs)
    );
    return Promise.race([promise, timeout]);
}

try {
    const result = await withTimeout(
        AppBridge.generateFcmToken(),
        5000
    );
    console.log('Success:', result);
} catch (error) {
    console.error('Error or timeout:', error.error);
}
Bad
// No timeout - could hang forever
const result = await AppBridge.generateFcmToken();
console.log(result);

Handle Errors Gracefully

Good
async function handleLogout() {
    if (AppBridge.isInApp()) {
        try {
            // App handles navigation after logout
            await AppBridge.logout();
        } catch (error) {
            console.error('Logout failed:', error.error);
        }
    } else {
        // Web browser: clear storage and redirect
        localStorage.clear();
        sessionStorage.clear();
        window.location.href = '/login';
    }
}
Bad
// Don't redirect in app environment - native app handles it
await AppBridge.logout();
window.location.href = '/login';

Access Data from Result Structure

Good
const result = await AppBridge.generateFcmToken();
if (result.success && result.data) {
    // Access nested data property
    console.log('Token:', result.data.token);
    await saveToken(result.data.token);
}
Bad
const result = await AppBridge.generateFcmToken();
// Wrong - data is nested
console.log('Token:', result.token);

Troubleshooting

Issue: AppBridge is not defined

SDK package not imported. Ensure you've installed and imported the package correctly.

// Install the package
npm install @aisahub/app-builder-sdk

// Import in your code
import AppBridge from '@aisahub/app-builder-sdk';

if (AppBridge && typeof AppBridge.isNativeApp === 'function') {
    console.log('SDK loaded successfully');
    initializeApp();
} else {
    console.error('SDK not found');
}

Issue: Promises never resolve

Ensure you're running in app environment and implement timeouts for safety.

if (!AppBridge.isInApp()) {
    console.warn('Not in app environment - app features unavailable');
    return;
}

// Add timeout wrapper
async function withTimeout(promise, ms = 5000) {
    const timeout = new Promise((_, reject) =>
        setTimeout(() => reject({ error: 'Timeout' }), ms)
    );
    return Promise.race([promise, timeout]);
}

try {
    const result = await withTimeout(
        AppBridge.generateFcmToken(),
        5000
    );
    console.log('Success:', result);
} catch (error) {
    console.error('Error or timeout:', error);
}

Issue: FCM token not saving

Check backend API endpoint and network connection. Ensure you're accessing result.data.

try {
    const result = await AppBridge.generateFcmToken();
    console.log('FCM Result:', result);

    if (result.success && result.data) {
        // Access token from nested data
        const response = await fetch('/api/save-token', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ token: result.data.token })
        });

        console.log('Backend status:', response.status);
        const data = await response.json();
        console.log('Backend response:', data);
    }
} catch (error) {
    console.error('Error:', error);
}

Issue: TypeScript errors with result.data

The SDK uses a Result<T> structure. Always check result.data exists before accessing properties.

// Correct TypeScript usage
const result = await AppBridge.generateFcmToken();

// Type guard
if (result.success && result.data) {
    // TypeScript knows result.data.token exists
    const token: string = result.data.token;
    console.log(token);
}

// Or use optional chaining
const token = result.data?.token;
if (token) {
    console.log('Token:', token);
}