npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@teamvortexsoftware/vortex-react-native

v1.0.3

Published

Vortex React Native SDK

Downloads

658

Readme

Vortex React Native SDK

Configuration-driven invitation forms for React Native apps.

Overview

The Vortex React Native SDK provides a React Native component for rendering customizable invitation forms in your React Native applications. The SDK fetches configuration from the Vortex platform and dynamically renders the appropriate UI.

Requirements

  • React 18+
  • React Native 0.75.0+

Installation

# Using pnpm (recommended)
pnpm add @teamvortexsoftware/vortex-react-native

# Using npm
npm install @teamvortexsoftware/vortex-react-native

# Using yarn
yarn add @teamvortexsoftware/vortex-react-native

Quick Start

Basic Usage

import { VortexInvite } from '@teamvortexsoftware/vortex-react-native';
import { useState, useEffect } from 'react';

function MyInviteScreen() {
  const [jwt, setJwt] = useState<string | null>(null);

  useEffect(() => {
    // Generate JWT for authenticated user
    const generateJwt = async () => {
      try {
        const response = await fetch('https://your-api.com/api/vortex/jwt', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
        });
        const data = await response.json();
        setJwt(data.jwt);
      } catch (error) {
        console.error('Failed to generate JWT:', error);
      }
    };

    generateJwt();
  }, []);

  if (!jwt) {
    return <Text>Loading...</Text>;
  }

  return (
    <VortexInvite
      componentId="my-component-id"
      jwt={jwt}
      scope="team-123"
      scopeType="team"
      onInvite={(data) => {
        console.log('Invitation sent:', data);
      }}
      onError={(error) => {
        console.error('Invitation error:', error);
      }}
    />
  );
}

With Group Context

import { VortexInvite } from '@teamvortexsoftware/vortex-react-native';

function TeamInviteScreen({ teamId, teamName }: { teamId: string; teamName: string }) {
  const { jwt, error } = useVortexAuth();

  if (error) {
    return <Text>Error: {error.message}</Text>;
  }

  return (
    <VortexInvite
      componentId={process.env.EXPO_PUBLIC_COMPONENT_ID}
      jwt={jwt || ''}
      scope={teamId}
      scopeType="team"
      onInvite={(result) => {
        console.log('Invitations sent:', result);
      }}
    />
  );
}

Complete Example with Modal

import { useState, useEffect } from 'react';
import { Modal, View, Button, StyleSheet } from 'react-native';
import { VortexInvite } from '@teamvortexsoftware/vortex-react-native';

function InviteModal({
  visible,
  onClose,
  workspaceId,
}: {
  visible: boolean;
  onClose: () => void;
  workspaceId: string;
}) {
  const [jwt, setJwt] = useState<string>('');

  useEffect(() => {
    if (visible) {
      // Generate JWT when modal opens
      fetchJwt().then(setJwt);
    }
  }, [visible]);

  return (
    <Modal visible={visible} animationType="slide">
      <View style={styles.container}>
        <View style={styles.header}>
          <Button title="Close" onPress={onClose} />
        </View>

        <VortexInvite
          componentId={process.env.EXPO_PUBLIC_COMPONENT_ID}
          jwt={jwt}
          scope={workspaceId}
          scopeType="workspace"
          onInvite={(result) => {
            console.log('Invitations sent successfully');
            onClose();
          }}
          onError={(error) => {
            console.error('Failed to send invitations:', error);
          }}
        />
      </View>
    </Modal>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
  header: {
    padding: 16,
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0',
  },
});

Invite Contacts

The Invite Contacts component displays a list of contacts that can be invited via SMS. Unlike the Contact Import feature (which fetches contacts from the device), Invite Contacts receives a pre-populated list of contacts from your app.

Basic Usage:

import { VortexInvite, InviteContactsConfig, InviteContactsContact } from '@teamvortexsoftware/vortex-react-native';

const contacts: InviteContactsContact[] = [
  { name: "Alice Johnson", phoneNumber: "+1 (555) 123-4567" },
  { name: "Bob Smith", phoneNumber: "+1 (555) 234-5678" },
  { name: "Carol Davis", phoneNumber: "+1 (555) 345-6789" }
];

<VortexInvite
  componentId="your-component-id"
  jwt={jwt}
  inviteContactsConfig={{ contacts }}
  onInvite={(result) => {
    console.log('Invitations sent:', result);
  }}
/>

How It Works:

  1. The component shows an "Invite your contacts" entry with a contact count
  2. Tapping it navigates to a searchable list of contacts
  3. Each contact has an "Invite" button that:
    • Creates an SMS invitation via the Vortex API
    • Opens the device's SMS app (on supported devices)
    • Pre-fills the message with the invitation link

InviteContactsContact Properties:

// Simple usage - just name and phone number (ID is auto-generated)
{
  name: string;            // Display name
  phoneNumber: string;     // Phone number for SMS
  avatarUrl?: string;      // Optional avatar image URL
  metadata?: Record<string, any>; // Optional custom metadata
}

