OddSockets React Native SDK
Official React Native SDK for OddSockets real-time messaging platform
Overview & Features
The OddSockets React Native SDK provides a powerful, easy-to-use interface for real-time messaging in React Native applications, with full TypeScript support and React Native-specific optimizations.
React Native Optimized
Built specifically for React Native with proper Metro bundler support and native performance.
Full TypeScript
Complete TypeScript implementation with comprehensive type definitions and strict typing.
Enhanced Surface
Reactions, typing, threads, DMs, presence and more over the live socket.
High Performance
Optimized for mobile with efficient WebSocket connections and smart routing.
Cost Effective
No per-message pricing, industry-standard 32KB message limits, transparent pricing.
Automatic Failover
Built-in redundancy and intelligent error handling for 99.9% uptime.
Installation
npm install oddsockets-react-native
yarn add oddsockets-react-native
expo install oddsockets-react-native
Quick Start
Basic Usage
import OddSockets from 'oddsockets-react-native';
const client = new OddSockets({
apiKey: 'ak_live_1234567890abcdef',
userId: 'user-123'
});
const channel = client.channel('my-channel');
// Subscribe to messages
await channel.subscribe((message) => {
console.log('Received:', message);
});
// Publish a message
await channel.publish('Hello, React Native!');
React Component Example
import React, { useState, useEffect } from 'react';
import { View, Text, TextInput, TouchableOpacity } from 'react-native';
import OddSockets from 'oddsockets-react-native';
const ChatComponent = () => {
const [client] = useState(() => new OddSockets({
apiKey: 'ak_live_1234567890abcdef',
userId: 'user-123'
}));
const [messages, setMessages] = useState([]);
const [inputText, setInputText] = useState('');
useEffect(() => {
const channel = client.channel('chat');
channel.subscribe((message) => {
setMessages(prev => [...prev, message]);
});
return () => {
client.disconnect();
};
}, []);
const sendMessage = async () => {
if (inputText.trim()) {
const channel = client.channel('chat');
await channel.publish(inputText);
setInputText('');
}
};
return (
<View>
{messages.map((msg, index) => (
<Text key={index}>{msg.message}</Text>
))}
<TextInput
value={inputText}
onChangeText={setInputText}
placeholder="Type a message..."
/>
<TouchableOpacity onPress={sendMessage}>
<Text>Send</Text>
</TouchableOpacity>
</View>
);
};
Enhanced Events
import OddSockets from 'oddsockets-react-native';
const client = new OddSockets({ apiKey: 'ak_live_1234567890abcdef', userId: 'alice' });
const channel = client.channel('room-42');
await channel.subscribe(() => {}, { enablePresence: true });
// Receive: broadcasts forwarded onto the client event surface
client.on('user_typing', (data) => console.log(`${data.userId} is typing`));
client.on('reaction_added', (data) => console.log(`${data.userId} reacted ${data.emoji}`));
// Send: enhanced actions over the live socket
client.enhanced.startTyping('alice', 'room-42');
client.enhanced.addReaction({
messageId: 'msg-1', channel: 'room-42', emoji: ':thumbsup:',
userId: 'alice', userName: 'Alice',
});
Configuration
Client Options
const client = new OddSockets({
apiKey: 'your-api-key', // Required: Your OddSockets API key
userId: 'user-id', // Optional: User identifier
autoConnect: true, // Optional: Auto-connect on creation
options: { // Optional: Socket.IO options
timeout: 10000,
transports: ['websocket', 'polling']
}
});
Channel Options
channel.subscribe(callback, {
enablePresence: true, // Enable presence tracking
retainHistory: true, // Retain message history
maxHistory: 100 // Max history size
});
channel.publish(message, {
ttl: 3600, // Time to live (seconds)
metadata: { priority: 'high' }, // Additional metadata
storeInHistory: true // Store in message history
});
Examples
Explore comprehensive examples demonstrating the OddSockets React Native SDK in action:
Enhanced Features
Beyond core pub/sub, OddSockets ships a Slack-like enhanced surface —
reactions, typing indicators, threads, read receipts, presence/status, notifications, DMs,
channel management, message editing and search. It lives on client.enhanced.
Send an action with a client.enhanced.* method; receive
the paired broadcast with client.on('<event>', handler) — the worker forwards
every enhanced broadcast onto the client event surface.
Typing & Reactions
const channel = client.channel('room-42');
await channel.subscribe(() => {}, { enablePresence: true });
// Receive-path: broadcasts from other users on the channel
client.on('user_typing', (data) => console.log(`${data.userId} is typing`));
client.on('reaction_added', (data) => console.log(`${data.userId} reacted ${data.emoji}`));
// Send-path: enhanced actions over the live socket
client.enhanced.startTyping('alice', 'room-42');
client.enhanced.addReaction({
messageId: 'msg-1', channel: 'room-42', emoji: ':thumbsup:',
userId: 'alice', userName: 'Alice',
});
Threads
client.on('thread_reply', (data) => console.log('new thread reply', data));
const reply = await client.enhanced.threadReply({
channel: 'room-42', parentMessageId: 'msg-1',
message: 'Replying in the thread', userId: 'alice', userName: 'Alice',
});
// Query methods return a Promise that resolves with the worker response
const thread = await client.enhanced.getThread('thread-1');
Each area exposes methods on client.enhanced; the worker broadcasts the paired
events which you handle with client.on(...):
- Typing —
startTyping,stopTyping→user_typing,user_stopped_typing - Reactions —
addReaction,removeReaction,getReactions→reaction_added,reaction_removed - Threads —
threadReply,getThread,subscribeThread,followThread,markThreadRead→thread_reply,thread_subscribed,thread_followed,thread_read_updated - Read receipts —
markRead,markAllRead,getUnreadCounts→user_read,unread_count_updated,all_marked_read - Messages —
editMessage,deleteMessage,pinMessage,unpinMessage,getPinnedMessages→message_edited,message_deleted,message_pinned,message_unpinned - Presence & status —
setStatus,setCustomStatus,setDND,getUserPresence→user_status_changed,custom_status_updated,dnd_status_changed - Channels —
createChannel,updateChannel,archiveChannel,inviteToChannel,joinChannel,leaveChannel→channel_created,channel_updated,user_invited,user_joined_channel,user_left_channel - DMs —
createDM,sendDM,getDMConversations→dm_created,dm_received - Notifications —
subscribeNotifications,getNotifications,markNotificationRead,clearNotifications→notification,notification_read,notifications_cleared - Search —
searchMessages,searchInChannel,searchByUser,filterMessages(results returned viaPromise)
For any worker event not wrapped above, subscribe with the raw
client.on('<event>', handler) API — all enhanced broadcasts are forwarded onto
the client surface.
Challenges & Leaderboards
Challenges, leaderboards and achievements build on the same live socket. The send
side lives on the enhanced surface (client.enhanced.*); request/query methods
resolve with the worker's reply (use await), while progress and achievement calls
are fire-and-forget. Inbound broadcasts arrive on the client event surface — subscribe
with the client's normal client.on('<event>', handler).
// Create a ranked challenge (leaderboard-backed)
await client.enhanced.createChallenge({ challengeId: 'daily-sprint', metric: 'points', ranked: true });
// Report progress as it happens (fire-and-forget)
client.enhanced.reportProgress({ challengeId: 'daily-sprint', value: 250 });
// Request the current standings (top-N + caller rank)
const standings = await client.enhanced.getStandings({ challengeId: 'daily-sprint', limit: 10 });
// Finalize with an outcome
await client.enhanced.completeChallenge({ challengeId: 'daily-sprint', outcome: 'completed' });
All methods live on client.enhanced. Request/query methods return a
Promise that resolves with the worker acknowledgement; progress and achievement
calls are fire-and-forget (no ack):
createChallenge— create a challenge / leaderboard. Ackchallenge_create_success.reportProgress— fire-and-forget metric progress (no ack).completeChallenge— finalize with an outcome. Ackchallenge_complete_success.unlockAchievement— fire-and-forget; passpercentComplete(0–100) (no ack).getStandings— request top-N + caller rank. Ackchallenge_standings_success.getAchievements— query achievement state. Ackachievement_state.sendChallengeInvite— directed invite to another user. Ackchallenge_invite_success.replyChallengeInvite— accept/decline an invite. Ackchallenge_reply_success.cancelChallengeInvite— cancel a sent invite. Ackchallenge_invite_cancel_success.getChallengeInvites— list pending invites. Ackchallenge_invites.
Outcome vocabulary
The outcome passed to completeChallenge is one of:
completed— win (rank 1)failed— losstied— drawconceded— resign / concedeexpired— timed out
Progressive achievements
unlockAchievement always emits the wire event achievement_unlock; the worker
is authoritative and derives the outbound broadcast from percentComplete. A value
< 100 broadcasts achievement_progress (status in_progress); a value
>= 100 or omitted broadcasts achievement_unlock (status unlocked).
You never emit achievement_progress yourself.
Inbound events
Subscribe to broadcasts and directed events with client.on('<event>', handler):
- Room broadcasts —
challenge_progress,leaderboard_rank_change,challenge_complete,achievement_unlock,achievement_progress - Directed (per-user) —
challenge_invited,challenge_reply_received,challenge_invite_cancelled
Usage Analytics
Pull your tenant's headline usage tiles — monthly active users, daily active users, total messages published, and error rate — straight from the SDK, without hand-rolling a REST call. getUsageStats() resolves the same four tiles the developer dashboard renders.
const stats = await client.getUsageStats();
// Each tile is number | null: null means "not live yet", never a real zero.
const dash = (v: number | null) => v ?? '\u2014';
console.log(`MAU: ${dash(stats.mau)}`);
console.log(`DAU: ${dash(stats.dau)}`);
console.log(`Messages: ${dash(stats.totalMessages)}`);
console.log(`Errors: ${dash(stats.errorRate)}`); // em-dash when null
The four tiles
- mau — monthly active users for your owner scope
- dau — daily active users
- totalMessages — total messages published
- errorRate — publish error rate,
0–1
Honesty rule — null, never a fake zero
Each tile is a number or null. A null means that leg of the analytics pipeline is not live yet for your tenant — the SDK returns null verbatim and never coerces it to 0. Render an em-dash (—) for a null tile so you never show a fabricated zero.
Requires an API key
getUsageStats() reads your owner-scoped analytics, so it needs an apiKey. Keyless / token-only clients have no owner scope to query and will throw getUsageStats requires an apiKey.
Performance & Compatibility
OddSockets React Native SDK delivers superior performance with broad compatibility:
React Native Support
- React Native 0.68+ (2022)
- iOS 11+ / Android API 21+
- Expo SDK 46+
- Metro bundler compatible
TypeScript Support
- TypeScript 4.5+
- Full type definitions
- Strict mode compatible
- IntelliSense support
React Native Specific Features
The OddSockets React Native SDK includes several React Native-specific optimizations and features:
Mobile Optimized
Optimized for mobile networks with intelligent reconnection and battery-efficient polling.
Metro Compatible
Fully compatible with Metro bundler and React Native's module resolution system.
Fetch API
Uses React Native's built-in fetch API instead of external HTTP libraries for better compatibility.
Background Support
Handles app backgrounding gracefully with automatic reconnection when returning to foreground.
React Hooks Integration
// Custom hook for OddSockets
const useOddSockets = (apiKey: string, userId: string) => {
const [client] = useState(() => new OddSockets({ apiKey, userId }));
const [isConnected, setIsConnected] = useState(false);
useEffect(() => {
client.on('connected', () => setIsConnected(true));
client.on('disconnected', () => setIsConnected(false));
return () => {
client.disconnect();
};
}, [client]);
return { client, isConnected };
};
// Usage in component
const MyComponent = () => {
const { client, isConnected } = useOddSockets('your-api-key', 'user-123');
return (
<View>
<Text>Status: {isConnected ? 'Connected' : 'Disconnected'}</Text>
</View>
);
};