Errors and events
The cases are the same in Swift, Kotlin and TypeScript (trueseal-sync ADR-0028); only the spelling of the type differs.
Errors
Swift throws an enum, Kotlin a sealed exception class, and TypeScript one TrueSealError class whose code is a string union, with the fields below as typed extras.
TrueSealError
The error a failing call throws. Its case is one of the cases below, and the case fields are typed extras.
| Platform | Spelling |
|---|---|
| Swift | enum TrueSealError: Error |
| Kotlin | sealed class TrueSealException : Exception |
| TypeScript | class TrueSealError extends Error { code; max?; status? } |
invalidRelayAddress
open() was given a Relay Address it can't parse.
What to do. Fix the configured Relay Address. Copy it as the relay CLI prints it.
| Platform | Spelling |
|---|---|
| Swift | TrueSealError.invalidRelayAddress |
| Kotlin | TrueSealException.InvalidRelayAddress |
| TypeScript | TrueSealError { code: "INVALID_RELAY_ADDRESS" } |
invalidNamespace
open() was given a namespace it refuses.
What to do. Pass another namespace, or leave the default.
| Platform | Spelling |
|---|---|
| Swift | TrueSealError.invalidNamespace |
| Kotlin | TrueSealException.InvalidNamespace |
| TypeScript | TrueSealError { code: "INVALID_NAMESPACE" } |
storage
Session State can't be read or written, or a migration failed; the old store is left intact.
What to do. Check that the storage location exists and is writable, then call TrueSeal.open again. The old store is intact.
| Platform | Spelling |
|---|---|
| Swift | TrueSealError.storage |
| Kotlin | TrueSealException.Storage |
| TypeScript | TrueSealError { code: "STORAGE" } |
closed
A call on a TrueSeal object after close().
What to do. Open a new object with TrueSeal.open. A closed object stays closed.
| Platform | Spelling |
|---|---|
| Swift | TrueSealError.closed |
| Kotlin | TrueSealException.Closed |
| TypeScript | TrueSealError { code: "CLOSED" } |
notMember{status}
send() while the status is not member.
What to do. Check status before calling, and hide the send action until it is member. Offer pairing while it is notJoined or pendingJoin.
| Platform | Spelling |
|---|---|
| Swift | TrueSealError.notMember(status:) |
| Kotlin | TrueSealException.NotMember(status) |
| TypeScript | TrueSealError { code: "NOT_MEMBER", status } |
alreadyInGroup
join(token) on a device that already has a Sync Group in this namespace.
What to do. Call leave() first, or use another namespace.
| Platform | Spelling |
|---|---|
| Swift | TrueSealError.alreadyInGroup |
| Kotlin | TrueSealException.AlreadyInGroup |
| TypeScript | TrueSealError { code: "ALREADY_IN_GROUP" } |
invalidPairingToken
join(token) with a string that is not a valid Pairing Token.
What to do. Ask the user to scan or paste the token again from the admitting device.
| Platform | Spelling |
|---|---|
| Swift | TrueSealError.invalidPairingToken |
| Kotlin | TrueSealException.InvalidPairingToken |
| TypeScript | TrueSealError { code: "INVALID_PAIRING_TOKEN" } |
pairingClosed
accept(request) after the pairing window closed.
What to do. Call startPairing() again on the admitting device, and join with the new token.
| Platform | Spelling |
|---|---|
| Swift | TrueSealError.pairingClosed |
| Kotlin | TrueSealException.PairingClosed |
| TypeScript | TrueSealError { code: "PAIRING_CLOSED" } |
groupFull{max}
accept(request) when admitting would exceed the Maximum Group Size; max is that size.
What to do. Remove a member before admitting another.
| Platform | Spelling |
|---|---|
| Swift | TrueSealError.groupFull(max:) |
| Kotlin | TrueSealException.GroupFull(max) |
| TypeScript | TrueSealError { code: "GROUP_FULL", max } |
memberNotFound
A member id that names no current member.
What to do. Read members again and use an id from it.
| Platform | Spelling |
|---|---|
| Swift | TrueSealError.memberNotFound |
| Kotlin | TrueSealException.MemberNotFound |
| TypeScript | TrueSealError { code: "MEMBER_NOT_FOUND" } |
payloadTooLarge{max}
send() with a body over max, the Protocol Size Limit or the lower maxPayloadBytes passed to open(). Nothing enters the outbox.
Values: Protocol Size Limit: 61,440 bytes (60 KiB).
What to do. Send a smaller body. Split the data, or store it elsewhere and send a reference.
| Platform | Spelling |
|---|---|
| Swift | TrueSealError.payloadTooLarge(max:) |
| Kotlin | TrueSealException.PayloadTooLarge(max) |
| TypeScript | TrueSealError { code: "PAYLOAD_TOO_LARGE", max } |
messageHandlerAlreadySet
A second onMessage(handler).
What to do. Register one handler for the object's life. Cancel its Subscription before registering another.
| Platform | Spelling |
|---|---|
| Swift | TrueSealError.messageHandlerAlreadySet |
| Kotlin | TrueSealException.MessageHandlerAlreadySet |
| TypeScript | TrueSealError { code: "MESSAGE_HANDLER_ALREADY_SET" } |
storeTooNew
open() found Session State written by a newer release; the store is left unchanged.
What to do. Ship the newer release again. The store is unchanged, so nothing is lost.
| Platform | Spelling |
|---|---|
| Swift | TrueSealError.storeTooNew |
| Kotlin | TrueSealException.StoreTooNew |
| TypeScript | TrueSealError { code: "STORE_TOO_NEW" } |
manifestVersionExhausted
The Sync Group's Group Manifest version space is exhausted, so membership can no longer change; the group must pair again.
What to do. Pair the group again: every device leaves and re-pairs with fresh identity keys.
| Platform | Spelling |
|---|---|
| Swift | TrueSealError.manifestVersionExhausted |
| Kotlin | TrueSealException.ManifestVersionExhausted |
| TypeScript | TrueSealError { code: "MANIFEST_VERSION_EXHAUSTED" } |
Events
onEvent(listener) accepts any number of listeners. Events raised before the first listener is registered are buffered.
statusChanged(status, reason)
The device's status changed.
What to do. Update the UI from status. Offer pairing again on notJoined.
| Platform | Spelling |
|---|---|
| Swift | .statusChanged(status, reason) |
| Kotlin | TrueSealEvent.StatusChanged(status, reason) |
| TypeScript | { type: "statusChanged", status, reason } |
membersChanged(members)
The whole current member list, 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. Never treat one event as a join or leave.
| Platform | Spelling |
|---|---|
| Swift | .membersChanged(members) |
| Kotlin | TrueSealEvent.MembersChanged(members) |
| TypeScript | { type: "membersChanged", members } |
joinRequest(request)
A device asks to join while the pairing window is open; request is JoinRequest{id, name}.
What to do. Show name and call accept(request) only after the user confirms it is the device in front of them.
| Platform | Spelling |
|---|---|
| Swift | .joinRequest(request) |
| Kotlin | TrueSealEvent.JoinRequest(request) |
| TypeScript | { type: "joinRequest", request } |
connectionChanged(connection)
The relay connection changed.
What to do. Show the state. On relayVersionUnsupported, ask the user to update the app. Queued messages wait in the outbox.
| Platform | Spelling |
|---|---|
| Swift | .connectionChanged(connection) |
| Kotlin | TrueSealEvent.ConnectionChanged(connection) |
| TypeScript | { type: "connectionChanged", connection } |
admissionDropped(name, reason)
An admission this device issued was dropped, for example because re-applying it would exceed the Maximum Group Size. The joiner stays in Pending Join.
What to do. Tell the admitting user the device was not admitted. Remove a member, then pair again.
| Platform | Spelling |
|---|---|
| Swift | .admissionDropped(name, reason) |
| Kotlin | TrueSealEvent.AdmissionDropped(name, reason) |
| TypeScript | { type: "admissionDropped", name, reason } |
Delivery issues
onDeliveryIssue(listener) is optional, and ignoring it is safe. Every issue stays on the device; nothing is sent back to the sender.
unreadable(reason)
A message the library can't use was acknowledged and dropped.
What to do. Act on the reason. Each reason says what to do.
unreadable(malformed)Its bytes couldn't be decoded or authenticated, or it carried an unknown frame type or message tag. What to do. Nothing. Log it if you collect diagnostics.unreadable(senderOutdated)Another device sent it in an older End-to-End Version than this device supports. What to do. Tell the user one of their other devices needs an update.unreadable(expired)Its Sender Timestamp is older than the Replay Window. Replay Window: 60 days. What to do. Nothing. The data is lost on this device. If it matters, ask another device to send its current state again.unreadable(droppedWhileHeld)More newer-version messages than the Held-Blob cap were waiting for this device, and this was the oldest. Held-Blob cap: 100 Blobs per device. What to do. Ask the user to update the app now. Messages are already being lost.
| Platform | Spelling |
|---|---|
| Swift | .unreadable(reason) |
| Kotlin | DeliveryIssue.Unreadable(reason) |
| TypeScript | { type: "unreadable", reason } |
unauthorized
A message failed the pairing or membership rules, for example its sender isn't a member, and was acknowledged and dropped.
What to do. Nothing. Log it if you collect diagnostics.
| Platform | Spelling |
|---|---|
| Swift | .unauthorized |
| Kotlin | DeliveryIssue.Unauthorized |
| TypeScript | { type: "unauthorized" } |
heldForUpgrade(version)
A message in a newer End-to-End Version waits on the relay, unacknowledged, until this device upgrades.
What to do. Ask the user to update the app. After the upgrade the message is handled, out of order with the messages around it.
| Platform | Spelling |
|---|---|
| Swift | .heldForUpgrade(version) |
| Kotlin | DeliveryIssue.HeldForUpgrade(version) |
| TypeScript | { type: "heldForUpgrade", version } |
handlerGaveUp(messageId, error)
The message handler threw on every attempt, and the message was acknowledged and dropped.
Values: Handler attempts: 5 attempts.
What to do. Treat it as a bug in the handler. Log messageId and report error. If the data matters, ask the sending device to send it again.
| Platform | Spelling |
|---|---|
| Swift | .handlerGaveUp(messageId, error) |
| Kotlin | DeliveryIssue.HandlerGaveUp(messageId, error) |
| TypeScript | { type: "handlerGaveUp", messageId, error } |
sendFailed(messageId, reason)
An outbox entry was removed for good, so the recipient never gets that message. Messages sent after it still arrive.
What to do. Mark the message send() returned as failed. The library never retries it.
sendFailed(messageId, tooLarge)The relay refused it because it is over the relay's Relay Size Limit, which an Operator may set below the Protocol Size Limit. What to do. Tell the user the message wasn't delivered. Send smaller messages, or pass a matching lowermaxPayloadBytestoopen()sosend()refuses them up front.maxPayloadBytescounts the body, and the Relay Size Limit counts the sealed envelope.sendFailed(messageId, malformed)The relay refused it because it couldn't parse it. What to do. Treat it as a bug and report it.sendFailed(messageId, expired)It was aSyncentry still in the outbox after the Outbox expiry, for example because the device was offline all that time. Outbox expiry: 30 days. What to do. Tell the user it wasn't delivered, and send it again if it still matters.
| Platform | Spelling |
|---|---|
| Swift | .sendFailed(messageId, reason) |
| Kotlin | DeliveryIssue.SendFailed(messageId, reason) |
| TypeScript | { type: "sendFailed", messageId, reason } |
Value sets
| Set | Values | Meaning | Source |
|---|---|---|---|
status | notJoined, pendingJoin, member, leaving, destroying | The device's status. | trueseal-sync ADR-0028, trueseal-sync ADR-0029 |
statusReason | created, joined, left, removed, destroyed, storeReset | The reason in statusChanged. | trueseal-sync ADR-0028, trueseal-sync ADR-0032 |
connection | connecting, connected, disconnected, relayVersionUnsupported{min, max} | The device's connection. | trueseal-sync ADR-0028 |
unreadableReason | malformed, senderOutdated, expired, droppedWhileHeld | The reason in unreadable. | trueseal-sync ADR-0034 |
sendFailedReason | tooLarge, malformed, expired | The reason in sendFailed. | trueseal-sync ADR-0028, trueseal-sync ADR-0034 |
admissionDroppedReason | groupFull | The reason in admissionDropped. | trueseal-sync ADR-0028, trueseal-sync ADR-0027; code: trueseal-sync docs/shared-facts.json (generated) |