Skip to content

wspulse/client-kt

Repository files navigation

wspulse/client-kt

CI JitPack Kotlin JVM Android License: MIT

A Kotlin WebSocket client with optional automatic reconnection, designed for use with wspulse/server.

Works on JVM 17+ and Android API 26+ via Ktor CIO.

Status: v0 — API is being stabilized. Artifact: com.github.wspulse:client-kt (JitPack).


Design Goals

  • Thin client: connect, send, receive, auto-reconnect
  • Matches server-side Frame wire format via JSON text frames
  • Exponential backoff with configurable retries (equal jitter)
  • Transport drop vs. permanent disconnect callbacks
  • Coroutine-native with non-blocking send()

Install

Add the JitPack repository and the dependency:

Gradle (Kotlin DSL)

// build.gradle.kts
repositories {
    maven { url = uri("https://jitpack.io") }
}

dependencies {
    implementation("com.github.wspulse:client-kt:v0.2.0")
}

Gradle (Groovy)

// build.gradle
repositories {
    maven { url 'https://jitpack.io' }
}

dependencies {
    implementation 'com.github.wspulse:client-kt:v0.2.0'
}

Maven

<repositories>
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
</repositories>

<dependency>
    <groupId>com.github.wspulse</groupId>
    <artifactId>client-kt</artifactId>
    <version>v0.2.0</version>
</dependency>

Quick Start

import com.wspulse.client.Frame
import com.wspulse.client.WspulseClient

val client = WspulseClient.connect("ws://localhost:8080/ws?room=r1&token=xyz") {
    onMessage = { frame ->
        println("[${frame.event}] ${frame.payload}")
    }
    autoReconnect = AutoReconnectConfig(
        maxRetries = 5,
        baseDelay = 1.seconds,
        maxDelay = 30.seconds,
    )
}

client.send(Frame(event = "msg", payload = mapOf("text" to "hello")))

// Suspend until permanently disconnected.
client.done.await()

Android (ViewModel)

class ChatViewModel : ViewModel() {
    private var client: Client? = null

    fun connect(url: String) {
        viewModelScope.launch {
            client = WspulseClient.connect(url) {
                onMessage = { frame ->
                    // Update UI state
                }
                autoReconnect = AutoReconnectConfig(maxRetries = 10)
            }
        }
    }

    override fun onCleared() {
        super.onCleared()
        viewModelScope.launch { client?.close() }
    }
}

Note: The library is lifecycle-agnostic — it does not reference Dispatchers.Main. Call client.close() in onCleared() or onDestroy() to release resources.


Frame Format

The default JsonCodec encodes frames as JSON text frames:

{
  "id": "msg-001",
  "event": "chat.message",
  "payload": { "text": "hello" }
}

The event field is the routing key on the server side. Set frame.event to match the handler registered with r.On("chat.message", ...) on the server. The payload field carries arbitrary data — the library does not inspect it.

// Send a typed frame — server routes by "event"
client.send(Frame(
    event = "chat.message",
    payload = mapOf("text" to "hello world"),
))

// Receive typed frames
onMessage = { frame ->
    when (frame.event) {
        "chat.message" -> handleMessage(frame)
        "chat.ack"     -> handleAck(frame)
    }
}

To use a custom wire format, implement the Codec interface:

object ProtobufCodec : Codec {
    override val frameType = FrameType.BINARY
    override fun encode(frame: Frame): ByteArray = /* serialize */
    override fun decode(data: ByteArray): Frame = /* deserialize */
}

val client = WspulseClient.connect(url) {
    codec = ProtobufCodec
}

Public API Surface

Symbol Description
Client Interface: send(), close(), done
WspulseClient Implementation with companion object { connect() }
Frame Data class: id?, event?, payload?
Codec Interface: encode(), decode(), frameType
JsonCodec Default codec — JSON text frames
ClientConfig Builder DSL for client configuration

Client Options

Option Type Default
onMessage (Frame) -> Unit no-op
onDisconnect (WspulseException?) -> Unit no-op
onTransportRestore () -> Unit no-op
onTransportDrop (Exception) -> Unit no-op
autoReconnect AutoReconnectConfig? null (disabled)
heartbeat HeartbeatConfig 20s / 60s
writeWait Duration 10s
maxMessageSize Long 1 MiB (1 048 576)
dialHeaders Map<String, String> emptyMap()
codec Codec JsonCodec

Logging

The client logs internal diagnostics via SLF4J. Add an SLF4J binding to your project to see log output.

Example with slf4j-simple (Gradle):

dependencies {
    runtimeOnly("org.slf4j:slf4j-simple:2.0.16")
}

Disable logging by using the slf4j-nop binding:

dependencies {
    runtimeOnly("org.slf4j:slf4j-nop:2.0.16")
}

Error Types

Exception Thrown by / Passed to
ConnectionClosedException send() after close()
SendBufferFullException send() when buffer full
RetriesExhaustedException onDisconnect
ConnectionLostException onDisconnect

Features

  • Auto-reconnect — exponential backoff with configurable max retries, base delay, and max delay. Equal jitter formula: delay ∈ [half, full] where full = min(base × 2^attempt, max).
  • Transport drop callbackonTransportDrop fires on every transport death, even when auto-reconnect follows. Useful for metrics and logging.
  • Permanent disconnect callbackonDisconnect fires exactly once when the client is truly done (close() called, retries exhausted, or connection lost without auto-reconnect).
  • Heartbeat — Client-side Ping/Pong keeps the connection alive and detects silently-dead servers.
  • Max message size — Inbound messages exceeding maxMessageSize are rejected with close code 1009.
  • Backpressure — bounded 256-frame send buffer; throws SendBufferFullException when full.
  • Non-blocking sendsend() is a regular function (not suspend), safe to call from any coroutine or thread.
  • done Deferred — completes when the client reaches CLOSED state. Await it to suspend until permanently disconnected.
  • Idempotent closeclose() is safe to call multiple times from concurrent coroutines.

Development

make fmt        # auto-format source files (ktlint)
make check      # lint, test (pre-commit gate)
make test       # ./gradlew test
make clean      # remove build artifacts

Related Modules

Module Description
wspulse/server WebSocket server
wspulse/client-go Go client (reference implementation)
wspulse/client-ts TypeScript client

About

Kotlin WebSocket client for JVM and Android with auto-reconnect, exponential backoff, and heartbeat. Designed for use with wspulse/server.

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors