Skip to content

Releases: adityaa-codes/echo-android

v1.1.0

05 Mar 18:07

Choose a tag to compare

v1.1.0 — Protocol Correctness

Package: io.github.adityaa-codes:echo:1.1.0
Minimum SDK: Android 30 (Android 11)
Kotlin: 2.3.10 · Ktor: 3.4.0


🎉 What's New

The v1.1.0 release focuses on maximizing stability and protocol correctness with the Pusher Protocol (v7). This update improves how the SDK handles long-lived connections, expiring authentication tokens, and granular server-side error states, ensuring a smooth experience when working with robust backends like Laravel Reverb, Soketi, and Sockudo.


✨ Features & Improvements

1. Per-Channel Error Handling

  • ChannelState.Failed: Channels now expose a discrete Failed(error: EchoError) state in their StateFlow<ChannelState> if their specific subscription request fails (e.g., 403 Forbidden on a single private channel).
  • Per-channel Error Stream: A new channel.error: StateFlow<EchoError?> property allows UIs to render granular error states without relying purely on the global client error stream.
  • Protocol Support: The SDK now intercepts and acts upon pusher_internal:subscription_error frames.

2. Terminal & Degraded Connection States

The ConnectionState hierarchy has been extended to stop infinite and destructive reconnection loops.

  • ConnectionState.Suspended: The SDK will now enter a degraded "Suspended" state after a configurable threshold of continuous reconnection failures (suspendAfterMs, defaulting to 2 minutes). Retries will slow down significantly instead of blindly looping.
  • ConnectionState.Failed: If the server rejects a connection with an unrecoverable Pusher close code (codes 4001–4009, such as an Invalid App Key), the client will gracefully halt all retry attempts and enter a terminal Failed state.

3. Proactive Token Refresh

  • Zero-Downtime Auth Renewals: The auth { } DSL block now accepts a tokenExpiryMs hint. The SDK will automatically launch a proactive refresh timer in the background, firing off the Authenticator and seamlessly re-sending the pusher:subscribe frame onto the existing socket before the token expires, preventing socket tear-downs on long-lived dashboard sessions.

4. Live Presence Updates

  • pusher_internal:member_updated: The SDK now fully supports and integrates the member updated frame sent by Laravel Reverb (≥1.4) and Soketi.
  • updating {} Callback: A new chainable callback has been added to PresenceChannel. You can now receive live metadata changes (e.g., status changes or typing flags) for a member without them needing to drop and re-join the connection.

5. Project & Internal Polish

  • Project Structure Reorganization: The primary logic module was renamed from :core to :echo to better match the final published artifact, and internal documentation was consolidated under the docs/ folder.
  • Local Testing Ecosystem: Instructions for spinning up the companion Laravel DDEV backend (echo-server) locally have been added to the README to streamline SDK integration testing.

📦 Upgrading

Update your dependency in build.gradle.kts:

implementation("io.github.adityaa-codes:echo:1.1.0")

Note: If you previously monitored the global StateFlow<ConnectionState> and mapped it exhaustively (e.g., using a when block without an else), you will need to add branches for the new Suspended and Failed sealed subtypes to ensure your project continues compiling successfully.

v1.0.0

03 Mar 12:42

Choose a tag to compare

Release Notes

v1.0.0 — Initial Stable Release

Package: io.github.adityaa-codes:echo:1.0.0
Minimum SDK: Android 30 (Android 11)
Kotlin: 2.3.10 · Ktor: 3.4.0


🎉 What's New

This is the first stable release of the Echo Kotlin SDK — a robust, type-safe, and idiomatic Kotlin client for Pusher-compatible WebSocket services, with first-class support for Laravel Reverb.


✨ Features

Core Client

  • Kotlin DSL configuration via Echo.create { ... } — configure host, API key, port, TLS, and auth in a single builder block.
  • EchoClient interface exposes connect(), disconnect(), ping(), channel(), private(), presence(), and leave().
  • StateFlow<ConnectionState> — observe Disconnected, Connecting, Connected, and Reconnecting states reactively.
  • SharedFlow<EchoError> — global typed error stream for centralized error handling.
  • Flow<EchoEvent> — global event stream for all incoming WebSocket frames.
  • socketId property available when connected.
  • activeChannels list for inspecting currently subscribed channels.

Connection Lifecycle & Resilience

  • Mutex-guarded connection state machine preventing concurrent connect/disconnect races.
  • Protocol-level ping/pong keep-alive — automatic response to pusher:ping with configurable 30 s pong timeout.
  • Automatic reconnection with configurable exponential backoff and jitter.
  • Reconnection DSL — tune maxAttempts, baseDelayMs, and maxDelayMs directly in the builder.
  • Connection timeout for fast-fail on unreachable servers.

Channel Support

  • Public channels — subscribe with echo.channel("name") and listen for any event.
  • Private channels — subscribe with echo.private("name"); private- prefix added automatically; authenticated before subscribe.
  • Presence channels — subscribe with echo.presence("name"); presence- prefix added automatically; supports here {}, joining {}, and leaving {} callbacks with full member tracking.
  • Client whispers on private channels via whisper(event, data) with enforced client- prefix.
  • Channel state machineUnsubscribed, Subscribing, Subscribed, Failed per-channel state exposed as StateFlow<ChannelState>.
  • Subscription deduplication — repeated channel() / private() / presence() calls return the existing instance.
  • Auto-resubscribe — all active channels are automatically re-authenticated and re-subscribed upon reconnection.

Protocol & Serialization

  • Pusher Protocol v7 compliant — full handling of pusher:connection_established, pusher:ping, pusher:pong, pusher:error, pusher_internal:subscription_succeeded, pusher_internal:member_added, pusher_internal:member_removed.
  • Polymorphic serialization using kotlinx.serialization sealed class hierarchies with @SerialName — no manual JSON parsing.
  • ignoreUnknownKeys = true for forward compatibility with future server frames.

Authentication

  • Suspending Authenticator interface — integrate with any auth backend (HTTP, token-based, custom).
  • Pre-flight auth — authentication is completed before sending pusher:subscribe for private/presence channels.
  • Typed auth failuresEchoError.Auth(status, body) emitted on failure without tearing down the connection.
  • onAuthFailure callback in the DSL for token refresh hooks.

Pluggable Architecture

  • EchoEngine interface — swap the underlying WebSocket transport. Default: KtorEchoEngine (Ktor + OkHttp).
  • EchoSerializer interface — provide a custom frame serializer/deserializer.
  • Both are configurable directly in the client { } DSL block.

Error Handling

  • Sealed EchoError hierarchy:
    • EchoError.Network — network/IO failures with cause.
    • EchoError.Auth — authentication failures with HTTP status and body.
    • EchoError.Protocol — Pusher protocol errors (codes 4000–4299).
    • EchoError.Serialization — JSON parsing failures with cause.

Developer Experience

  • internal by default — minimal public API footprint; only consumer-facing types are public.
  • Explicit API mode (explicitApi()) enabled to prevent accidental API surface leaks.
  • Structured concurrency throughout — no GlobalScope; CoroutineScope is injected.
  • Configurable logging — enable debug logs or supply a custom logger via logging { } DSL block.
  • KDoc on all public API members with usage samples.

📦 Installation

// build.gradle.kts
dependencies {
    implementation("io.github.adityaa-codes:echo:1.0.0")
}

🔧 Quick Start

val echo = Echo.create {
    client {
        host = "your-reverb-server.com"
        apiKey = "your-app-key"
        port = 8080
        useTls = false
    }
    auth {
        authenticator = { channelName, socketId ->
            Result.success("""{"auth":"$socketId:signature"}""")
        }
    }
    reconnection {
        maxAttempts = 10
        baseDelayMs = 1_000
        maxDelayMs = 30_000
    }
    logging { enabled = true }
}

echo.connect()

val channel = echo.channel("chat-room")
channel.listen("MessageSent") { event -> println(event.data) }

echo.state.collect { state -> println("Connection: $state") }
echo.errors.collect { error -> println("Error: $error") }

📋 Requirements

Requirement Version
Android minSdk 30 (Android 11)
compileSdk 36 (Android 16)
Kotlin 2.3.10
Java 11+
Gradle 9.1+

🧪 Test Coverage

  • 40 unit tests across 8 test classes.
  • ≥ 80% line coverage on the core library module.
  • Tests written with MockK and app.cash.turbine for Flow assertions.
  • Integration tests use Ktor MockEngine for end-to-end protocol verification.

📁 Sample App

A fully functional reference app is included in the sample module demonstrating connection lifecycle, channel subscriptions, presence tracking, error handling, and manual ping — all built with UDF architecture (StateFlow<ViewState> + processIntent()).

./gradlew :sample:installDebug

🔗 Links