OddSockets Svelte SDK
Official Svelte SDK with reactive stores and components for OddSockets real-time messaging
Overview & Features
The OddSockets Svelte SDK provides reactive stores and components built on top of the JavaScript SDK, offering seamless integration with Svelte's reactive system for automatic UI updates.
Reactive Stores
Automatic UI updates with Svelte's reactive system. Messages, presence, and connection state update automatically.
SvelteKit Ready
Full SSR and hydration support with proper client-side initialization and cleanup.
JavaScript SDK Core
Built on the JavaScript SDK as single source of truth for all WebSocket functionality.
High Performance
Optimized reactive updates with efficient store subscriptions and automatic cleanup.
Pre-built Components
Ready-to-use Svelte components for common real-time messaging patterns.
Automatic Cleanup
Built-in lifecycle management with automatic cleanup on component destroy.
Installation
npm install oddsockets-svelte-sdk
yarn add oddsockets-svelte-sdk
pnpm add oddsockets-svelte-sdk
Quick Start
Basic Chat with Reactive Stores
<script>
import { createChannelStore } from 'oddsockets-svelte-sdk/stores';
import { onMount } from 'svelte';
const { messages, presence, publish, subscribe, unsubscribe } = createChannelStore('chat', {
apiKey: 'ak_your_api_key_here',
userId: 'user123'
});
let messageText = '';
onMount(() => {
subscribe();
return unsubscribe; // Cleanup on destroy
});
async function sendMessage() {
if (messageText.trim()) {
await publish({
type: 'chat',
text: messageText,
username: 'John Doe'
});
messageText = '';
}
}
</script>
<!-- Reactive UI updates automatically -->
<div class="chat-container">
<div class="messages">
{#each $messages as message}
<div class="message">
<strong>{message.data.username}:</strong>
{message.data.text}
</div>
{/each}
</div>
<div class="presence">
Online: {$presence.length} users
</div>
<input
bind:value={messageText}
placeholder="Type a message..."
on:keydown={(e) => e.key === 'Enter' && sendMessage()}
/>
<button on:click={sendMessage}>Send</button>
</div>
Connection Status
<script>
import { createConnectionStore } from 'oddsockets-svelte-sdk/stores';
const { connectionState, isConnected, reconnectAttempts } = createConnectionStore({
apiKey: 'ak_your_api_key_here'
});
</script>
<div class="status {$connectionState}">{$connectionState}</div>
{#if $isConnected}
<span>Live</span>
{:else if $reconnectAttempts > 0}
<span>Reconnecting (attempt {$reconnectAttempts})</span>
{/if}
Reactive Stores
The Svelte SDK's core strength is its reactive stores — subscribe once and let Svelte update the UI automatically as messages, presence and connection state change.
Channel Store
<script>
import { createChannelStore } from 'oddsockets-svelte-sdk/stores';
const {
messages, presence, messageCount, presenceCount, latestMessage,
publish, subscribe, unsubscribe
} = createChannelStore('my-channel', { apiKey: 'ak_your_api_key_here' });
</script>
<div>Messages: {$messageCount} · Online: {$presenceCount}</div>
{#if $latestMessage}
<div>Latest: {$latestMessage.data.text}</div>
{/if}
Multi-Channel Store
<script>
import { createMultiChannelStore } from 'oddsockets-svelte-sdk/stores';
const { channels, allMessages, totalPresence } = createMultiChannelStore(
['chat', 'notifications', 'updates'],
{ apiKey: 'ak_your_api_key_here' }
);
</script>
<div>Total users: {$totalPresence}</div>
{#each $allMessages as message}
<div data-channel="{message.channel}">{message.data.text}</div>
{/each}
Examples
Explore complete working examples in the examples/ directory of the repository:
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,
where client is the instance returned by createOddSocketsClient.
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
import { createOddSocketsClient } from 'oddsockets-svelte-sdk';
const client = createOddSocketsClient({ apiKey: 'ak_your_api_key_here', userId: 'alice' });
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 (positional args)
client.enhanced.startTyping('alice', 'room-42');
client.enhanced.addReaction('msg-1', 'room-42', ':thumbsup:', 'alice', 'Alice');
Threads
client.on('thread_reply', (data) => console.log('new thread reply', data));
const reply = await client.enhanced.threadReply(
'room-42', 'msg-1', 'Replying in the thread', 'alice', '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 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 client.on('<event>', handler).
// Create a leaderboard-style challenge (awaits challenge_create_success)
const { challengeId } = await client.enhanced.createChallenge({
name: 'Weekend Sprint', metric: 'points', scope: 'room-42'
});
// Fire-and-forget metric progress (no ack)
client.enhanced.reportProgress({ challengeId, userId: 'alice', value: 120 });
// Request top-N + the caller's rank (awaits challenge_standings_success)
const standings = await client.enhanced.getStandings({ challengeId, top: 10 });
// Finalize with an outcome (awaits challenge_complete_success)
await client.enhanced.completeChallenge({ challengeId, userId: 'alice', outcome: 'completed' });
Send-path methods
Each method is called on client.enhanced. Methods marked await resolve with the
worker's reply on the named ack event; the rest are fire-and-forget over the live socket.
createChallenge(await) — create a challenge/leaderboard → ackchallenge_create_successreportProgress— fire-and-forget metric progress (no ack)completeChallenge(await) — finalize with an outcome → ackchallenge_complete_successunlockAchievement— fire-and-forget; passpercentComplete(0–100) (no ack)getStandings(await) — request top-N + caller rank → ackchallenge_standings_successgetAchievements(await) — query achievement state → ackachievement_statesendChallengeInvite(await) — directed invite to another user → ackchallenge_invite_successreplyChallengeInvite(await) — accept/decline an invite → ackchallenge_reply_successcancelChallengeInvite(await) — cancel a sent invite → ackchallenge_invite_cancel_successgetChallengeInvites(await) — 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);
>= 100 or an omitted value broadcasts achievement_unlock (status
unlocked). You never emit achievement_progress yourself.
Inbound events
Subscribe to these with client.on('<event>', handler). Room broadcasts fan out to
everyone in scope; directed events are delivered only to the targeted user.
- 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
Keep reactive rendering fast by capping the messages you render and keying each loop.
Efficient Rendering
<script>
import { createChannelStore } from 'oddsockets-svelte-sdk/stores';
const { messages } = createChannelStore('chat', { apiKey: 'ak_your_api_key_here' });
// Only render the most recent messages for smooth updates
$: recentMessages = $messages.slice(-50);
</script>
{#each recentMessages as message (message.id)}
<div class="message">{message.data.text}</div>
{/each}
- Low latency real-time delivery over a genuine Socket.IO connection.
- 32KB message size limit for predictable performance.
- Automatic reconnection with exponential backoff and session stickiness.
- Bulk publishing via
client.publishBulk(messages)for high-throughput fan-out.
SvelteKit
The SDK connects from the browser, so create stores inside onMount (guarded by the
browser flag) to avoid running during server-side rendering.
<!-- src/routes/chat/+page.svelte -->
<script>
import { browser } from '$app/environment';
import { createChannelStore } from 'oddsockets-svelte-sdk/stores';
import { onMount } from 'svelte';
let store;
onMount(() => {
if (browser) {
store = createChannelStore('chat', { apiKey: 'ak_your_api_key_here' });
store.subscribe();
return () => store.unsubscribe();
}
});
</script>
{#if store}
{#each $store.messages as message}
<div>{message.data.text}</div>
{/each}
{:else}
<div>Loading chat...</div>
{/if}