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

@oxyhq/services

v22.3.0

Published

OxyHQ Expo/React Native SDK — UI components, screens, and native features

Downloads

17,529

Readme

@oxyhq/services

A comprehensive TypeScript UI library for the Oxy API providing authentication, user management, and UI components for React Native, Expo, and web (React Native Web) applications.

For web apps (Vite + React Native Web): Use this same package — OxyProvider is universal. See the Platform Guide.

For backend / Node.js: Use @oxyhq/core only (@oxyhq/core/server for auth middleware).

For the full platform guide: See PLATFORM_GUIDE.md.

Table of Contents

Features

  • Zero-Config Authentication: Automatic token management and refresh
  • Device-First Sessions: Sign in once per device; every Oxy app on the device restores silently and syncs account changes live over Socket.IO
  • Universal Provider: Single OxyProvider works on iOS, Android, Expo Web, and React Native Web
  • UI Components: Pre-built components for auth, profiles, and more
  • Inter Font Included: Default Oxy ecosystem font with automatic loading
  • Cross-Platform: Works in Expo and React Native (iOS, Android, Web)
  • Multi-Session Support: Manage multiple user sessions simultaneously
  • TypeScript First: Full type safety and IntelliSense support
  • Performance Optimized: Automatic caching with TanStack Query
  • Production Ready: Error handling, retry logic, and security best practices

Architecture

The OxyHQ SDK is split into three packages:

| Package | Use Case | Dependencies | |---------|----------|--------------| | @oxyhq/services | Expo / React Native / Web (React Native Web) | Full (RN, Expo) | | @oxyhq/core | All platforms (types, API client, crypto, server middleware) | None | | @oxyhq/contracts | Shared API schemas (Zod) | zod only |

This package (@oxyhq/services) is the single UI SDK for every React surface. It provides OxyProvider, the unified account dialog, UI components, screens, bottom sheet routing, fonts, and hooks.

See PLATFORM_GUIDE.md for the complete architecture guide.

Installation

bun add @oxyhq/services @oxyhq/core

Peer Dependencies

To avoid duplicate native modules and ensure smooth integration across apps, install (or ensure your app already includes) the following peer dependencies:

  • react: >=18, react-native: >=0.76
  • react-native-reanimated: >=3.16, react-native-gesture-handler: >=2.16
  • react-native-safe-area-context: ^5.4.0, react-native-svg: >=13
  • Expo projects: expo, expo-font, expo-image, expo-linear-gradient
  • Navigation (if you use the provided screens): @react-navigation/native

Example for Expo:

npm i react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-svg \
  expo expo-font expo-image expo-linear-gradient @react-navigation/native

React Native/Expo Setup

For React Native and Expo projects, add the polyfill import at the very top of your entry file:

// index.js or App.js (very first line)
import 'react-native-url-polyfill/auto';

Note: This polyfill is already included in the package dependencies, but you need to import it to activate it.

Quick Start

Expo Apps (Native + Web)

import { OxyProvider, useAuth } from '@oxyhq/services';

function App() {
  return (
    <OxyProvider
      baseURL="https://api.oxy.so"
      clientId={process.env.EXPO_PUBLIC_OXY_CLIENT_ID}
    >
      <YourApp />
    </OxyProvider>
  );
}

function UserProfile() {
  const { user, isAuthenticated, signIn, signOut } = useAuth();

  if (!isAuthenticated) {
    return <Button onPress={() => signIn()} title="Sign In" />;
  }

  return (
    <View>
      <Text>Welcome, {user?.username}!</Text>
      <Button onPress={signOut} title="Sign Out" />
    </View>
  );
}

OxyProvider handles iOS, Android, and Expo web. Always use OxyProvider in Expo apps.

Typography - Inter Font

Inter is the default font for all Oxy ecosystem apps. This package includes the Inter font family and provides automatic font loading for both web and native platforms.

Automatic Loading

If you are using OxyProvider, fonts are loaded automatically. No additional setup needed:

import { OxyProvider } from '@oxyhq/services';

function App() {
  return (
    <OxyProvider baseURL="https://api.oxy.so">
      <YourAppContent />
    </OxyProvider>
  );
}

Using Font Constants

import { fontFamilies, fontStyles } from '@oxyhq/services';

const styles = StyleSheet.create({
  title: {
    ...fontStyles.titleLarge,  // Pre-defined style (54px, Bold)
    color: '#000000',
  },
  customText: {
    fontFamily: fontFamilies.interSemiBold,  // Cross-platform font family
    fontSize: 18,
  },
});