// Full usage - with custom ID
{
  id: string;              // Custom unique identifier
  name: string;            // Display name
  phoneNumber: string;     // Phone number for SMS
  avatarUrl?: string;      // Optional avatar image URL
  metadata?: Record<string, any>; // Optional custom metadata
}

InviteContactsConfig Properties:

{
  contacts: InviteContactsContact[];  // List of contacts to display
}

Find Friends

The Find Friends component displays a list of contacts provided by your app. Each contact has a "Connect" button that creates an invitation with targetType: internalId.

Basic Usage:

import { VortexInvite, FindFriendsConfig, FindFriendsContact } from '@teamvortexsoftware/vortex-react-native';

<VortexInvite
  componentId="your-component-id"
  jwt={jwt}
  findFriendsConfig={{
    contacts: [
      { internalId: "user-123", name: "Alice Johnson", subtitle: "@alice" },
      { internalId: "user-456", name: "Bob Smith", subtitle: "@bob" }
    ],
    onInvitationCreated: (contact) => {
      // Called after an invitation is successfully created
      console.log('Invitation created for', contact.name);
    }
  }}
/>

How It Works:

  1. Your app provides a list of contacts with internal IDs (users already in your platform)
  2. The component displays them with a "Connect" button (text configurable via widget config)
  3. When the user taps "Connect", the SDK creates an invitation via the Vortex API with targetType: internalId
  4. The onInvitationCreated callback is called after a successful invitation
  5. The section is hidden when there are no contacts to display

FindFriendsContact Properties:

{
  internalId: string;          // Required: ID in your platform
  name: string;                // Required: Display name
  subtitle?: string;           // Optional: Secondary text (e.g., username)
  avatarUrl?: string;          // Optional: Avatar image URL
  metadata?: Record<string, any>; // Optional: Custom metadata
}

FindFriendsConfig Properties:

{
  contacts: FindFriendsContact[];                        // Required: List of contacts to display
  onInvitationCreated?: (contact: FindFriendsContact) => void; // Optional: Called after successful invitation
}

Invitation Suggestions

The Invitation Suggestions component displays a list of suggested contacts provided by your app. Each contact has an "Invite" button and a dismiss (X) button. When the user taps "Invite", an invitation with targetType: internalId is created. The dismiss button removes the suggestion from the list.

Basic Usage:

import { VortexInvite, InvitationSuggestionsConfig, InvitationSuggestionContact } from '@teamvortexsoftware/vortex-react-native';

<VortexInvite
  componentId="your-component-id"
  jwt={jwt}
  invitationSuggestionsConfig={{
    contacts: [
      {
        internalId: "user-123",
        name: "Alice Johnson",
        subtitle: "@alice",
        avatarUrl: "https://example.com/alice-avatar.jpg"
      },
      {
        internalId: "user-456",
        name: "Bob Smith",
        subtitle: "@bob"
      }
    ],
    onDismiss: (contact) => {
      // Called when user taps the X button
      console.log('Dismissed suggestion for', contact.name);
    },
    onInvitationCreated: (contact) => {
      console.log('Invitation created for', contact.name);
    },
    onInvitationFailed: (contact, error) => {
      console.error('Failed to invite', contact.name, error);
    }
  }}
/>

How It Works:

  1. Your app provides a list of suggested contacts with internal IDs
  2. The component displays them with an "Invite" button and a dismiss (X) button
  3. When the user taps "Invite", the SDK creates an invitation via the Vortex API with targetType: internalId
  4. The onInvitationCreated or onInvitationFailed callback is called based on the result
  5. When the user taps the X button, the onDismiss callback is called and the contact is removed from the list

InvitationSuggestionContact Properties:

{
  internalId: string;          // Required: ID in your platform
  name: string;                // Required: Display name
  subtitle?: string;           // Optional: Secondary text (e.g., username)
  avatarUrl?: string;          // Optional: Avatar image URL (rendered instead of initials if provided)
  metadata?: Record<string, any>; // Optional: Custom metadata
}

InvitationSuggestionsConfig Properties:

{
  contacts: InvitationSuggestionContact[];                      // Required: List of contacts to display
  onDismiss: (contact: InvitationSuggestionContact) => void;    // Required: Called when user dismisses a suggestion
  onInvitationCreated?: (contact: InvitationSuggestionContact) => void;  // Called after successful invitation
  onInvitationFailed?: (contact: InvitationSuggestionContact, error: Error) => void;  // Called on failure
}

Search Box

The Search Box component displays a search input with a search button. When the user taps the search button, your app's onSearch callback is invoked to return matching contacts. The results are rendered below the search box with "Connect" buttons, identical to the Find Friends component.

Basic Usage:

import { VortexInvite, SearchBoxConfig, SearchBoxContact } from '@teamvortexsoftware/vortex-react-native';

<VortexInvite
  componentId="your-component-id"
  jwt={jwt}
  searchBoxConfig={{
    onSearch: async (query) => {
      // Return matching contacts from your backend or local data
      const results = await myAPI.searchUsers(query);
      return results.map(user => ({
        internalId: user.id,
        name: user.displayName,
        subtitle: `@${user.username}`,
        avatarUrl: user.avatarUrl
      }));
    },
    onInvitationCreated: (contact) => {
      console.log('Invitation created for', contact.name);
    }
  }}
/>

How It Works:

  1. The component renders a search text field with a configurable placeholder and a search button
  2. An optional title can be displayed above the search box (configured in the widget editor)
  3. When the user taps the search button, your onSearch callback is called with the query string
  4. Your callback returns a list of SearchBoxContact objects
  5. The matching contacts are rendered below the search box with a "Connect" button next to each
  6. If the search returns no results, a configurable "no results" message is displayed
  7. When the user taps "Connect", the SDK creates an invitation via the Vortex API with targetType: internalId and the contact is removed from the list (identical to Find Friends behavior)

SearchBoxConfig Properties:

{
  onSearch: (query: string) => Promise<SearchBoxContact[]>;      // Required: Search callback
  onInvitationCreated?: (contact: SearchBoxContact) => void;     // Optional: Called after successful invitation
}

Note: The placeholder text, connect button text, no-results message, and title are all configurable from the widget editor.

Incoming Invitations

The Incoming Invitations component displays invitations the user has received, with Accept and Delete actions.

Basic Usage:

import { VortexInvite, IncomingInvitationsConfig } from '@teamvortexsoftware/vortex-react-native';

<VortexInvite
  componentId="your-component-id"
  jwt={jwt}
  incomingInvitationsConfig={{
    onAccept: async (invitation) => {
      // Handle acceptance (return true to proceed with API call)
      await myAPI.acceptInvitation(invitation.id);
      return true;
    },
    onDelete: async (invitation) => {
      // Handle deletion (return true to proceed with API call)
      return true;
    }
  }}
/>

With Internal Invitations:

You can merge your app's invitations with Vortex API invitations:

<VortexInvite
  componentId="your-component-id"
  jwt={jwt}
  incomingInvitationsConfig={{
    internalInvitations: [
      {
        id: "internal-1",
        name: "Alice Johnson",
        subtitle: "[email protected]",
        avatarUrl: "https://example.com/avatar.jpg"
      }
    ],
    onAccept: async (invitation) => {
      if (invitation.isVortexInvitation) {
        // Vortex invitation: return true to let SDK call the Vortex API
        return true;
      } else {
        // Internal/app invitation: handle it yourself
        await myAPI.acceptInvitation(invitation.id);
        return true;  // Return true to remove from list
      }
    },
    onDelete: async (invitation) => {
      if (invitation.isVortexInvitation) {
        return true;  // Let SDK handle the API call
      } else {
        await myAPI.deleteInvitation(invitation.id);
        return true;  // Return true to remove from list
      }
    }
  }}
/>

Identifying Invitation Source:

Use the isVortexInvitation property to determine where an invitation came from:

  • true: Fetched from the Vortex API — the SDK will handle accept/delete API calls
  • false: Provided by your app via internalInvitations — your app must handle the action

IncomingInvitationsConfig Properties:

{
  internalInvitations?: IncomingInvitationItem[];  // App-provided invitations (isVortexInvitation = false)
  onAccept?: (invitation: IncomingInvitationItem) => Promise<boolean>;  // Called when user accepts
  onDelete?: (invitation: IncomingInvitationItem) => Promise<boolean>;  // Called when user deletes
}

Callback Return Values:

| Invitation Source | Return true | Return false | |-------------------|---------------|----------------| | Vortex (isVortexInvitation === true) | SDK calls Vortex API, removes from list | Cancels the action | | Internal (isVortexInvitation === false) | Removes from list (no API call) | Keeps in list |

Outgoing Invitations

The Outgoing Invitations component displays invitations the user has sent, with a Cancel action.

Basic Usage:

import { VortexInvite, OutgoingInvitationsConfig } from '@teamvortexsoftware/vortex-react-native';

<VortexInvite
  componentId="your-component-id"
  jwt={jwt}
  outgoingInvitationsConfig={{
    onCancel: async (invitation) => {
      // Handle cancellation (return true to proceed with API call)
      return true;
    }
  }}
/>

With Internal Invitations:

You can merge your app's outgoing invitations with Vortex API invitations:

<VortexInvite
  componentId="your-component-id"
  jwt={jwt}
  outgoingInvitationsConfig={{
    internalInvitations: [
      {
        id: "internal-1",
        name: "Bob Smith",
        subtitle: "user-12345",  // e.g., user ID or other identifier
        avatarUrl: "https://example.com/avatar.jpg"
      }
    ],
    onCancel: async (invitation) => {
      if (invitation.isVortexInvitation) {
        // Vortex invitation: return true to let SDK call the Vortex API
        return true;
      } else {
        // Internal/app invitation: handle it yourself
        await myAPI.cancelInvitation(invitation.id);
        return true;  // Return true to remove from list
      }
    }
  }}
/>

Identifying Invitation Source:

Use the isVortexInvitation property to determine where an invitation came from:

  • true: Fetched from the Vortex API — the SDK will handle cancel API calls
  • false: Provided by your app via internalInvitations — your app must handle the action

OutgoingInvitationsConfig Properties:

{
  internalInvitations?: OutgoingInvitationItem[];  // App-provided invitations (isVortexInvitation = false)
  onCancel?: (invitation: OutgoingInvitationItem) => Promise<boolean>;  // Called when user cancels
}

Callback Return Values:

| Invitation Source | Return true | Return false | |-------------------|---------------|----------------| | Vortex (isVortexInvitation === true) | SDK calls Vortex API, removes from list | Cancels the action | | Internal (isVortexInvitation === false) | Removes from list (no API call) | Keeps in list |

Prefetch for Instant Rendering

The SDK supports prefetching widget configurations to eliminate loading delays when opening the invite form. This uses a stale-while-revalidate pattern: cached configurations are shown immediately while fresh data is fetched in the background.

Basic Prefetch Example

import { VortexInvite, usePrefetchWidgetConfiguration } from '@teamvortexsoftware/vortex-react-native';
import { useState, useEffect } from 'react';

function InviteModal({ onClose, groupId }) {
  const [jwt, setJwt] = useState<string | undefined>();
  
  // Fetch JWT on mount
  useEffect(() => {
    fetchJwt().then(setJwt);
  }, []);

  // Prefetch widget configuration as soon as JWT is available
  const { widgetConfiguration } = usePrefetchWidgetConfiguration({
    componentId: process.env.EXPO_PUBLIC_COMPONENT_ID,
    vortexApiUrl: 'https://client-api.vortexsoftware.com',
    jwt,
    enabled: !!jwt, // Only prefetch when JWT is ready
  });

  return (
    <VortexInvite
      componentId={process.env.EXPO_PUBLIC_COMPONENT_ID}
      jwt={jwt}
      widgetConfiguration={widgetConfiguration} // Pass prefetched config
      scope={groupId}
      scopeType="team"
      onClose={onClose}
    />
  );
}

How It Works

  1. Without Prefetching (default behavior):

    • Component mounts → Shows loading spinner
    • Fetches configuration → Renders form
  2. With Prefetching (optimized):

    • Parent prefetches configuration early (when JWT becomes available)
    • Component mounts → Renders form immediately (no loading spinner!)
    • Fetches latest configuration in background
    • Updates form if configuration changed

Benefits

Instant rendering - No loading spinner when modal/screen is shown
Always fresh - Component still fetches latest configuration in background
Automatic updates - Form updates if configuration changes on server
Backward compatible - Works perfectly without prefetching (shows loading spinner as before)

Optional Features

The SDK supports optional native features that enhance the user experience. These features require additional libraries that you can install based on your needs.

Why Optional?

These features use native code that must be explicitly installed in your app. The SDK gracefully degrades when these libraries aren't installed:

  • Gradients → Falls back to solid colors
  • Haptics → Silently skipped
  • QR Codes → Shows placeholder message
  • Clipboard → Falls back to web clipboard API, or shows error with install instructions
  • Contacts → Shows error with install instructions when contacts import is triggered
  • Google Sign-In → Shows warning when Google contacts import is triggered

Configuration

Specify which libraries you've installed via the unified modules prop on VortexInvite:

<VortexInvite
  componentId="your-component-id"
  jwt={jwt}
  // Optional feature modules (unified configuration)
  modules={{
    gradient: 'expo-linear-gradient',
    haptics: 'expo-haptics',
    qrCode: 'react-native-qrcode-svg',
    clipboard: 'expo-clipboard',
    contacts: 'expo-contacts',
    googleSignin: '@react-native-google-signin/google-signin',
  }}
/>

Available Features

| Feature | Property | Options | Description | |---------|----------|---------|-------------| | Gradients | modules.gradient | 'expo-linear-gradient' or 'react-native-linear-gradient' | Gradient backgrounds on buttons | | Haptics | modules.haptics | 'expo-haptics' | Haptic feedback on button presses | | QR Codes | modules.qrCode | 'react-native-qrcode-svg' or 'react-qr-code' | QR code display for shareable links | | Clipboard | modules.clipboard | 'expo-clipboard' or '@react-native-clipboard/clipboard' | Clipboard operations (copy link) | | Contacts | modules.contacts | 'expo-contacts' | Device contacts import | | Google Sign-In | modules.googleSignin | '@react-native-google-signin/google-signin' | Google Contacts import | | Sharing | modules.sharing | 'expo-sharing' | Native share sheet |

Installation by Project Type

Expo Projects (Recommended)

# Gradients
npx expo install expo-linear-gradient

# Haptics
npx expo install expo-haptics

# QR Codes (requires both packages)
npx expo install react-native-qrcode-svg react-native-svg

