@oxyhq/services
Version:
818 lines (619 loc) • 23.7 kB
Markdown
# @oxyhq/services
A comprehensive TypeScript UI library for the Oxy API providing authentication, user management, and UI components for React Native and Expo applications.
> **For web apps (Vite, Next.js, CRA):** Use [`@oxyhq/auth`](../auth) for authentication and [`@oxyhq/core`](../core) for types and services.
>
> **For backend / Node.js:** Use [`@oxyhq/core`](../core) only.
>
> **For the full platform guide:** See [PLATFORM_GUIDE.md](./PLATFORM_GUIDE.md).
## Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Typography - Inter Font](#typography---inter-font)
- [Usage Patterns](#usage-patterns)
- [API Reference](#api-reference)
- [Configuration](#configuration)
- [Authentication](#authentication)
- [UI Components](#ui-components)
- [Internationalization (i18n)](#internationalization-i18n)
- [Troubleshooting](#troubleshooting)
- [Requirements](#requirements)
## Features
- **Zero-Config Authentication**: Automatic token management and refresh
- **Cross-Domain SSO**: Sign in once, authenticated everywhere (FedCM, popup, redirect)
- **Universal Provider**: Single `OxyProvider` works on iOS, Android, and Expo 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 apps | Full (RN, Expo) |
| `@oxyhq/auth` | Web apps (Vite, Next.js) | React only |
| `@oxyhq/core` | All platforms (types, API client, crypto) | None |
This package (`@oxyhq/services`) is for **Expo and React Native** applications. It provides `OxyProvider`, UI components, screens, bottom sheet routing, fonts, and hooks.
See [PLATFORM_GUIDE.md](./PLATFORM_GUIDE.md) for the complete architecture guide.
## Installation
```bash
npm install @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:
```bash
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:
```javascript
// 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)
```typescript
import { OxyProvider, useAuth } from '@oxyhq/services';
function App() {
return (
<OxyProvider baseURL="https://api.oxy.so">
<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:
```typescript
import { OxyProvider } from '@oxyhq/services';
function App() {
return (
<OxyProvider baseURL="https://api.oxy.so">
<YourAppContent />
</OxyProvider>
);
}
```
### Using Font Constants
```typescript
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](./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.
```typescript
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:
```typescript
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:
```typescript
// App.tsx - React Native setup
import { OxyProvider } from '@oxyhq/services';
function App() {
return (
<OxyProvider baseURL="https://cloud.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)
```typescript
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)
```typescript
import {
OxyProvider, // Context provider
useOxy, // React Native hook
useAuth, // Auth hook
OxySignInButton, // UI components
Avatar,
FollowButton
} from '@oxyhq/services';
```
### OxyServices Methods
```typescript
// 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
// Karma System
const karma = await oxyClient.getUserKarma('user123'); // Get user karma
await oxyClient.giveKarma('user123', 10, 'helpful comment'); // Give karma
const karmaTotal = await oxyClient.getUserKarmaTotal('user123'); // Get karma total
const karmaHistory = await oxyClient.getUserKarmaHistory('user123'); // Get karma history
const leaderboard = await oxyClient.getKarmaLeaderboard(); // Get leaderboard
const rules = await oxyClient.getKarmaRules(); // Get karma rules
// 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
```typescript
const {
// Service instance
oxyServices,
// Authentication state
user,
isAuthenticated,
isLoading,
error,
// Identity management (Public Key Authentication)
createIdentity, // Create new identity with recovery phrase
importIdentity, // Import identity from recovery phrase
signIn, // Sign in with stored identity
hasIdentity, // Check if identity exists on device
getPublicKey, // Get stored public key
// Session management
logout,
// Session management
sessions,
activeSessionId,
switchSession,
removeSession
} = useOxy();
```
## Configuration
### OxyProvider Props
```typescript
<OxyProvider
baseURL="https://api.oxy.so" // API base URL
storageKeyPrefix="oxy_session" // Storage key prefix
onAuthStateChange={(user) => {}} // Auth state callback
onError={(error) => {}} // Error callback
>
{children}
</OxyProvider>
```
### Environment Variables
```bash
# .env
EXPO_PUBLIC_API_URL=https://api.oxy.so
```
### Custom Configuration
```typescript
import { OxyServices } from '@oxyhq/core';
const oxy = new OxyServices({
baseURL: process.env.OXY_API_URL || 'https://cloud.oxy.so'
});
```
## Authentication
Oxy supports **public/private key cryptography** (ECDSA secp256k1) as the primary identity system, with optional password-based accounts for the web gateway. Users manage their cryptographic identity in the **Oxy Accounts** app, and other apps can integrate "Sign in with Oxy" for seamless authentication.
### Public Key Authentication
```typescript
import { useOxy } from '@oxyhq/services';
function AuthScreen() {
const { createIdentity, importIdentity, signIn, hasIdentity } = useOxy();
// Create new identity (in Oxy Accounts app)
const handleCreate = async () => {
const { user, recoveryPhrase } = await createIdentity('username', 'email');
// Show recoveryPhrase to user - they must save it!
};
// Import existing identity
const handleImport = async (phrase: string) => {
const user = await importIdentity(phrase, 'username', 'email');
};
// Sign in with stored identity
const handleSignIn = async () => {
const user = await signIn();
};
// Check if identity exists
const hasStoredIdentity = await hasIdentity();
}
```
### Password Authentication (Web Gateway)
For web-only or legacy flows, Oxy also supports password sign-in (email/username + password). Use the auth gateway (`/login`, `/signup`, `/recover`) for browser-based flows, or call the API directly:
```typescript
import { oxyClient } from '@oxyhq/core';
const session = await oxyClient.signUp('username', 'email@example.com', 'password');
const session2 = await oxyClient.signIn('username-or-email', 'password');
```
### Cross-App Authentication (Sign in with Oxy)
For third-party apps that want to allow users to sign in with their Oxy identity:
```typescript
import { OxySignInButton } from '@oxyhq/services';
function LoginScreen() {
return <OxySignInButton variant="contained" />;
}
```
This displays:
- A QR code that users can scan with Oxy Accounts
- A button to open Oxy Accounts directly via deep link
Web fallback: send users to the auth gateway at `https://accounts.oxy.so/authorize?token=...` to approve the session.
### Documentation
See [Complete Public Key Authentication Guide](./docs/PUBLIC_KEY_AUTHENTICATION.md) for architecture, concepts, user flows, developer integration, crypto module API reference, security best practices, and migration from password auth.
## UI Components
### Built-in Components
```typescript
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,
},
});
```
### Bottom Sheet Routing System
The bottom sheet routing system provides a clean, professional way to display authentication screens, account management, and other UI flows within a modal bottom sheet.
**Quick Example:**
```typescript
import { useOxy } from '@oxyhq/services';
function MyComponent() {
const { showBottomSheet } = useOxy();
return (
<Button
onPress={() => showBottomSheet('OxyAuth')}
title="Sign in with Oxy"
/>
);
}
```
**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:**
- `OxyAuth` (Sign in with Oxy - for third-party apps)
- `AccountOverview`, `AccountSettings`, `AccountCenter`
- `Profile`, `SessionManagement`, `PaymentGateway`
- And many more...
**Documentation:**
For complete documentation, see [Bottom Sheet Routing Guide](./docs/BOTTOM_SHEET_ROUTING.md).
### Using OxyRouter Standalone
If you need to embed the router in your own modal or container instead of using `showBottomSheet()`:
```typescript
import { Modal } from 'react-native';
import { useOxy, OxyRouter } from '@oxyhq/services';
function AuthModal({ visible, onRequestClose }: { visible: boolean; onRequestClose: () => void }) {
const { oxyServices } = useOxy();
if (!visible || !oxyServices) return null;
return (
<Modal visible onRequestClose={onRequestClose} animationType="slide">
<OxyRouter
oxyServices={oxyServices}
initialScreen="OxyAuth"
onClose={onRequestClose}
onAuthenticated={onRequestClose}
theme="light"
containerWidth={360}
/>
</Modal>
);
}
```
## 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
```typescript
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)
```typescript
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
```typescript
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`
```typescript
import { OxyProvider } from '@oxyhq/services';
function App() {
return (
<OxyProvider baseURL="https://cloud.oxy.so">
<YourApp />
</OxyProvider>
);
}
```
#### 2. FormData Issues in React Native/Expo
**Solution**: Add polyfill import at the very top of your entry file
```javascript
// 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
```typescript
<OxyProvider
baseURL="https://api.oxy.so"
storageKeyPrefix="my_app_oxy" // Custom storage key
>
{children}
</OxyProvider>
```
### Error Handling
```typescript
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);
}
}
```
## Requirements
- **React Native**: 0.76+ (for mobile components)
- **Expo**: 54+ (recommended)
- **TypeScript**: 4.0+ (optional but recommended)
### Peer Dependencies
For React Native/Expo projects:
```bash
npm install jwt-decode invariant
```
**Note**: `react-native-url-polyfill` is already included as a dependency in this package.
## Examples
### Complete React Native App
```typescript
// App.tsx
import { OxyProvider } from '@oxyhq/services';
function App() {
return (
<OxyProvider baseURL="https://cloud.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:
- **[Getting Started](./GET_STARTED.md)** - Quick start guide for new developers
- **[Platform Guide](./PLATFORM_GUIDE.md)** - Platform-specific setup guide
- **[CROSS_DOMAIN_AUTH.md](../../docs/CROSS_DOMAIN_AUTH.md)** - SSO deep dive
---
## License
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.