Available Exports

  • setupFonts() - Function to manually load fonts (called automatically by OxyProvider)
  • fontFamilies - Object with all Inter weight variants (inter, interLight, interMedium, interSemiBold, interBold, interExtraBold, interBlack)
  • fontStyles - Pre-defined text styles (titleLarge, titleMedium, titleSmall, buttonText)

See FONTS.md for the complete typography guide, including detailed usage examples, all available font weights, platform-specific handling, best practices, and migration guide.

Usage Patterns

1. OxyProvider + useAuth Hook (Recommended)

OxyProvider works on all platforms (iOS, Android, Web). Use useAuth for authentication.

import { OxyProvider, useAuth, OxySignInButton } from '@oxyhq/services';

// App.tsx - Setup the provider
function App() {
  return (
    <OxyProvider
      baseURL="https://api.oxy.so"
      onAuthStateChange={(user) => {
        console.log('Auth state changed:', user ? 'logged in' : 'logged out');
      }}
    >
      <YourApp />
    </OxyProvider>
  );
}

// Component.tsx - Use the hook
function UserProfile() {
  const { user, isAuthenticated, signIn, signOut, isLoading } = useAuth();

  if (isLoading) return <ActivityIndicator />;

  if (!isAuthenticated) {
    return (
      <View>
        <Text>Welcome! Please sign in.</Text>
        <OxySignInButton />
        {/* Or use signIn() directly: */}
        <Button onPress={() => signIn()} title="Sign In" />
      </View>
    );
  }

  return (
    <View>
      <Text style={styles.title}>Welcome, {user?.username}!</Text>
      <Button onPress={signOut} title="Sign Out" />
    </View>
  );
}

2. Direct Import (Non-React Files)

For utility functions, services, or non-React Native files:

import { oxyClient } from '@oxyhq/core';

// utils/api.ts
export const userUtils = {
  async fetchUserById(userId: string) {
    return await oxyClient.getUserById(userId);
  },

  async fetchProfileByUsername(username: string) {
    return await oxyClient.getProfileByUsername(username);
  },

  async updateUserProfile(updates: any) {
    return await oxyClient.updateProfile(updates);
  }
};

3. Mixed Usage (Hooks + Direct Client)

You can use both hooks and the direct client in the same Expo app:

// App.tsx - React Native setup
import { OxyProvider } from '@oxyhq/services';

function App() {
  return (
    <OxyProvider baseURL="https://api.oxy.so">
      <YourApp />
    </OxyProvider>
  );
}

// utils/api.ts - Direct import
import { oxyClient } from '@oxyhq/core';

export const apiUtils = {
  async fetchData() {
    return await oxyClient.getCurrentUser();
  }
};

// Component.tsx - React Native hook
import { useOxy } from '@oxyhq/services';

function Component() {
  const { oxyServices } = useOxy();
  // Both oxyServices and oxyClient share the same tokens
}

API Reference

Core Exports (from @oxyhq/core)

import {
  OxyServices,           // Main service class
  oxyClient,            // Pre-configured instance
  OXY_CLOUD_URL,        // Default API URL
  OxyAuthenticationError,
  OxyAuthenticationTimeoutError,
  KeyManager,
  SignatureService,
  RecoveryPhraseService
} from '@oxyhq/core';

React Native Exports (from @oxyhq/services)

import {
  OxyProvider,          // Context provider
  useOxy,              // React Native hook
  useAuth,             // Auth hook
  OxySignInButton,     // UI components
  Avatar,
  FollowButton
} from '@oxyhq/services';

OxyServices Methods

// Authentication (Public Key Based)
await oxyClient.register(publicKey, username, signature, timestamp, email?);
await oxyClient.requestChallenge(publicKey);
await oxyClient.verifyChallenge(publicKey, challenge, signature, timestamp, deviceName?, deviceFingerprint?);
await oxyClient.checkPublicKeyRegistered(publicKey);
await oxyClient.getUserByPublicKey(publicKey);

// User Management
const user = await oxyClient.getCurrentUser();                    // Get current user
const userById = await oxyClient.getUserById('user123');          // Get user by ID
const profileByUsername = await oxyClient.getProfileByUsername('john_doe'); // Get profile by username
await oxyClient.updateProfile({ name: 'John Doe' });             // Update current user
await oxyClient.updateUser('user123', { name: 'John' });         // Update user by ID (admin)