# Clipboard
npx expo install expo-clipboard

# Contacts (device contacts import)
npx expo install expo-contacts

# Google Sign-In (Google contacts import)
npm install @react-native-google-signin/google-signin

# Sharing
npx expo install expo-sharing

Then configure:

<VortexInvite
  componentId="your-component-id"
  jwt={jwt}
  modules={{
    gradient: 'expo-linear-gradient',
    haptics: 'expo-haptics',
    qrCode: 'react-native-qrcode-svg',
    clipboard: 'expo-clipboard',
    contacts: 'expo-contacts',
    googleSignin: '@react-native-google-signin/google-signin',
    sharing: 'expo-sharing',
  }}
/>

Bare React Native Projects

# Gradients
npm install react-native-linear-gradient
cd ios && pod install

# QR Codes (requires both packages)
npm install react-native-qrcode-svg react-native-svg
cd ios && pod install

# Clipboard
npm install @react-native-clipboard/clipboard
cd ios && pod install

# Contacts (device contacts import)
npx expo install expo-contacts
# or for bare RN, follow expo-contacts installation docs

# Google Sign-In (Google contacts import)
npm install @react-native-google-signin/google-signin
cd ios && pod install

Then configure:

<VortexInvite
  componentId="your-component-id"
  jwt={jwt}
  modules={{
    gradient: 'react-native-linear-gradient',
    qrCode: 'react-native-qrcode-svg',
    clipboard: '@react-native-clipboard/clipboard',
    contacts: 'expo-contacts',
    googleSignin: '@react-native-google-signin/google-signin',
  }}
/>

Helpful Warnings

In development mode, the SDK will print helpful warnings when:

  • A feature is detected in your widget configuration but no module is specified
  • A module is specified but not installed

Example warning:

[Vortex] ⚠️ Gradient backgrounds detected in widget configuration, but no gradient module specified.

To enable gradients on native platforms, add the modules prop to <VortexInvite>:

  For Expo projects:
    <VortexInvite modules={{ gradient: 'expo-linear-gradient' }} ... />
    Then run: npx expo install expo-linear-gradient

Deferred Deep Linking

Deferred deep linking allows your app to retrieve invitation context even when a user installs the app after clicking an invitation link. When a user clicks an invitation link but doesn't have the app installed, they're redirected to the App Store or Play Store. After installation, the SDK can match the device fingerprint to retrieve the original invitation context.

Basic Usage

Call VortexDeferredLinks.retrieveDeferredDeepLink when the user signs in or when the app session is restored:

import { VortexDeferredLinks } from '@teamvortexsoftware/vortex-react-native';

// In your authentication manager or app initialization
async function onUserSignedIn(vortexJwt: string) {
  try {
    const result = await VortexDeferredLinks.retrieveDeferredDeepLink({ jwt: vortexJwt });

    if (result.matched && result.deepLink) {
      console.log('Found pending invitation!');
      console.log('Invitation ID:', result.invitationId);
      console.log('Deep Link:', result.deepLink);
      console.log('Metadata:', result.metadata);
      // Handle the invitation (e.g., show UI, auto-join group, navigate to specific screen)
    }
  } catch (error) {
    console.error('Deferred link check failed:', error);
  }
}

Response Types

MatchFingerprintResponse:

interface MatchFingerprintResponse {
  matched: boolean;           // Whether a matching invitation was found
  invitationId?: string;      // The original invitation ID
  deepLink?: string;          // Deep link URL to handle
  metadata?: Record<string, any>;  // Additional metadata
}

