Menu 0.6.0 preview

Reference

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.

PlatformSpelling
Swiftenum TrueSealError: Error
Kotlinsealed class TrueSealException : Exception
TypeScriptclass 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.

PlatformSpelling
SwiftTrueSealError.invalidRelayAddress
KotlinTrueSealException.InvalidRelayAddress
TypeScriptTrueSealError { code: "INVALID_RELAY_ADDRESS" }

invalidNamespace

open() was given a namespace it refuses.

What to do. Pass another namespace, or leave the default.

PlatformSpelling
SwiftTrueSealError.invalidNamespace
KotlinTrueSealException.InvalidNamespace
TypeScriptTrueSealError { 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.

PlatformSpelling
SwiftTrueSealError.storage
KotlinTrueSealException.Storage
TypeScriptTrueSealError { 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.

PlatformSpelling
SwiftTrueSealError.closed
KotlinTrueSealException.Closed
TypeScriptTrueSealError { 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.

PlatformSpelling
SwiftTrueSealError.notMember(status:)
KotlinTrueSealException.NotMember(status)
TypeScriptTrueSealError { 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.

PlatformSpelling
SwiftTrueSealError.alreadyInGroup
KotlinTrueSealException.AlreadyInGroup
TypeScriptTrueSealError { 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.

PlatformSpelling
SwiftTrueSealError.invalidPairingToken
KotlinTrueSealException.InvalidPairingToken
TypeScriptTrueSealError { 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.

PlatformSpelling
SwiftTrueSealError.pairingClosed
KotlinTrueSealException.PairingClosed
TypeScriptTrueSealError { 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.

PlatformSpelling
SwiftTrueSealError.groupFull(max:)
KotlinTrueSealException.GroupFull(max)
TypeScriptTrueSealError { 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.

PlatformSpelling
SwiftTrueSealError.memberNotFound
KotlinTrueSealException.MemberNotFound
TypeScriptTrueSealError { 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.

PlatformSpelling
SwiftTrueSealError.payloadTooLarge(max:)
KotlinTrueSealException.PayloadTooLarge(max)
TypeScriptTrueSealError { 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.

PlatformSpelling
SwiftTrueSealError.messageHandlerAlreadySet
KotlinTrueSealException.MessageHandlerAlreadySet
TypeScriptTrueSealError { 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.

PlatformSpelling
SwiftTrueSealError.storeTooNew
KotlinTrueSealException.StoreTooNew
TypeScriptTrueSealError { 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.

PlatformSpelling
SwiftTrueSealError.manifestVersionExhausted
KotlinTrueSealException.ManifestVersionExhausted
TypeScriptTrueSealError { 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.

PlatformSpelling
Swift.statusChanged(status, reason)
KotlinTrueSealEvent.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.

PlatformSpelling
Swift.membersChanged(members)
KotlinTrueSealEvent.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.

PlatformSpelling
Swift.joinRequest(request)
KotlinTrueSealEvent.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.

PlatformSpelling
Swift.connectionChanged(connection)
KotlinTrueSealEvent.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.

PlatformSpelling
Swift.admissionDropped(name, reason)
KotlinTrueSealEvent.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.
PlatformSpelling
Swift.unreadable(reason)
KotlinDeliveryIssue.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.

PlatformSpelling
Swift.unauthorized
KotlinDeliveryIssue.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.

PlatformSpelling
Swift.heldForUpgrade(version)
KotlinDeliveryIssue.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.

PlatformSpelling
Swift.handlerGaveUp(messageId, error)
KotlinDeliveryIssue.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 lower maxPayloadBytes to open() so send() refuses them up front. maxPayloadBytes counts 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 a Sync entry 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.
PlatformSpelling
Swift.sendFailed(messageId, reason)
KotlinDeliveryIssue.SendFailed(messageId, reason)
TypeScript{ type: "sendFailed", messageId, reason }

Value sets

SetValuesMeaningSource
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)