OddSockets JavaScript SDK

Official JavaScript/TypeScript SDK for OddSockets real-time messaging platform

npm ready TypeScript Universal High Performance PubNub Compatible

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

bash
npm install @oddsocketsai/javascript-sdk
bash
yarn add @oddsocketsai/javascript-sdk
html
<script src="https://prodmedia.tyga.host/public/npm/@oddsocketsai/javascript-sdk@latest/dist/oddsockets.min.js"></script>

Quick Start

Basic Usage

javascript
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

javascript
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

typescript
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

javascript
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

javascript
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

  • TypingstartTyping(userId, channel), stopTyping(userId, channel)user_typing, user_stopped_typing
  • ReactionsaddReaction({messageId, channel, emoji, userId, userName}), removeReaction({...}), await getReactions(messageId)reaction_added, reaction_removed
  • Threadsawait threadReply({channel, parentMessageId, message, userId, userName}), await getThread(threadId), await subscribeThread(threadId, userId), markThreadRead, followThread, unfollowThreadthread_reply, thread_subscribed, thread_followed, thread_unfollowed, thread_read_updated
  • Read receiptsmarkRead({messageId, channel, userId, userName}), await getUnreadCounts(userId, channels), markAllRead(channel, userId)user_read, unread_count_updated, all_marked_read
  • MessageseditMessage({messageId, channel, newContent, userId}), deleteMessage({...}), pinMessage({...}), unpinMessage({...}), await getPinnedMessages(channel)message_edited, message_deleted, message_pinned, message_unpinned
  • Presence & statussetStatus(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
  • Channelsawait 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 messagesawait createDM({userIds, type}), sendDM({conversationId, message, userId, userName}), await getDMConversations(userId, includeArchived)dm_created, dm_received
  • NotificationssubscribeNotifications(userId), markNotificationRead(notificationId, userId), markAllNotificationsRead(userId), clearNotifications(userId), await getNotifications({userId, limit, status})notification, notification_read, all_notifications_read, notifications_cleared
  • Searchawait 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. Acks challenge_create_success.
  • reportProgress({challengeId, value}) — fire-and-forget metric progress (no ack).
  • await completeChallenge({challengeId, outcome}) — finalize with an outcome. Acks challenge_complete_success.
  • unlockAchievement({achievementId, percentComplete}) — fire-and-forget; pass percentComplete (0–100). No ack.
  • await getStandings({challengeId, limit}) — request top-N + caller rank. Acks challenge_standings_success.
  • await getAchievements({achievementId}) — query achievement state. Acks achievement_state.
  • await sendChallengeInvite({toUserId, type, payload}) — directed invite to another user. Acks challenge_invite_success.
  • await replyChallengeInvite({inviteId, accept}) — accept/decline an invite. Acks challenge_reply_success.
  • await cancelChallengeInvite({inviteId}) — cancel a sent invite. Acks challenge_invite_cancel_success.
  • await getChallengeInvites() — list pending invites. Acks challenge_invites.

Completion outcomes

The outcome passed to completeChallenge is one of:

  • completed — win (rank 1)
  • failed — loss
  • tied — draw
  • conceded — resign / concede
  • expired — 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 broadcastschallenge_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:

<50ms
Latency
99.9%
Uptime
32KB
Max Message
1M+
Messages/sec

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

jsx
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

vue
<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

typescript
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

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

jsx
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

vue
<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

jsx
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;