Official JavaScript/TypeScript SDK for OddSockets real-time messaging platform.
npm install @oddsocketsai/javascript-sdk
# or
yarn add @oddsocketsai/javascript-sdkimportOddSocketsfrom'@oddsocketsai/javascript-sdk';// Create client (auto-connects by default)constclient=newOddSockets({apiKey: 'your-api-key-here'});// Get a channelconstchannel=client.channel('my-channel');// Subscribe to messageschannel.subscribe((message)=>{console.log('Received:',message);});// Publish a messagechannel.publish('Hello, World!');Need an API Key?Sign up for free at https://oddsockets.com/signup to get your API key and start building real-time applications.
// Basic client (auto-connects)constclient=newOddSockets({apiKey: 'your-api-key'});// With optionsconstclient=newOddSockets({apiKey: 'your-api-key',userId: 'user123',// Optional: custom user IDautoConnect: false,// Optional: disable auto-connectoptions: {// Optional: Socket.IO optionstransports: ['websocket'],timeout: 10000}});// Manual connection (if autoConnect: false)awaitclient.connect();client.on('connecting',()=>{console.log('Connecting to OddSockets...');});client.on('connected',()=>{console.log('Connected successfully!');});client.on('worker_assigned',(info)=>{console.log('Assigned to worker:',info.workerId);console.log('Worker URL:',info.workerUrl);});client.on('disconnected',(reason)=>{console.log('Disconnected:',reason);});client.on('reconnecting',(info)=>{console.log(`Reconnecting... attempt ${info.attempt}/${info.maxAttempts}`);});client.on('error',(error)=>{console.error('Connection error:',error);});// Get a channel (creates if doesn't exist)constchannel=client.channel('chat-room');// Subscribe to messagesawaitchannel.subscribe((message)=>{console.log('Message from',message.userId,':',message.data);});// Subscribe with optionsawaitchannel.subscribe((message)=>{console.log('Received:',message);},{enablePresence: true,// Track who's onlineretainHistory: true,// Keep message historymaxHistory: 50// Max messages to retain});// Publish messagesawaitchannel.publish('Hello everyone!');// Publish with optionsawaitchannel.publish({text: 'Hello!',timestamp: Date.now()},{ttl: 3600,// Time to live (seconds)metadata: {priority: 'high'},storeInHistory: true});// Unsubscribeawaitchannel.unsubscribe();// Get recent messagesconsthistory=awaitchannel.getHistory();console.log('Recent messages:',history);// Get specific rangeconstmessages=awaitchannel.getHistory({count: 20,// Number of messagesstart: '2023-01-01T00:00:00Z',// Start timeend: '2023-01-02T00:00:00Z'// End time});// Get cached history (from memory)constcached=channel.getCachedHistory();// Enable presence on subscriptionawaitchannel.subscribe(callback,{enablePresence: true});// Get current presenceconstpresence=awaitchannel.getPresence();console.log('Online users:',presence.occupants);// Listen for presence changeschannel.on('presence_change',(data)=>{if(data.action==='join'){console.log('User joined:',data.user.userId);}elseif(data.action==='leave'){console.log('User left:',data.user.userId);}});// Update your stateawaitchannel.updateState({status: 'online',mood: 'happy'});// Publish to multiple channels at onceconstresults=awaitclient.publishBulk([{channel: 'channel1',message: 'Hello channel 1!'},{channel: 'channel2',message: {text: 'Hello channel 2!'},options: {ttl: 3600}}]);// Check resultsresults.forEach((result,index)=>{if(result.success){console.log(`Message ${index} sent successfully`);}else{console.error(`Message ${index} failed:`,result.error);}});// Check connection stateconsole.log('State:',client.getState());// 'connected', 'connecting', etc.// Get worker infoconstworkerInfo=client.getWorkerInfo();if(workerInfo){console.log('Connected to worker:',workerInfo.workerId);}// Manual disconnectclient.disconnect();// Manual reconnectawaitclient.connect();try{awaitchannel.publish('My message');}catch(error){if(error.message.includes('32KB')){console.error('Message too large! Max size is 32KB');}elseif(error.message.includes('Not connected')){console.error('Not connected to OddSockets');awaitclient.connect();}else{console.error('Publish failed:',error.message);}}importOddSockets,{Channel}from'@oddsocketsai/javascript-sdk';interfaceMyMessage{text: string;userId: string;timestamp: number;}constclient: OddSockets=newOddSockets({apiKey: 'your-api-key'});constchannel: Channel=client.channel('typed-channel');channel.subscribe((message: MyMessage)=>{console.log(`${message.userId}: ${message.text}`);});awaitchannel.publish<MyMessage>({text: 'Hello TypeScript!',userId: 'user123',timestamp: Date.now()});<!DOCTYPE html><html><head><scriptsrc="https://prodemedia.tyga.host/npm/@oddsocketsai/javascript-sdk@latest/dist/oddsockets.min.js"></script></head><body><script>constclient=newOddSockets({apiKey: 'your-api-key'});constchannel=client.channel('browser-chat');channel.subscribe((message)=>{console.log('Browser received:',message);});// Send message when page loadschannel.publish('Hello from browser!');</script></body></html>Enhanced (Slack-like) events layer on top of the core pub/sub. The send side lives
on client.enhanced.*; fire-and-forget actions return undefined, while query/request
methods return a Promise that resolves with the worker's response. The matching
broadcast is forwarded to the client's own event surface, so any subscriber can
react with client.on('<event>', handler).
importOddSocketsfrom'@oddsocketsai/javascript-sdk';constclient=newOddSockets({apiKey: 'your-api-key',userId: 'alice'});awaitclient.connect();awaitclient.channel('room-42').subscribe(()=>{});// join the scoped room// Receive-path: enhanced broadcasts arrive on the client event surfaceclient.on('user_typing',(data)=>console.log('typing:',data));client.on('reaction_added',(data)=>console.log('reaction:',data));// Send-path: fire-and-forget actionsclient.enhanced.startTyping('alice','room-42');client.enhanced.addReaction({messageId: 'msg-1',channel: 'room-42',emoji: ':thumbsup:',userId: 'alice',userName: 'Alice'});// Request/response methods resolve with the worker's dataconstreactions=awaitclient.enhanced.getReactions('msg-1');constresults=awaitclient.enhanced.searchMessages({query: 'launch',userId: 'alice',limit: 20});| Area | Send (client.enhanced.*) | Broadcast (client.on(...)) |
|---|---|---|
| Typing | startTyping(userId, channel) · stopTyping(userId, channel) | user_typing · user_stopped_typing |
| Reactions | addReaction({messageId, channel, emoji, userId, userName}) · removeReaction({messageId, channel, emoji, userId}) · await getReactions(messageId) | reaction_added · reaction_removed |
| Threads | await threadReply({channel, parentMessageId, message, userId, userName}) · await getThread(threadId) · await subscribeThread(threadId, userId) · markThreadRead(threadId, userId) · followThread(threadId, userId) · unfollowThread(threadId, userId) | thread_reply · thread_subscribed · thread_followed · thread_unfollowed · thread_read_updated |
| Read receipts | markRead({messageId, channel, userId, userName}) · await getUnreadCounts(userId, channels) · markAllRead(channel, userId) | user_read · unread_count_updated · all_marked_read |
| Messages | editMessage({messageId, channel, newContent, userId}) · deleteMessage({messageId, channel, userId}) · pinMessage({messageId, channel, userId}) · unpinMessage({messageId, channel, userId}) · await getPinnedMessages(channel) | message_edited · message_deleted · message_pinned · message_unpinned |
| Presence & status | setStatus(userId, status) · setCustomStatus({userId, emoji, text, expiresAt}) · clearCustomStatus(userId) · setDND(userId, until) · clearDND(userId) · await getUserPresence(userIds) | user_status_changed · custom_status_updated · custom_status_cleared · dnd_status_changed · status_updated |
| Channels | await createChannel({name, type, description, topic, createdBy, createdByName}) · updateChannel({channelId, updates, userId}) · archiveChannel(channelId, userId) · inviteToChannel({channelId, invitedUserId, invitedUserName, invitedBy}) · removeFromChannel({channelId, removedUserId, removedBy}) · joinChannel({channelId, userId, userName}) · leaveChannel(channelId, userId) · await getChannelMembers(channelId) | channel_created · channel_updated · user_invited · user_joined_channel · user_left_channel · user_removed |
| Direct messages | await createDM({userIds, type}) · sendDM({conversationId, message, userId, userName}) · await getDMConversations(userId, includeArchived) | dm_created · dm_received |
| Notifications | subscribeNotifications(userId) · markNotificationRead(notificationId, userId) · markAllNotificationsRead(userId) · clearNotifications(userId) · await getNotifications({userId, limit, status}) | notification · notification_read · all_notifications_read · notifications_cleared |
| Search | await searchMessages({query, userId, limit}) · await filterMessages({...}) · await searchInChannel({channel, query, limit}) · await searchByUser({userId, query, limit}) | resolves with the matching result set |
- Maximum message size: 32KB (industry standard)
- Automatic validation: SDK validates message size before sending
- UTF-8 encoding: Proper byte counting for international characters
- Single endpoint: SDK connects to cluster loadbalanacer for simplicity
- Automatic routing: Infrastructure transparently routes to optimal regional worker
- Global load balancing: Manager handles regional distribution behind the scenes
- Optimal worker assignment: Manager assigns best worker based on load and location
- Session persistence: Reconnections use same worker when possible
- Load balancing: Intelligent distribution across workers in the cluster
- Exponential backoff: Smart retry timing
- Max attempts: Configurable retry limits
- State preservation: Maintains subscriptions across reconnects
AI agents can sign up with a verified email in two steps — no dashboard, no human required.
Step 1: Request a verification code
curl -X POST https://oddsockets.com/api/agent-signup \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com", "agentName": "my-agent", "platform": "claude"}'Step 2: Verify the 6-digit code from your email and get your API key
curl -X POST https://oddsockets.com/api/agent-signup/verify \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com", "code": "123456", "agentName": "my-agent"}'| Free | Starter | Pro | |
|---|---|---|---|
| Price | $0/mo | $49.99/mo | $299/mo |
| MAU | 100 | 1,000 | 50,000 |
| Concurrent connections | 50 | 1,000 | Unlimited |
| Messages/day | 10,000 | 4,320,000 | Unlimited |
| Messages/minute | 100 | 3,000 | Unlimited |
| Channels | 10 | Unlimited | Unlimited |
| Storage | 100MB (24h) | 50GB (6 months) | Unlimited |
| Webhooks | No | Yes | Yes |
| Analytics | No | Yes | Yes |
| Support | Community | 24/5 email & chat | Dedicated team |
All limits are enforced in real time. When a limit is reached, the SDK receives a RATE_LIMIT_EXCEEDED error with a retryAfter value.
Prove you can build and operate real-time features on OddSockets — channels, presence, pub/sub, delivery guarantees and production liveops — on the stack itself. Three tiers (TCU / TCA / TCP), certified through tyga.games and delivered on ClassaaS.
Get accredited on tyga.games →
MIT License - Copyright (c) 2026 Joe Wee, Tyga.Cloud Ltd. See LICENSE for details.