Menu 0.6.0 preview

Reference

SDK API reference

The three SDKs expose one API (trueseal-sync ADR-0028). Each entry gives the shared signature, then each platform’s spelling where it differs. Swift uses async throws, Kotlin uses suspend with exceptions, and TypeScript uses Promises. Errors, events and delivery issues are on Errors and events. Values are on Limits and Versions and Relay Address.

Opening and closing

Import

Each SDK is one package with one public module.

PlatformSpelling
Swiftimport TrueSealSync
Kotlinimport dev.trueseal.sync.*
TypeScriptimport { TrueSeal, RelayAddress, TrueSealError } from "@trueseal/sync"

TrueSeal

One device in one namespace. The object outlives group membership. After a removal, leave() or destroyGroup(), the library wipes the namespace, makes a fresh identity and reports statusChanged(notJoined, reason), and the same object can pair again.

What to do. Open one object per namespace when the app starts and keep it. Never rebuild it after leaving a group.

PlatformSpelling
Swift@Observable class TrueSeal
Kotlinclass TrueSeal : Closeable
TypeScriptclass TrueSeal

static async open(relay: RelayAddress | string, storage: Storage, namespace: string = "default", maxPayloadBytes?: int) -> TrueSeal

Opens the device stored in storage under namespace. It returns before the relay connection is up and connects in the background, and send() works at once. maxPayloadBytes may lower the Protocol Size Limit; a higher value is clamped to it.

Values: Protocol Size Limit: 61,440 bytes (60 KiB).

Fails with invalidRelayAddress, invalidNamespace, storage, storeTooNew.

PlatformSpelling
Swiftstatic func open(relay: RelayAddress, storage: URL, namespace: String = "default", maxPayloadBytes: UInt32? = nil) async throws -> TrueSeal
Kotlinsuspend fun open(relay: RelayAddress, storage: Storage, namespace: String = "default", maxPayloadBytes: UInt? = null): TrueSeal
TypeScriptstatic open(options: { relay: RelayAddress | string; storageDir?: string; namespace?: string; maxPayloadBytes?: number }): Promise<TrueSeal>

async close()

Stops the relay connection and every handler, and keeps Session State. After close(), every call fails with closed.

PlatformSpelling
Swiftfunc close() async
Kotlinfun close()
TypeScriptclose(): Promise<void>

RelayAddress(host: string, publicKey: string, receivePort?: int, pushPort?: int)

The typed form of a Relay Address. TrueSeal.open takes it or the string the relay CLI prints. The port is never dropped.

Values: Relay Address format: trueseal://<relay public key hex>@host[:receivePort][?push=pushPort]; Default receive port: 7700; Default push port: 7701.

PlatformSpelling
SwiftRelayAddress(host: String, publicKey: String, receivePort: Int, pushPort: Int)
KotlinRelayAddress(host: String, publicKey: String, receivePort: Int, pushPort: Int)
TypeScriptnew RelayAddress({ host, publicKey, receivePort, pushPort })

RelayAddress.parse(address: string) -> RelayAddress

Reads a Relay Address string into the typed form.

What to do. Use it to check a configured address before calling TrueSeal.open.

PlatformSpelling
SwiftRelayAddress(_ address: String) throws
KotlinRelayAddress.parse(address: String): RelayAddress
TypeScriptRelayAddress.parse(address: string): RelayAddress

Storage

Where Session State lives. Kotlin always takes an explicit location, and Storage.android(context) builds it from an Android context. Swift takes the location as a plain URL on open; TS takes it as the storageDir option.

PlatformSpelling
Swiftstorage: URL (a parameter of TrueSeal.open)
KotlinStorage.android(context) | Storage.directory(File)
TypeScriptstorageDir?: string (an option of TrueSeal.open)

State

status: GroupStatus

Where this device is in a Sync Group. Swift observes it through @Observable, Kotlin through StateFlow, and TS through the statusChanged event.

PlatformSpelling
Swiftvar status: GroupStatus
Kotlinval status: StateFlow<GroupStatus>
TypeScriptreadonly status: GroupStatus

me: Member

This device. Its id changes when the library makes a fresh identity.

PlatformSpelling
Swiftvar me: Member
Kotlinval me: Member
TypeScriptreadonly me: Member

members: Member[]

Every other member of the Sync Group, as a whole list and never a delta. It can briefly show a member leave and rejoin while a concurrent change is re-applied.

What to do. Render the whole list each time it changes.

PlatformSpelling
Swiftvar members: [Member]
Kotlinval members: StateFlow<List<Member>>
TypeScriptreadonly members: Member[]

connection: Connection

The relay connection. TS reports changes through the connectionChanged event.

PlatformSpelling
Swiftvar connection: Connection
Kotlinval connection: StateFlow<Connection>
TypeScriptreadonly connection: Connection

GroupStatus: notJoined | pendingJoin | member | leaving | destroying

The device's place in a Sync Group. destroying lasts until the relay has accepted a Revoke for every member, with no timeout.

PlatformSpelling
Swiftenum GroupStatus: .notJoined | .pendingJoin | .member | .leaving | .destroying
KotlinGroupStatus: GroupStatus.NotJoined | GroupStatus.PendingJoin | GroupStatus.Member | GroupStatus.Leaving | GroupStatus.Destroying
TypeScripttype GroupStatus: "notJoined" | "pendingJoin" | "member" | "leaving" | "destroying"

Connection: connecting | connected | disconnected | relayVersionUnsupported{min, max}

The relay connection state. relayVersionUnsupported carries the Transport Versions the relay accepts; versions are never negotiated.

PlatformSpelling
Swiftenum Connection: .connecting | .connected | .disconnected | .relayVersionUnsupported(min:max:)
KotlinConnection: Connection.Connecting | Connection.Connected | Connection.Disconnected | Connection.RelayVersionUnsupported(min, max)
TypeScripttype Connection: { state: "connecting" } | { state: "connected" } | { state: "disconnected" } | { state: "relayVersionUnsupported", min, max }

Member{id, name}

A device in the Sync Group. name is a label the device chose, never authentication.

PlatformSpelling
SwiftMember(id:name:)
KotlinMember(id, name)
TypeScriptinterface Member { id; name }

Receiving

onMessage(handler: async (Message) -> void) -> Subscription

Registers the one message handler, and registering it starts receiving. The library awaits the handler and acks the message after it returns. A throw retries the message, up to the handler attempts below, then reports handlerGaveUp. Cancelling the Subscription pauses receiving.

Values: Handler attempts: 5 attempts.

Fails with messageHandlerAlreadySet.

What to do. Finish storing the message before the handler returns. Make the handler safe to run twice for one MessageId.

PlatformSpelling
Swiftfunc onMessage(_ handler: @escaping (Message) async throws -> Void) throws -> Subscription
Kotlinfun onMessage(handler: suspend (Message) -> Unit): Subscription
TypeScriptonMessage(handler: (message: Message) => Promise<void>): () => void

Message{id: MessageId, from: Member, body: bytes}

One received message. from.id is the same id as in members. body is the bytes the sender passed to send().

PlatformSpelling
SwiftMessage(id: MessageID, from: Member, body: Data)
KotlinMessage(id: MessageId, from: Member, body: ByteArray)
TypeScriptinterface Message { id: MessageId; from: Member; body: Uint8Array }

MessageId

An opaque message id, the same on sender and receiver. The library drops duplicates by it before the handler runs.

What to do. Store it as given. Never parse or rebuild it.

PlatformSpelling
SwiftMessageID

Sending

async send(body: bytes) -> MessageId

Queues body for every other member. It returns once the message is in the durable outbox, never after delivery. Bodies are opaque bytes.

Values: Protocol Size Limit: 61,440 bytes (60 KiB).

Fails with payloadTooLarge, notMember.

What to do. Keep the returned MessageId to match a later sendFailed.

PlatformSpelling
Swiftfunc send(_ body: Data) async throws -> MessageID
Kotlinsuspend fun send(body: ByteArray): MessageId
TypeScriptsend(body: Uint8Array): Promise<MessageId>

Pairing

Pairing needs an explicit action on both devices. The admitting device calls startPairing() and accept(request), and the joining device calls join(token).

startPairing() -> string

Opens the pairing window on an admitting device and returns the Pairing Token. Join requests arrive as joinRequest events.

What to do. Show the token as a QR code or copyable text on the admitting device.

PlatformSpelling
Swiftfunc startPairing() throws -> String
Kotlinfun startPairing(): String
TypeScriptstartPairing(): string

async accept(request: JoinRequest)

Admits the device that sent request. The window closes after it.

Values: Maximum Group Size: 32 members.

Fails with pairingClosed, groupFull, manifestVersionExhausted.

What to do. Call it only after the user confirms the request comes from the device in front of them.

PlatformSpelling
Swiftfunc accept(_ request: JoinRequest) async throws
Kotlinsuspend fun accept(request: JoinRequest)
TypeScriptaccept(request: JoinRequest): Promise<void>

cancelPairing()

Closes the pairing window. There is no reject call; a request that was never accepted disappears when the window closes.

PlatformSpelling
Swiftfunc cancelPairing()
Kotlinfun cancelPairing()
TypeScriptcancelPairing(): void

JoinRequest{id, name}

A device asking to join. name is a label the device chose, never authentication.

PlatformSpelling
SwiftJoinRequest(id:name:)
KotlinJoinRequest(id, name)
TypeScriptinterface JoinRequest { id; name }

async join(token: string)

