KtorClientLoom

class KtorClientLoom(httpClient: HttpClient, dispatcher: CoroutineDispatcher = Dispatchers.Default, val selfPeerId: PeerId = PeerId(Uuid.random().toString()), weft: Weft<WebSocketDialContext> = { WebSocketDialContext() }, connectivity: ConnectivityObserver = UnobservedConnectivity) : Loom

Client-side Loom backed by Ktor WebSockets.

join connects directly to a WebSocketAdvertisement URL and returns a 2-peer WebSocketSeam — no intermediate contract Session adapter.

Pairing: KtorClientLoom ↔ KtorServerLoom/KtorRoomHost — a 2-peer relay with no in-band handshake. It is not the client for a us.tractat.kuilt.core.MuxServerLoom hub: that hub handshakes every spoke with an in-band MeshHello preamble a WebSocketSeam never sends, so pointing this loom at one silently never completes admit. Use KtorMeshClientLoom (hub-spoke mesh) for a MuxServerLoom hub.

PeerId discovery:

Stable identity across reconnects: supplying selfPeerId gives this loom a fixed fabric identity reused on every call to weave/join. This is required for cluster-client failover: the server derives a learner NodeId from the admitted PeerId; if a reconnect mints a new random id the server admits a different learner and Raft routing breaks. The default mints a fresh random identity per loom instance (mirroring the old per-join behaviour for callers that do not need stable identity). See #544.

Per-dial credentials: weft is invoked fresh inside every weave call — the first dial and every subsequent redial — so a caller can mint a single-use credential (e.g. a short-lived WS ticket) that survives kuilt's transparent reconnect instead of being baked once into a static WebSocketAdvertisement.url. See #1330.

HttpClient lifecycle: the httpClient is not closed by this loom. Callers are responsible for closing it when all connections are done.

Parameters

dispatcher

Scheduler for the per-connection seam's read/write loops; the loom confines it to a single thread via limitedParallelism(1). Production default is Dispatchers.Default; tests inject kotlinx.coroutines.test.UnconfinedTestDispatcher.

selfPeerId

The fabric identity this loom presents on every join. Defaults to a random UUID minted once at construction; supply a deterministic value for stable cluster-client identity across reconnects.

weft

Supplies a WebSocketDialContext fresh on every dial. Defaults to an empty context (no extra query params/headers).

connectivity

Live device-reachability observer driving every woven Seam's Seam.capability (#1725). Defaults to UnobservedConnectivity — the identity element, under which a seam reports the honest FabricAvailability.Unknown floor exactly as before. Supply androidConnectivityObserver(context) on Android or browserConnectivityObserver() on wasmJs; the desktop JVM has no portable observer and is meant to be left unwired (see ConnectivityObserver).

Constructors

Link copied to clipboard
constructor(httpClient: HttpClient, dispatcher: CoroutineDispatcher = Dispatchers.Default, selfPeerId: PeerId = PeerId(Uuid.random().toString()), weft: Weft<WebSocketDialContext> = { WebSocketDialContext() }, connectivity: ConnectivityObserver = UnobservedConnectivity)

Properties

Link copied to clipboard

Functions

Link copied to clipboard
Link copied to clipboard
open override fun capability(): TransportCapability

The pre-connect report, deliberately unchanged by #1725: it answers "can this fabric be attempted on this runtime" — a Ktor WebSocket client exists on every target — not "is the device's path up right now", which is the live per-session Seam.capability's job. Keeping the two apart is the #1712 distinction; connectivity therefore feeds the seam, not this.

Link copied to clipboard
open suspend fun host(pattern: Pattern): Seam
Link copied to clipboard
open suspend fun join(tag: Tag): Seam
Link copied to clipboard
open suspend override fun weave(rendezvous: Rendezvous): Seam

Establishes a Seam: