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.
| Platform | Spelling |
|---|---|
| Swift | import TrueSealSync |
| Kotlin | import dev.trueseal.sync.* |
| TypeScript | import { 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.
| Platform | Spelling |
|---|---|
| Swift | @Observable class TrueSeal |
| Kotlin | class TrueSeal : Closeable |
| TypeScript | class 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.
| Platform | Spelling |
|---|---|
| Swift | static func open(relay: RelayAddress, storage: URL, namespace: String = "default", maxPayloadBytes: UInt32? = nil) async throws -> TrueSeal |
| Kotlin | suspend fun open(relay: RelayAddress, storage: Storage, namespace: String = "default", maxPayloadBytes: UInt? = null): TrueSeal |
| TypeScript | static 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.
| Platform | Spelling |
|---|---|
| Swift | func close() async |
| Kotlin | fun close() |
| TypeScript | close(): 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.
| Platform | Spelling |
|---|---|
| Swift | RelayAddress(host: String, publicKey: String, receivePort: Int, pushPort: Int) |
| Kotlin | RelayAddress(host: String, publicKey: String, receivePort: Int, pushPort: Int) |
| TypeScript | new 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.
| Platform | Spelling |
|---|---|
| Swift | RelayAddress(_ address: String) throws |
| Kotlin | RelayAddress.parse(address: String): RelayAddress |
| TypeScript | RelayAddress.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.
| Platform | Spelling |
|---|---|
| Swift | storage: URL (a parameter of TrueSeal.open) |
| Kotlin | Storage.android(context) | Storage.directory(File) |
| TypeScript | storageDir?: 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.
| Platform | Spelling |
|---|---|
| Swift | var status: GroupStatus |
| Kotlin | val status: StateFlow<GroupStatus> |
| TypeScript | readonly status: GroupStatus |
me: Member
This device. Its id changes when the library makes a fresh identity.
| Platform | Spelling |
|---|---|
| Swift | var me: Member |
| Kotlin | val me: Member |
| TypeScript | readonly 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.
| Platform | Spelling |
|---|---|
| Swift | var members: [Member] |
| Kotlin | val members: StateFlow<List<Member>> |
| TypeScript | readonly members: Member[] |
connection: Connection
The relay connection. TS reports changes through the connectionChanged event.
| Platform | Spelling |
|---|---|
| Swift | var connection: Connection |
| Kotlin | val connection: StateFlow<Connection> |
| TypeScript | readonly 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.
| Platform | Spelling |
|---|---|
| Swift | enum GroupStatus: .notJoined | .pendingJoin | .member | .leaving | .destroying |
| Kotlin | GroupStatus: GroupStatus.NotJoined | GroupStatus.PendingJoin | GroupStatus.Member | GroupStatus.Leaving | GroupStatus.Destroying |
| TypeScript | type 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.
| Platform | Spelling |
|---|---|
| Swift | enum Connection: .connecting | .connected | .disconnected | .relayVersionUnsupported(min:max:) |
| Kotlin | Connection: Connection.Connecting | Connection.Connected | Connection.Disconnected | Connection.RelayVersionUnsupported(min, max) |
| TypeScript | type 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.
| Platform | Spelling |
|---|---|
| Swift | Member(id:name:) |
| Kotlin | Member(id, name) |
| TypeScript | interface 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.
| Platform | Spelling |
|---|---|
| Swift | func onMessage(_ handler: @escaping (Message) async throws -> Void) throws -> Subscription |
| Kotlin | fun onMessage(handler: suspend (Message) -> Unit): Subscription |
| TypeScript | onMessage(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().
| Platform | Spelling |
|---|---|
| Swift | Message(id: MessageID, from: Member, body: Data) |
| Kotlin | Message(id: MessageId, from: Member, body: ByteArray) |
| TypeScript | interface 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.
| Platform | Spelling |
|---|---|
| Swift | MessageID |
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.
| Platform | Spelling |
|---|---|
| Swift | func send(_ body: Data) async throws -> MessageID |
| Kotlin | suspend fun send(body: ByteArray): MessageId |
| TypeScript | send(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.
| Platform | Spelling |
|---|---|
| Swift | func startPairing() throws -> String |
| Kotlin | fun startPairing(): String |
| TypeScript | startPairing(): 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.
| Platform | Spelling |
|---|---|
| Swift | func accept(_ request: JoinRequest) async throws |
| Kotlin | suspend fun accept(request: JoinRequest) |
| TypeScript | accept(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.
| Platform | Spelling |
|---|---|
| Swift | func cancelPairing() |
| Kotlin | fun cancelPairing() |
| TypeScript | cancelPairing(): void |
JoinRequest{id, name}
A device asking to join. name is a label the device chose, never authentication.
| Platform | Spelling |
|---|---|
| Swift | JoinRequest(id:name:) |
| Kotlin | JoinRequest(id, name) |
| TypeScript | interface 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.
| Platform | Spelling |
|---|---|
| Swift | func join(_ token: String) async throws |
| Kotlin | suspend fun join(token: String) |
| TypeScript | join(token: string): Promise<void> |
cancelJoin()
Moves a pendingJoin device back to notJoined.
| Platform | Spelling |
|---|---|
| Swift | func cancelJoin() |
| Kotlin | fun cancelJoin() |
| TypeScript | cancelJoin(): 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.
| Platform | Spelling |
|---|---|
| Swift | func remove(_ memberId: Member.ID) async throws |
| Kotlin | suspend fun remove(memberId: String) |
| TypeScript | remove(memberId: string): Promise<void> |
async leave()
Leaves the Sync Group. The device moves to leaving, then to notJoined with reason left.
Fails with manifestVersionExhausted.
| Platform | Spelling |
|---|---|
| Swift | func leave() async throws |
| Kotlin | suspend fun leave() |
| TypeScript | leave(): 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.
| Platform | Spelling |
|---|---|
| Swift | func destroyGroup() async throws |
| Kotlin | suspend fun destroyGroup() |
| TypeScript | destroyGroup(): 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.
| Platform | Spelling |
|---|---|
| Swift | trueseal.onEvent { event in … } -> Subscription |
| Kotlin | fun onEvent(listener: (TrueSealEvent) -> Unit): Subscription |
| TypeScript | onEvent(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.
| Platform | Spelling |
|---|---|
| Swift | trueseal.onDeliveryIssue { issue in … } -> Subscription |
| Kotlin | fun onDeliveryIssue(listener: (DeliveryIssue) -> Unit): Subscription |
| TypeScript | onDeliveryIssue(listener: (issue) => void): () => void |
Subscription.cancel()
The handle every on call returns. Cancelling it removes the handler or listener.
| Platform | Spelling |
|---|---|
| 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).
| Platform | Spelling |
|---|---|
| Kotlin | TrueSeal.MAX_PAYLOAD_BYTES |
| TypeScript | TrueSeal.MAX_PAYLOAD_BYTES |
TrueSeal.maxGroupSize
The most devices one Sync Group holds.
Values: Maximum Group Size: 32 members.
| Platform | Spelling |
|---|---|
| Kotlin | TrueSeal.MAX_GROUP_SIZE |
| TypeScript | TrueSeal.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).