OddSockets JavaScript SDK
Official JavaScript/TypeScript SDK for OddSockets real-time messaging platform
Overview & Features
The OddSockets JavaScript SDK provides a powerful, easy-to-use interface for real-time messaging in both Node.js and browser environments.
Universal Support
Works seamlessly in Node.js and all modern browsers with the same API.
TypeScript Ready
Full TypeScript support with comprehensive type definitions included.
PubNub Compatible
Drop-in replacement for PubNub with migration utilities included.
High Performance
Optimized for low latency 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 @oddsocketsai/javascript-sdk
yarn add @oddsocketsai/javascript-sdk
<script src="https://prodmedia.tyga.host/public/npm/@oddsocketsai/javascript-sdk@latest/dist/oddsockets.min.js"></script>
Quick Start
Basic Usage
import OddSockets from '@oddsocketsai/javascript-sdk';
const client = new OddSockets({
apiKey: 'ak_live_1234567890abcdef'
});
const channel = client.channel('my-channel');
// Subscribe to messages
channel.subscribe((message) => {
console.log('Received:', message);
});
// Publish a message
channel.publish('Hello, World!');
PubNub Migration
import { PubNubCompat } from '@oddsocketsai/javascript-sdk';
// Drop-in replacement for PubNub
const pubnub = new PubNubCompat({
publishKey: 'ak_live_1234567890abcdef',
subscribeKey: 'ak_live_1234567890abcdef',
userId: 'user123'
});
pubnub.addListener({
message: function(messageEvent) {
console.log('Message:', messageEvent.message);
}
});
pubnub.subscribe({
channels: ['my-channel']
});
TypeScript Usage
import OddSockets, { Channel, Message, PresenceInfo } from '@oddsocketsai/javascript-sdk';
const client: OddSockets = new OddSockets({
apiKey: 'ak_live_1234567890abcdef'
});
const channel: Channel = client.channel('typed-channel');
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
reconnectAttempts: 5, // Optional: Max reconnection attempts
heartbeatInterval: 30000 // Optional: Heartbeat interval (ms)
});
Channel Options
channel.subscribe(callback, {
enablePresence: true, // Enable presence tracking
retainHistory: true, // Retain message history
filter: 'user.premium == true' // Message filter expression
});
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 JavaScript SDK in action:
Enhanced Features
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).
Typing, reactions & queries
import OddSockets from '@oddsocketsai/javascript-sdk';
const client = new OddSockets({ apiKey: 'your-api-key', userId: 'alice' });
await client.connect();
await client.channel('room-42').subscribe(() => {}); // join the scoped room
// Receive-path: enhanced broadcasts arrive on the client event surface
client.on('user_typing', (data) => console.log('typing:', data));
client.on('reaction_added', (data) => console.log('reaction:', data));
// Send-path: fire-and-forget actions
client.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 data
const reactions = await client.enhanced.getReactions('msg-1');
const results = await client.enhanced.searchMessages({ query: 'launch', userId: 'alice', limit: 20 });
Event surface
- Typing —
startTyping(userId, channel),stopTyping(userId, channel)→user_typing,user_stopped_typing - Reactions —
addReaction({messageId, channel, emoji, userId, userName}),removeReaction({...}),await getReactions(messageId)→reaction_added,reaction_removed - Threads —
await threadReply({channel, parentMessageId, message, userId, userName}),await getThread(threadId),await subscribeThread(threadId, userId),markThreadRead,followThread,unfollowThread→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({...}),pinMessage({...}),unpinMessage({...}),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({...}),removeFromChannel({...}),joinChannel({...}),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
Enhanced broadcasts are forwarded to the client event surface, so you subscribe to any of them with client.on('<event>', handler) — the same emitter used for connection events.
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 return a Promise that resolves with the worker's reply, while progress and achievement calls are fire-and-forget. Inbound broadcasts arrive on the client event surface — subscribe with the client's normal on(...).
Run a challenge
// Fire-and-forget progress + awaited request/reply methods
await client.enhanced.createChallenge({ challengeId: 'daily-500', metric: 'score', ranked: true });
client.enhanced.reportProgress({ challengeId: 'daily-500', value: 320 });
const board = await client.enhanced.getStandings({ challengeId: 'daily-500', limit: 10 });
await client.enhanced.completeChallenge({ challengeId: 'daily-500', outcome: 'completed' });
Methods
await createChallenge({challengeId, metric, ranked})— create a challenge/leaderboard. Ackschallenge_create_success.reportProgress({challengeId, value})— fire-and-forget metric progress (no ack).await completeChallenge({challengeId, outcome})— finalize with an outcome. Ackschallenge_complete_success.unlockAchievement({achievementId, percentComplete})— fire-and-forget; passpercentComplete(0–100). No ack.await getStandings({challengeId, limit})— request top-N + caller rank. Ackschallenge_standings_success.await getAchievements({achievementId})— query achievement state. Acksachievement_state.await sendChallengeInvite({toUserId, type, payload})— directed invite to another user. Ackschallenge_invite_success.await replyChallengeInvite({inviteId, accept})— accept/decline an invite. Ackschallenge_reply_success.await cancelChallengeInvite({inviteId})— cancel a sent invite. Ackschallenge_invite_cancel_success.await getChallengeInvites()— list pending invites. Ackschallenge_invites.
Completion outcomes
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); >= 100 or omitting the value broadcasts achievement_unlock (status unlocked). You never emit achievement_progress yourself.
Inbound events
Subscribe to these via the client's on(...) surface:
- Room broadcasts —
challenge_progress,leaderboard_rank_change,challenge_complete,achievement_unlock,achievement_progress - Directed (per-user) —
challenge_invited,challenge_reply_received,challenge_invite_cancelled
Performance & Compatibility
OddSockets JavaScript SDK delivers superior performance with broad compatibility:
Browser Support
- Chrome 88+ (2021)
- Firefox 85+ (2021)
- Safari 14+ (2020)
- Edge 88+ (2021)
Node.js Support
- Node.js 16+ (LTS)
- ES2020+ features
- CommonJS & ESM
- TypeScript 4.5+
Framework Integrations
The OddSockets JavaScript SDK works seamlessly with all modern JavaScript frameworks. Here are examples showing how to integrate with popular frameworks:
React
import React, { useState, useEffect } from 'react';
import OddSockets from '@oddsocketsai/javascript-sdk';
function ChatComponent() {
const [client, setClient] = useState(null);
const [messages, setMessages] = useState([]);
const [inputValue, setInputValue] = useState('');
useEffect(() => {
// Initialize OddSockets client
const oddSocketsClient = new OddSockets({
apiKey: 'ak_live_1234567890abcdef'
});
// Get channel and subscribe
const channel = oddSocketsClient.channel('chat-room');
channel.subscribe((message) => {
setMessages(prev => [...prev, message]);
});
setClient(oddSocketsClient);
// Cleanup on unmount
return () => {
oddSocketsClient.disconnect();
};
}, []);
const sendMessage = () => {
if (client && inputValue.trim()) {
const channel = client.channel('chat-room');
channel.publish(inputValue);
setInputValue('');
}
};
return (
<div>
<div>
{messages.map((msg, index) => (
<div key={index}>{msg.message}</div>
))}
</div>
<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
/>
<button onClick={sendMessage}>Send</button>
</div>
);
}
Vue 3
<template>
<div>
<div>
<div v-for="(message, index) in messages" :key="index">
{{ message.message }}
</div>
</div>
<input
v-model="inputValue"
@keyup.enter="sendMessage"
placeholder="Type a message..."
/>
<button @click="sendMessage">Send Message</button>
</div>
</template>
<script>
import { ref, onMounted, onUnmounted } from 'vue';
import OddSockets from '@oddsocketsai/javascript-sdk';
export default {
name: 'ChatComponent',
setup() {
const client = ref(null);
const messages = ref([]);
const inputValue = ref('');
onMounted(() => {
// Initialize OddSockets client
client.value = new OddSockets({
apiKey: 'ak_live_1234567890abcdef'
});
// Get channel and subscribe
const channel = client.value.channel('chat-room');
channel.subscribe((message) => {
messages.value.push(message);
});
});
onUnmounted(() => {
if (client.value) {
client.value.disconnect();
}
});
const sendMessage = () => {
if (client.value && inputValue.value.trim()) {
const channel = client.value.channel('chat-room');
channel.publish(inputValue.value);
inputValue.value = '';
}
};
return {
messages,
inputValue,
sendMessage
};
}
};
</script>
Angular
import { Component, OnInit, OnDestroy } from '@angular/core';
import OddSockets from '@oddsocketsai/javascript-sdk';
@Component({
selector: 'app-chat',
template: `
<div>
<div>
<div *ngFor="let message of messages">
{{ message.message }}
</div>
</div>
<input
[(ngModel)]="inputValue"
(keyup.enter)="sendMessage()"
placeholder="Type a message..."
>
<button (click)="sendMessage()">Send Message</button>
</div>
`
})
export class ChatComponent implements OnInit, OnDestroy {
private client: any;
messages: any[] = [];
inputValue: string = '';
ngOnInit() {
// Initialize OddSockets client
this.client = new OddSockets({
apiKey: 'ak_live_1234567890abcdef'
});
// Get channel and subscribe
const channel = this.client.channel('chat-room');
channel.subscribe((message: any) => {
this.messages.push(message);
});
}
ngOnDestroy() {
if (this.client) {
this.client.disconnect();
}
}
sendMessage() {
if (this.client && this.inputValue.trim()) {
const channel = this.client.channel('chat-room');
channel.publish(this.inputValue);
this.inputValue = '';
}
}
}
Svelte
<script>
import { onMount, onDestroy } from 'svelte';
import OddSockets from '@oddsocketsai/javascript-sdk';
let client;
let messages = [];
let inputValue = '';
onMount(() => {
// Initialize OddSockets client
client = new OddSockets({
apiKey: 'ak_live_1234567890abcdef'
});
// Get channel and subscribe
const channel = client.channel('chat-room');
channel.subscribe((message) => {
messages = [...messages, message];
});
});
onDestroy(() => {
if (client) {
client.disconnect();
}
});
function sendMessage() {
if (client && inputValue.trim()) {
const channel = client.channel('chat-room');
channel.publish(inputValue);
inputValue = '';
}
}
function handleKeyPress(event) {
if (event.key === 'Enter') {
sendMessage();
}
}
</script>
<div>
<div>
{#each messages as message, index}
<div key={index}>{message.message}</div>
{/each}
</div>
<input
bind:value={inputValue}
on:keypress={handleKeyPress}
placeholder="Type a message..."
/>
<button on:click={sendMessage}>Send Message</button>
</div>
Next.js
import { useState, useEffect } from 'react';
import dynamic from 'next/dynamic';
// Dynamically import OddSockets to avoid SSR issues
const OddSockets = dynamic(() => import('@oddsocketsai/javascript-sdk'), {
ssr: false
});
export default function ChatPage() {
const [client, setClient] = useState(null);
const [messages, setMessages] = useState([]);
const [inputValue, setInputValue] = useState('');
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
// Only run on client side
if (typeof window !== 'undefined') {
import('@oddsocketsai/javascript-sdk').then((OddSocketsModule) => {
const OddSocketsClass = OddSocketsModule.default;
const oddSocketsClient = new OddSocketsClass({
apiKey: 'ak_live_1234567890abcdef'
});
const channel = oddSocketsClient.channel('chat-room');
channel.subscribe((message) => {
setMessages(prev => [...prev, message]);
});
setClient(oddSocketsClient);
setIsLoaded(true);
});
}
return () => {
if (client) {
client.disconnect();
}
};
}, []);
const sendMessage = () => {
if (client && inputValue.trim()) {
const channel = client.channel('chat-room');
channel.publish(inputValue);
setInputValue('');
}
};
if (!isLoaded) {
return <div>Loading chat...</div>;
}
return (
<div>
<h1>Next.js Chat</h1>
<div>
{messages.map((msg, index) => (
<div key={index}>{msg.message}</div>
))}
</div>
<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
placeholder="Type a message..."
/>
<button onClick={sendMessage}>Send</button>
</div>
);
}
Nuxt.js
<template>
<div>
<h1>Nuxt.js Chat</h1>
<div v-if="!isLoaded">Loading chat...</div>
<div v-else>
<div>
<div v-for="(message, index) in messages" :key="index">
{{ message.message }}
</div>
</div>
<input
v-model="inputValue"
@keyup.enter="sendMessage"
placeholder="Type a message..."
/>
<button @click="sendMessage">Send Message</button>
</div>
</div>
</template>
<script>
export default {
name: 'ChatPage',
data() {
return {
client: null,
messages: [],
inputValue: '',
isLoaded: false
};
},
async mounted() {
// Dynamic import to avoid SSR issues
if (process.client) {
const { default: OddSockets } = await import('@oddsocketsai/javascript-sdk');
this.client = new OddSockets({
apiKey: 'ak_live_1234567890abcdef'
});
const channel = this.client.channel('chat-room');
channel.subscribe((message) => {
this.messages.push(message);
});
this.isLoaded = true;
}
},
beforeDestroy() {
if (this.client) {
this.client.disconnect();
}
},
methods: {
sendMessage() {
if (this.client && this.inputValue.trim()) {
const channel = this.client.channel('chat-room');
channel.publish(this.inputValue);
this.inputValue = '';
}
}
}
};
</script>
Solid.js
import { createSignal, createEffect, onCleanup } from 'solid-js';
import { For } from 'solid-js';
import OddSockets from '@oddsocketsai/javascript-sdk';
function ChatComponent() {
const [client, setClient] = createSignal(null);
const [messages, setMessages] = createSignal([]);
const [inputValue, setInputValue] = createSignal('');
createEffect(() => {
// Initialize OddSockets client
const oddSocketsClient = new OddSockets({
apiKey: 'ak_live_1234567890abcdef'
});
// Get channel and subscribe
const channel = oddSocketsClient.channel('chat-room');
channel.subscribe((message) => {
setMessages(prev => [...prev, message]);
});
setClient(oddSocketsClient);
// Cleanup on component unmount
onCleanup(() => {
if (oddSocketsClient) {
oddSocketsClient.disconnect();
}
});
});
const sendMessage = () => {
const currentClient = client();
const currentInput = inputValue();
if (currentClient && currentInput.trim()) {
const channel = currentClient.channel('chat-room');
channel.publish(currentInput);
setInputValue('');
}
};
const handleKeyPress = (e) => {
if (e.key === 'Enter') {
sendMessage();
}
};
return (
<div>
<div>
<For each={messages()}>
{(message, index) => (
<div>{message.message}</div>
)}
</For>
</div>
<input
value={inputValue()}
onInput={(e) => setInputValue(e.target.value)}
onKeyPress={handleKeyPress}
placeholder="Type a message..."
/>
<button onClick={sendMessage}>Send Message</button>
</div>
);
}
export default ChatComponent;