Best Practices

  1. Call on authentication: Check for deferred deep links immediately after user sign-in or session restore
  2. Use Vortex JWT: The endpoint requires a Vortex JWT token (not your app's auth token)
  3. Handle once: Clear or mark the invitation as handled after processing to avoid showing it repeatedly
  4. Graceful degradation: The check may fail (network issues, no match found) - handle errors gracefully

Example Integration with Navigation

import { useEffect } from 'react';
import { VortexDeferredLinks } from '@teamvortexsoftware/vortex-react-native';

function App() {
  const [jwt, setJwt] = useState<string | null>(null);
  const navigation = useNavigation();

  useEffect(() => {
    if (jwt) {
      checkForPendingInvitations();
    }
  }, [jwt]);

  async function checkForPendingInvitations() {
    try {
      const result = await VortexDeferredLinks.retrieveDeferredDeepLink({ jwt });

      if (result.matched && result.deepLink) {
        // Parse deep link and navigate
        const params = parseDeepLink(result.deepLink);
        navigation.navigate('TeamScreen', { teamId: params.teamId });
      }
    } catch (error) {
      // Log error but don't block the user
      console.error('Deferred deep link check failed:', error);
    }
  }

  return <YourApp />;
}

Unfurl Configuration

When invitation links are shared on social platforms (iMessage, WhatsApp, Facebook, Twitter, etc.), the UnfurlConfig allows you to customize the Open Graph metadata that appears in the link preview card.

Usage

<VortexInvite
  componentId="your-component-id"
  jwt={jwt}
  scope={teamId}
  scopeType="team"
  unfurlConfig={{
    title: "Join our team!",
    description: "You've been invited to collaborate on an exciting project",
    image: "https://example.com/preview-image.png",
    siteName: "My App",
    type: "website"
  }}
  onInvite={(result) => {
    console.log('Invitations sent:', result);
  }}
/>

UnfurlConfig Properties

| Property | Type | Description | |----------|------|-------------| | title | string? | The title shown in the link preview (og:title) | | description | string? | The description shown in the link preview (og:description) | | image | string? | URL to the image shown in the link preview (og:image). Must be a valid URL. | | siteName | string? | The site name shown in the link preview (og:site_name) | | type | string? | The Open Graph type (og:type). Defaults to "website" if not provided. |

Priority Chain

The backend uses this priority for unfurl values:

  1. Invitation metadata (values from UnfurlConfig) - highest priority
  2. Domain profile unfurlData - fallback from account settings in Vortex dashboard
  3. Hardcoded defaults - last resort

Features

Invitation Methods:

  • Email invitations with validation
  • Copy shareable link to clipboard
  • Native share sheet integration
  • SMS sharing
  • QR code generation
  • WhatsApp, LINE, and other social messaging integrations

Contact Import:

  • Device Contacts integration (iOS/Android)
  • Google Contacts integration (via Google Sign-In)

Advanced Components:

  • Invite Contacts - Display a list of contacts for SMS invitation
  • Find Friends - Display contacts with "Connect" buttons for internal invitations
  • Invitation Suggestions - Show suggested contacts with invite/dismiss actions
  • Search Box - Search and invite users from your platform
  • Incoming Invitations - Display and manage received invitations
  • Outgoing Invitations - Display and manage sent invitations

Core Capabilities:

  • Dynamic form rendering from server configuration
  • JWT authentication
  • Group/team/workspace context support
  • Real-time loading states and error handling
  • Customizable UI based on widget configuration
  • Deferred deep linking via fingerprint matching
  • Prefetching for instant rendering
  • Open Graph unfurl metadata customization

Optional Native Features:

  • Linear gradients (Expo or React Native)
  • Haptic feedback (Expo)
  • QR code generation (react-native-qrcode-svg or react-qr-code)
  • Enhanced clipboard (Expo or React Native Community)
  • Device contacts import (Expo)
  • Google Contacts import (via Google Sign-In)
  • Native share sheet (Expo)

API Reference

VortexInvite

The main React Native component for rendering invitation forms.

Props:

| Prop | Type | Required | Description | |------|------|----------|-------------| | componentId | string | ✅ | Unique identifier for the Vortex component | | jwt | string | ✅ | JWT token for authenticated user | | scope | string | ✅ | Scope identifier (e.g., team ID, workspace ID) | | scopeType | string | ✅ | Type of scope (e.g., "team", "workspace", "project") | | widgetConfiguration | WidgetConfiguration | ❌ | Pre-fetched widget configuration | | onInvite | (result: any) => void | ❌ | Callback when invitations are successfully sent | | onError | (error: Error) => void | ❌ | Callback when an error occurs | | onClose | () => void | ❌ | Callback when the component is closed | | templateVariables | Record<string, string> | ❌ | Variables for invitation templates | | analyticsSegmentation | Record<string, any> | ❌ | Analytics tracking data | | modules | VortexModules | ❌ | Unified configuration for optional native libraries (see below) | | inviteContactsConfig | InviteContactsConfig | ❌ | Configuration for Invite Contacts feature | | findFriendsConfig | FindFriendsConfig | ❌ | Configuration for Find Friends feature | | invitationSuggestionsConfig | InvitationSuggestionsConfig | ❌ | Configuration for Invitation Suggestions feature | | searchBoxConfig | SearchBoxConfig | ❌ | Configuration for Search Box feature | | incomingInvitationsConfig | IncomingInvitationsConfig | ❌ | Configuration for Incoming Invitations feature | | outgoingInvitationsConfig | OutgoingInvitationsConfig | ❌ | Configuration for Outgoing Invitations feature | | unfurlConfig | UnfurlConfig | ❌ | Configuration for Open Graph unfurl metadata |

modules Object Properties:

| Property | Type | Description | |----------|------|-------------| | gradient | 'expo-linear-gradient' | 'react-native-linear-gradient' | Gradient library for button backgrounds | | haptics | 'expo-haptics' | Haptics library for touch feedback | | qrCode | 'react-native-qrcode-svg' | 'react-qr-code' | QR code library | | clipboard | 'expo-clipboard' | '@react-native-clipboard/clipboard' | Clipboard library | | contacts | 'expo-contacts' | Device contacts import | | googleSignin | '@react-native-google-signin/google-signin' | Google Contacts import | | sharing | 'expo-sharing' | Sharing library |

usePrefetchWidgetConfiguration

Hook for prefetching widget configuration.

Options:

| Option | Type | Required | Description | |--------|------|----------|-------------| | componentId | string | ✅ | Your widget component ID | | vortexApiUrl | string | ✅ | Vortex API URL | | jwt | string | ❌ | JWT token (or use user) | | user | string \| object | ❌ | User data for unsigned JWT | | enabled | boolean | ❌ | Conditionally enable/disable prefetching (default: true) |

Return Value:

| Property | Type | Description | |----------|------|-------------| | widgetConfiguration | WidgetConfiguration \| undefined | The prefetched configuration | | isLoading | boolean | True while fetching | | error | Error \| null | Error if fetch failed |

VortexDeferredLinks

Class for handling deferred deep linking - track invitation attribution even when the app wasn't installed at the time the invitation link was clicked.

Methods:

| Method | Parameters | Returns | Description | |--------|------------|---------|-------------| | collectDeviceFingerprint() | none | DeviceFingerprint | Collects device fingerprint data for matching | | retrieveDeferredDeepLink(options) | RetrieveDeferredDeepLinkOptions | Promise<MatchFingerprintResponse> | Retrieves deferred deep link by matching fingerprint |

Example:

import { VortexDeferredLinks } from '@teamvortexsoftware/vortex-react-native';

// On app first launch after install
async function checkDeferredDeepLink(jwt: string) {
  const result = await VortexDeferredLinks.retrieveDeferredDeepLink({ jwt });
  if (result.matched && result.deepLink) {
    console.log('User came from invitation:', result.invitationId);
    // Navigate to appropriate screen based on deepLink
  }
}

Types:

interface DeviceFingerprint {
  platform: 'ios' | 'android';
  osVersion: string;
  deviceModel: string;
  deviceBrand: string;
  timezone: string;
  language: string;
  screenWidth: number;
  screenHeight: number;
  carrierName?: string;
  totalMemory?: number;
}

interface MatchFingerprintResponse {
  matched: boolean;
  invitationId?: string;
  deepLink?: string;
  metadata?: Record<string, any>;
}

interface RetrieveDeferredDeepLinkOptions {
  jwt: string;
}

VortexClient

Standalone API client for invitation management. Use this when you need to interact with invitations outside of the VortexInvite component (e.g., in your own screens or background logic).

Constructor:

import { VortexClient } from '@teamvortexsoftware/vortex-react-native';

const client = new VortexClient({
  jwt: 'your-jwt-token',
});

Methods:

| Method | Parameters | Returns | Description | |--------|------------|---------|-------------| | setJwt(jwt) | jwt: string | void | Updates the JWT token used for authentication | | getInvitation(invitationId) | invitationId: string | Promise<Invitation> | Retrieves details of a specific invitation by its ID | | acceptInvitation(invitationId) | invitationId: string | Promise<void> | Accepts an incoming invitation | | revokeInvitation(invitationId) | invitationId: string | Promise<void> | Revokes (deactivates) an invitation where the user is either the creator or a target |

Example — Get, Accept, and Revoke invitations:

import { VortexClient, Invitation } from '@teamvortexsoftware/vortex-react-native';

const client = new VortexClient({ jwt: userToken });

try {
  // Get invitation details
  const invitation: Invitation = await client.getInvitation('invitation-id');
  console.log(invitation.status, invitation.target);

  // Accept an incoming invitation
  await client.acceptInvitation('invitation-id');

  // Revoke (deactivate) an invitation
  await client.revokeInvitation('invitation-id');
} catch (error) {
  console.error('Operation failed:', error.message);
}

Types:

interface Invitation {
  id: string;
  accountId: string;
  projectId: string;
  deploymentId: string;
  widgetConfigurationId: string | null;
  status: string;
  invitationType: string;
  deliveryTypes: string[];
  source?: string | null;
  subtype?: string | null;
  foreignCreatorId: string | null;
  creatorName?: string | null;
  creatorAvatarUrl?: string | null;
  createdAt: string;
  modifiedAt: string | null;
  deactivated: boolean;
  deliveryCount: number;
  views: number | null;
  clickThroughs: number | null;
  configurationAttributes: Record<string, any>;
  attributes: Record<string, any> | null;
  metadata: Record<string, any> | null;
  passThrough?: string | null;
  target: InvitationTarget[];
  groups?: InvitationGroup[];
  accepts?: InvitationAcceptance[];
  scope?: string;
  scopeType?: string;
  expired: boolean;
  expires?: string;
}

interface InvitationTarget {
  type: string;
  value: string;
  name?: string | null;
  avatarUrl?: string | null;
}

interface InvitationGroup {
  id: string;
  groupId: string;
  type: string;
  name?: string | null;
}

interface InvitationAcceptance {
  id: string;
  accountId: string;
  projectId: string;
  acceptedAt: string;
  targetType: string;
  targetValue: string;
  identifiers?: Record<string, any> | null;
}

Type Definitions

InviteContactsConfig:

interface InviteContactsConfig {
  contacts: InviteContactsContact[];
}

interface InviteContactsContact {
  id?: string;
  name: string;
  phoneNumber: string;
  avatarUrl?: string;
  metadata?: Record<string, any>;
}

FindFriendsConfig:

interface FindFriendsConfig {
  contacts: FindFriendsContact[];
  onInvitationCreated?: (contact: FindFriendsContact) => void;
}

interface FindFriendsContact {
  internalId: string;
  name: string;
  subtitle?: string;
  avatarUrl?: string;
  metadata?: Record<string, any>;
}

InvitationSuggestionsConfig:

interface InvitationSuggestionsConfig {
  contacts: InvitationSuggestionContact[];
  onDismiss: (contact: InvitationSuggestionContact) => void;
  onInvitationCreated?: (contact: InvitationSuggestionContact) => void;
  onInvitationFailed?: (contact: InvitationSuggestionContact, error: Error) => void;
}

interface InvitationSuggestionContact {
  internalId: string;
  name: string;
  subtitle?: string;
  avatarUrl?: string;
  metadata?: Record<string, any>;
}

SearchBoxConfig:

interface SearchBoxConfig {
  onSearch: (query: string) => Promise<SearchBoxContact[]>;
  onInvitationCreated?: (contact: SearchBoxContact) => void;
}

interface SearchBoxContact {
  internalId: string;
  name: string;
  subtitle?: string;
  avatarUrl?: string;
  metadata?: Record<string, any>;
}

IncomingInvitationsConfig:

interface IncomingInvitationsConfig {
  internalInvitations?: IncomingInvitationItem[];
  onAccept?: (invitation: IncomingInvitationItem) => Promise<boolean>;
  onDelete?: (invitation: IncomingInvitationItem) => Promise<boolean>;
}

interface IncomingInvitationItem {
  id: string;
  name: string;
  subtitle?: string;
  avatarUrl?: string;
  isVortexInvitation: boolean;
  metadata?: Record<string, any>;
}

OutgoingInvitationsConfig:

interface OutgoingInvitationsConfig {
  internalInvitations?: OutgoingInvitationItem[];
  onCancel?: (invitation: OutgoingInvitationItem) => Promise<boolean>;
}

interface OutgoingInvitationItem {
  id: string;
  name: string;
  subtitle?: string;
  avatarUrl?: string;
  isVortexInvitation: boolean;
  metadata?: Record<string, any>;
}

UnfurlConfig:

interface UnfurlConfig {
  title?: string;
  description?: string;
  image?: string;
  siteName?: string;
  type?: string;
}

Authentication

Your backend should generate JWTs for authenticated users:

Node.js Example

import { generateJwt } from '@teamvortexsoftware/vortex-node-22-sdk';

const jwt = generateJwt({
  user: {
    id: user.id,
    email: user.email,
    adminScopes: user.isAdmin ? ['autojoin'] : [],
  },
  apiKey: process.env.VORTEX_API_KEY,
});

Express/Fastify Example

app.post('/api/vortex/jwt', async (req, res) => {
  const user = await getCurrentUser(req);

  const jwt = generateJwt({
    user: {
      id: user.id,
      email: user.email,
      adminScopes: user.isAutojoinAdmin ? ['autojoin'] : [],
    },
    apiKey: process.env.VORTEX_API_KEY,
  });

  res.json({ jwt });
});

Configuration

The SDK automatically fetches and caches widget configuration from your Vortex backend. The configuration determines:

  • Available invitation methods
  • Form fields and validation
  • UI theme and styling
  • Enabled features

Environment Variables

Set up your environment variables in .env:

EXPO_PUBLIC_COMPONENT_ID=your-component-id-here

Error Handling

The SDK provides error callbacks for handling failures:

<VortexInvite
  componentId="my-component-id"
  jwt={jwt}
  scope={teamId}
  scopeType="team"
  onError={(error) => {
    // Handle specific error types
    if (error.message.includes('network')) {
      Alert.alert('Network Error', 'Please check your connection and try again.');
    } else if (error.message.includes('unauthorized')) {
      Alert.alert('Authentication Error', 'Please log in again.');
    } else {
      Alert.alert('Error', error.message);
    }
  }}
/>

Common error scenarios:

  • Network errors - Connection issues or API unavailable
  • Authentication errors - Invalid or expired JWT
  • Configuration errors - Invalid component ID or missing configuration
  • Validation errors - Invalid form input (e.g., malformed email)

Development

# Install dependencies
pnpm install

# Build the package
pnpm run build

# Run type checking
pnpm run type-check

# Run linting
pnpm run lint

# Watch mode for development
pnpm run dev

License

Apache License 2.0 - see LICENSE file for details.

Support

For questions or issues:

  • 📚 Documentation: https://docs.vortexsoftware.com
  • 📧 Email: [email protected]
  • 🐛 GitHub Issues: https://github.com/teamvortexsoftware/vortex/issues