Asks to join the Sync Group behind token and moves the device to pendingJoin, which survives restarts. Once admitted, the device reports statusChanged(member, joined).

Fails with alreadyInGroup, invalidPairingToken.

PlatformSpelling
Swiftfunc join(_ token: String) async throws
Kotlinsuspend fun join(token: String)
TypeScriptjoin(token: string): Promise<void>

cancelJoin()

Moves a pendingJoin device back to notJoined.

PlatformSpelling
Swiftfunc cancelJoin()
Kotlinfun cancelJoin()
TypeScriptcancelJoin(): void

Membership

async remove(memberId: Member.id)

Removes another member. Any member may remove any other member. The removed device wipes and makes a fresh identity.

Fails with manifestVersionExhausted.

PlatformSpelling
Swiftfunc remove(_ memberId: Member.ID) async throws
Kotlinsuspend fun remove(memberId: String)
TypeScriptremove(memberId: string): Promise<void>

async leave()

Leaves the Sync Group. The device moves to leaving, then to notJoined with reason left.

Fails with manifestVersionExhausted.

PlatformSpelling
Swiftfunc leave() async throws
Kotlinsuspend fun leave()
TypeScriptleave(): Promise<void>

async destroyGroup()

Ends the Sync Group for every member. The device stays destroying until the relay has accepted a Revoke for every member, with no timeout, then moves to notJoined with reason destroyed. Each member forwards the Revoke and wipes. An offline member wipes when it reconnects within the Relay TTL ceiling.

Values: Relay TTL ceiling: 30 days.

What to do. Show destroying until statusChanged reports notJoined, then pair again.

PlatformSpelling
Swiftfunc destroyGroup() async throws
Kotlinsuspend fun destroyGroup()
TypeScriptdestroyGroup(): Promise<void>

Events and delivery issues

onEvent(listener: (event) -> void) -> Subscription

Registers an event listener. Any number may be registered. Events raised before the first listener are buffered.

PlatformSpelling
Swifttrueseal.onEvent { event in … } -> Subscription
Kotlinfun onEvent(listener: (TrueSealEvent) -> Unit): Subscription
TypeScriptonEvent(listener: (event) => void): () => void

onDeliveryIssue(listener: (issue) -> void) -> Subscription

Registers a delivery-issue listener. It is optional, and ignoring issues is safe. Every issue stays on the device; nothing goes back to the sender.

PlatformSpelling
Swifttrueseal.onDeliveryIssue { issue in … } -> Subscription
Kotlinfun onDeliveryIssue(listener: (DeliveryIssue) -> Unit): Subscription
TypeScriptonDeliveryIssue(listener: (issue) => void): () => void

Subscription.cancel()

The handle every on call returns. Cancelling it removes the handler or listener.

PlatformSpelling
TypeScript() => void (call it to unsubscribe)

Version and limits

TrueSeal.info: {sdkVersion, transportVersion, endToEndVersion}

The TrueSeal Release of this SDK and the Protocol Versions it speaks. Nothing is negotiated.

Values: TrueSeal Release: 0.6.0; Transport Version: 1; End-to-End Version: 1.

TrueSeal.maxPayloadBytes

The largest body send() accepts, in bytes.

Values: Protocol Size Limit: 61,440 bytes (60 KiB).

PlatformSpelling
KotlinTrueSeal.MAX_PAYLOAD_BYTES
TypeScriptTrueSeal.MAX_PAYLOAD_BYTES

TrueSeal.maxGroupSize

The most devices one Sync Group holds.

Values: Maximum Group Size: 32 members.

PlatformSpelling
KotlinTrueSeal.MAX_GROUP_SIZE
TypeScriptTrueSeal.MAX_GROUP_SIZE

What the API does not do

  • Conflict resolution is the app’s job. The library delivers every message and never merges them (trueseal-sync ADR-0003).
  • Application payload versioning is the app’s job. Bodies are opaque bytes, and the Transport and End-to-End Versions cover the protocol only (trueseal-sync ADR-0003, trueseal-sync ADR-0022).
  • Ordering holds per sender only (trueseal-sync ADR-0026).
  • The library sets no handler timeout (trueseal-sync ADR-0026).
  • The app never acks a message. The library acks after the handler returns (trueseal-sync ADR-0026).
  • A join request has no reject call. An unaccepted request disappears when the pairing window closes (trueseal-sync ADR-0028).
  • Events and delivery issues arrive through handlers, and the SDKs offer no native streams (trueseal-sync ADR-0028).
  • send() takes bytes only and has no text overloads (trueseal-sync ADR-0028).
  • The library never splits a message across frames (trueseal-sync ADR-0025).
  • Versions are never negotiated (trueseal-sync ADR-0022).
  • The Developer Preview has no public Rust API (trueseal-sync ADR-0030).