// Session Management
const userBySession = await oxyClient.getUserBySession('session123'); // Get user by session
const sessions = await oxyClient.getSessionsBySessionId('session123'); // Get all sessions
await oxyClient.logoutSession('session123');                     // Logout specific session
await oxyClient.logoutAllSessions('session123');                 // Logout all sessions

// Social Features
await oxyClient.followUser('user123');                           // Follow user
await oxyClient.unfollowUser('user123');                         // Unfollow user
const followStatus = await oxyClient.getFollowStatus('user123'); // Check follow status
const followers = await oxyClient.getUserFollowers('user123');   // Get user followers
const following = await oxyClient.getUserFollowing('user123');   // Get user following

// Notifications
const notifications = await oxyClient.getNotifications();        // Get notifications
const unreadCount = await oxyClient.getUnreadCount();            // Get unread count
await oxyClient.markNotificationAsRead('notification123');       // Mark as read
await oxyClient.markAllNotificationsAsRead();                    // Mark all as read
await oxyClient.deleteNotification('notification123');           // Delete notification

// File Management
const fileData = await oxyClient.uploadFile(file);                     // Upload file
const file = await oxyClient.getFile('file123');                       // Get file info
await oxyClient.deleteFile('file123');                                 // Delete file
const downloadUrl = oxyClient.getFileDownloadUrl('file123', 'thumb'); // Get download/stream URL
const userFiles = await oxyClient.listUserFiles('user123');            // List user files

// Payments
const payment = await oxyClient.createPayment(paymentData);      // Create payment
const paymentInfo = await oxyClient.getPayment('payment123');    // Get payment info
const userPayments = await oxyClient.getUserPayments();          // Get user payments

// Trust
showBottomSheet('TrustCenter');                                  // Trust center
showBottomSheet('TrustLeaderboard');                             // Trust leaderboard

// Location Services
await oxyClient.updateLocation(40.7128, -74.0060);              // Update location
const nearby = await oxyClient.getNearbyUsers(1000);             // Get nearby users

// Analytics
await oxyClient.trackEvent('user_action', { action: 'click' });  // Track event
const analytics = await oxyClient.getAnalytics('2024-01-01', '2024-01-31'); // Get analytics

// Device Management
await oxyClient.registerDevice(deviceData);                      // Register device
const devices = await oxyClient.getUserDevices();                // Get user devices
await oxyClient.removeDevice('device123');                       // Remove device
const deviceSessions = await oxyClient.getDeviceSessions('session123'); // Get device sessions
await oxyClient.logoutAllDeviceSessions('session123');           // Logout device sessions
await oxyClient.updateDeviceName('session123', 'iPhone 15');     // Update device name

// Utilities
const metadata = await oxyClient.fetchLinkMetadata('https://example.com'); // Fetch link metadata

useOxy Hook

const {
  // Service instance
  oxyServices,

  // Authentication state
  user,
  isAuthenticated,
  isLoading,
  canUsePrivateApi,   // Gate private API calls on this
  isPrivateApiPending,
  error,

  // Identity (public-key auth; identities are created in Commons by Oxy)
  signIn,             // Sign in with stored identity
  hasIdentity,        // Check if identity exists on device
  getPublicKey,       // Get stored public key

  // Account dialog (switcher + sign-in)
  openAccountDialog,
  closeAccountDialog,

  // Session management
  logout,
  sessions,
  activeSessionId,
  switchToAccount,    // Switch active account (device session + account graph)
  removeSession
} = useOxy();

Configuration

OxyProvider Props

<OxyProvider
  baseURL="https://api.oxy.so"            // API base URL
  clientId="oxy_dk_..."                   // Registered Application credential (Oxy Console)
  storageKeyPrefix="oxy_session"          // Storage key prefix
  onAuthStateChange={(user) => {}}        // Auth state callback
  onError={(error) => {}}                 // Error callback
>
  {children}
</OxyProvider>

Environment Variables

# .env
EXPO_PUBLIC_API_URL=https://api.oxy.so
EXPO_PUBLIC_OXY_CLIENT_ID=oxy_dk_...

Custom Configuration

import { OxyServices } from '@oxyhq/core';

const oxy = new OxyServices({
  baseURL: process.env.OXY_API_URL || 'https://api.oxy.so'
});

Authentication

Oxy supports public/private key cryptography (ECDSA secp256k1) as the primary identity system, with optional password-based accounts. Users create and manage their cryptographic identity in the Commons by Oxy app (the native identity vault); every app integrates the same "Sign in with Oxy" surface.

How it works (device-first)

  • Cold boot is silent. On mount, OxyProvider restores the ambient device session — the server-side DeviceSession records which accounts are signed in on this device and which one is active. No redirects, no browser identity APIs, no UI. See device sessions.
  • Interactive sign-in is a dialog. useAuth().signIn() or useOxy().openAccountDialog('signin') opens the unified account dialog (Bloom Dialog — bottom sheet on phones, centered on desktop): account switcher, Sign in with Oxy via the Oxy app (QR on web, deep link / shared keychain on native), and a collapsed password form.
  • Cross-app sync. Adding, switching, or signing out an account bumps the device-session revision and is pushed over the session_state socket event to every Oxy app on the device.
import { useAuth } from '@oxyhq/services';

function SignInCTA() {
  const { isAuthenticated, signIn } = useAuth();
  if (isAuthenticated) return null;
  return <Button title="Sign in" onPress={() => signIn()} />; // opens the dialog
}

Password Authentication

Oxy also supports password sign-in (email/username + password) — shown collapsed inside the account dialog, or callable directly:

import { oxyClient } from '@oxyhq/core';

const session = await oxyClient.signUp('username', '[email protected]', 'password');
const session2 = await oxyClient.signIn('username-or-email', 'password');

Cross-App Authentication (Sign in with Oxy)

OxySignInButton resolves your registered Application (GET /auth/oauth/client/:clientId) and picks the right flow:

import { OxySignInButton } from '@oxyhq/services';

function LoginScreen() {
  return <OxySignInButton variant="contained" />;
}
  • Official Oxy apps (isOfficial / first-party types): opens the in-app account dialog.
  • Third-party apps (type: 'third_party'): starts the standard OAuth 2.0 Authorization Code + PKCE redirect to auth.oxy.so (the SDK generates state + PKCE via @oxyhq/core). Pass oauthRedirectUri; on native handle onOAuthResult to complete the token exchange.

See the integration guide for Console registration, OAuth endpoints, and backend verification, and AUTHENTICATION.md for the full model.

Documentation

See Complete Public Key Authentication Guide for architecture, concepts, user flows, developer integration, crypto module API reference, security best practices, and migration from password auth.

UI Components

Built-in Components

import {
  OxySignInButton,
  Avatar,
  FollowButton,
  OxyLogo
} from '@oxyhq/services';

function MyComponent() {
  return (
    <View style={styles.container}>
      <OxyLogo />
      <Avatar userId="user123" size={40} />
      <FollowButton userId="user123" />
      <OxySignInButton />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16,
  },
});

Account Dialog (auth + switcher)

Sign-in and account switching do not use the bottom sheet — they live in the unified account dialog:

import { useOxy } from '@oxyhq/services';

function MyComponent() {
  const { openAccountDialog } = useOxy();

  return (
    <Button
      onPress={() => openAccountDialog('signin')}
      title="Sign in with Oxy"
    />
  );
}

Views: 'accounts' (switcher, default), 'signin', 'qr', 'add'. The exported ProfileButton component opens it for you.

Bottom Sheet Routing System

The bottom sheet routing system provides a clean, professional way to display account management and other non-auth UI flows within a modal bottom sheet.

Quick Example:

import { useOxy } from '@oxyhq/services';

function MyComponent() {
  const { showBottomSheet } = useOxy();

  return (
    <Button
      onPress={() => showBottomSheet('ManageAccount')}
      title="Manage account"
    />
  );
}

Features:

  • Full navigation history with back button support
  • Step-based screen navigation (multi-step flows)
  • Keyboard-aware (automatically adjusts for keyboard)
  • Dynamic sizing (fits content automatically)
  • Type-safe route names
  • 25+ pre-built screens available

Available Screens:

  • ManageAccount, AccountSettings, AccountMembers, CreateAccount
  • Profile, EditProfile, PaymentGateway, TrustCenter, ConnectedApps
  • FileManagement, LanguageSelector, PrivacySettings, Preferences
  • And many more (see RouteName in src/ui/navigation/routes.ts)

Internationalization (i18n)

OxyHQ Services includes built-in language selection and storage. The selected language can be accessed and synced with your app's i18n system.

Getting Current Language

import { useOxy } from '@oxyhq/services';

function MyComponent() {
  const {
    currentLanguage,           // 'en-US'
    currentLanguageName,       // 'English'
    currentNativeLanguageName, // 'Espanol' (if Spanish is selected)
    currentLanguageMetadata    // Full metadata object
  } = useOxy();

  return <Text>Current language: {currentLanguageName}</Text>;
}

