JavaScript

JavaScript SDK

Type-safe client for browsers and Node.js.

Installation

npm install @raucheacho/konet-js

Quick Start

import { createClient } from '@raucheacho/konet-js'
 
const client = createClient('ws://localhost:4000/socket', {
  token: '<anon_key>',
})
 
const channel = client.channel('room:lobby')
await channel.subscribe()
 
channel.on('message', (payload) => {
  console.log('Received:', payload)
})
 
channel.send('message', { text: 'Hello!' })

createClient connects immediately. Call client.disconnect() to close the socket.

Client Options

interface KonetClientOptions {
  token: string
  heartbeatIntervalMs?: number   // default: 30000
  reconnectDelayMs?: number      // default: 1000
  maxReconnectAttempts?: number  // default: 10
  heartbeatTimeoutMs?: number    // default: 10000
}

heartbeatTimeoutMs is how long the client waits for a heartbeat reply before declaring the socket dead. Keep it below the server's socket timeout (45s).

Reconnection

The client reconnects with exponential backoff and re-joins every channel you subscribed to, so a dropped connection is transparent to your code. A channel you left with unsubscribe() is never re-joined.

While disconnected, send() throws rather than writing into a socket the server no longer associates with your topic. After a re-join the server replays presence_state, so getPresence() is accurate again on its own.

Suspended Environments

setInterval is a scheduler, not a clock: browsers throttle timers in inactive tabs and mobile runtimes freeze them. A suspended client wakes up with a socket that still reports OPEN long after the server timed it out.

Call checkConnection() whenever the host may have suspended the client. It probes the connection, reconnects if the probe goes unanswered, and revives a client that exhausted maxReconnectAttempts while suspended.

document.addEventListener('visibilitychange', () => {
  if (!document.hidden) client.checkConnection()
})

In React Native, use @raucheacho/konet-rn, which wires this to AppState for you.

Presence

Presence updates arrive as a presence event with the current member list:

channel.on('presence', (users) => {
  console.log('Online:', users.length)
})
 
// or read the current snapshot on demand
const users = channel.getPresence().list()

Error Handling

A join that the server refuses rejects the subscribe() promise:

try {
  await channel.subscribe()
} catch (err) {
  console.error('Join failed:', err.reason)
}

Konet acknowledges a broadcast only when it refuses it — rate limiting, an unsupported payload. There is no ack on success, so send() stays fire-and-forget rather than returning a promise that could never settle. Refusals surface per call and per channel:

channel.send('audio', chunk, (err) => {
  console.warn('dropped:', err.reason)   // e.g. "rate_limited"
})
 
channel.on('send_error', (err) => metrics.increment(err.reason))
interface KonetSendError {
  topic: string
  event: string      // the application event you passed to send()
  payload: unknown
  reason: string     // server-supplied, e.g. "rate_limited"
}

This matters most on high-frequency streams, where the server's per-socket rate limit is otherwise reached silently. Raise it with KONET_RATE_LIMIT on the server if you see rate_limited under normal load.

Binary Frames & Floor Control

For anything at a media rate. Take the floor before sending — a binary frame without it is refused. See the protocol page for the guarantees.

const channel = client.channel('room:team-42:ptt')
await channel.subscribe()
 
channel.on('konet:floor', ({ holder }) => {
  // holder is a user id, or null when the floor is free
})
 
channel.on('a', (data: Uint8Array) => {
  // an incoming frame; the view is only valid for this call
})
 
try {
  await channel.acquireFloor()
  channel.sendBinary('a', frame) // frame: Uint8Array
} catch (error) {
  // someone else is talking; the error names them
} finally {
  await channel.releaseFloor()
}

sendBinary copies its argument, so a caller may reuse one buffer for every frame — which an audio path does, every 20 ms.

Unlike send(), binary frames are never buffered across a reconnect. Replaying audio recorded seconds ago into a live channel is worse than losing it.

Types

The SDK is written in TypeScript and ships with declaration files.