Syncing with Your i18n Library

To integrate with react-i18next, i18n-js, next-intl, or other i18n libraries, see the comprehensive guide:

See the language utilities and useOxy() hook sections above for integration with your i18n system.

Using OxyServices (Non-React)

import { OxyServices } from '@oxyhq/core';

const oxy = new OxyServices({ baseURL: 'https://api.oxy.so' });

// Get current language code
const languageCode = await oxy.getCurrentLanguage();

// Get language name
const languageName = await oxy.getCurrentLanguageName();

// Get full metadata
const metadata = await oxy.getCurrentLanguageMetadata();

Language Utilities

import {
  SUPPORTED_LANGUAGES,
  getLanguageName,
  getLanguageMetadata
} from '@oxyhq/services';

// Get all supported languages
const languages = SUPPORTED_LANGUAGES;

// Get language name from code
const name = getLanguageName('es-ES'); // 'Spanish'

// Get full metadata
const metadata = getLanguageMetadata('es-ES');
// { id: 'es-ES', name: 'Spanish', nativeName: 'Espanol', flag: '...', ... }

Troubleshooting

Common Issues

1. "useOxy must be used within an OxyContextProvider"

Solution: Wrap your app with OxyProvider

import { OxyProvider } from '@oxyhq/services';

function App() {
  return (
    <OxyProvider baseURL="https://api.oxy.so">
      <YourApp />
    </OxyProvider>
  );
}

2. FormData Issues in React Native/Expo

Solution: Add polyfill import at the very top of your entry file

// index.js or App.js (very first line)
import 'react-native-url-polyfill/auto';

Why needed: Your app uses file uploads which require FormData. React Native with Hermes engine does not include FormData natively, so it needs to be polyfilled.

3. Authentication Not Persisting

Solution: Check storage configuration

<OxyProvider
  baseURL="https://api.oxy.so"
  storageKeyPrefix="my_app_oxy"  // Custom storage key
>
  {children}
</OxyProvider>

Error Handling

import { OxyAuthenticationError } from '@oxyhq/core';

try {
  await oxyClient.getCurrentUser();
} catch (error) {
  if (error instanceof OxyAuthenticationError) {
    // Handle authentication errors
    console.log('Auth error:', error.message);
  } else {
    // Handle other errors
    console.log('Other error:', error.message);
  }
}

Payment & Storage Query Hooks

The following React Query hooks are exported from the package root:

import {
  useUserSubscription,
  useUserPayments,
  useUserWallet,
  useUserWalletTransactions,
  useAccountStorageUsage,
} from '@oxyhq/services';

Typed returns are defined in ui/hooks/queries/paymentTypes.ts (Subscription, Payment, Wallet, WalletTransaction). The payments query namespace is whitelisted for offline persistence alongside accounts, users, sessions, devices, and privacy.

Sign-In Token Planting

@oxyhq/core OxyServices.verifyChallenge() plants setTokens(accessToken, refreshToken ?? '') internally before returning. useAuthOperations.performSignIn no longer needs to hand-plant the token or call a session-token fallback — just await verifyChallenge and proceed.

Requirements

  • React Native: 0.76+ (for mobile components)
  • Expo: 56+ (recommended)
  • TypeScript: 4.0+ (optional but recommended)

Peer Dependencies

For React Native/Expo projects:

bun add jwt-decode invariant

Note: react-native-url-polyfill is already included as a dependency in this package.

Examples

Complete React Native App

// App.tsx
import { OxyProvider } from '@oxyhq/services';

function App() {
  return (
    <OxyProvider baseURL="https://api.oxy.so">
      <UserDashboard />
    </OxyProvider>
  );
}

// UserDashboard.tsx
import { useOxy } from '@oxyhq/services';
import { View, Text, StyleSheet } from 'react-native';

function UserDashboard() {
  const { user, isAuthenticated, oxyServices } = useOxy();

  const [followers, setFollowers] = useState([]);

  useEffect(() => {
    if (isAuthenticated && user) {
      oxyServices.getUserFollowers(user.id).then(setFollowers);
    }
  }, [isAuthenticated, user]);

  if (!isAuthenticated) {
    return <Text>Please sign in</Text>;
  }

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Welcome, {user?.name}!</Text>
      <Text>Followers: {followers.length}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16,
    justifyContent: 'center',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 16,
  },
});

Documentation

Comprehensive documentation is available in the /docs directory:


License

This project is licensed under the MIT License. See the LICENSE file for details.