Swift SDK
trueseal-sync-swift wraps the trueseal-sync Rust core via UniFFI. It targets iOS and macOS and is distributed as a Swift package.
Structure
The SDK exposes one public module: TrueSealSync. All FFI types are hidden behind the internal TrueSealSyncBindings module. Never import TrueSealSyncBindings directly from app code — use TrueSealSync only.
Construction
import TrueSealSync
let client = try TrueSealSyncClient(
relayURL: URL(string: "tcp://relay-host:7700")!,
relayPublicKey: Data([/* 32-byte X25519 key — see relay setup */]),
storageDirectory: appScopedStorageURL,
namespace: "default"
)
relayPublicKey is a build-time constant. Decode the 64-char hex string from relay -genkey into 32 bytes:
func hexToData(_ hex: String) -> Data {
var data = Data()
var index = hex.startIndex
while index < hex.endIndex {
let next = hex.index(index, offsetBy: 2)
data.append(UInt8(hex[index..<next], radix: 16)!)
index = next
}
return data
}
let relayPublicKey = hexToData("your64charhexstring...")
Async streams
All event streams are AsyncStream values. Consume them on Task instances. Bridge to @MainActor / @Published for UI updates.
// Connection state
Task {
for await state in client.connectionState {
await MainActor.run {
self.isRelayConnected = (state == .connected)
}
}
}
// Incoming blobs
Task {
for await blob in client.blobs {
await MainActor.run {
self.handleIncoming(blob.data, from: blob.senderNoisePub)
}
}
}
// Member events
Task {
for await event in client.memberEvents {
await MainActor.run { self.handleMemberEvent(event) }
}
}
// Pairing requests
Task {
for await request in client.pairingRequests {
await MainActor.run { self.showPairingRequest(request) }
}
}
Key methods
// Identity
client.localDeviceName // String — e.g. "FreeMap"
client.localNodeId // String — e.g. "aB3xK9qR2mN"
// Members
client.members // [SyncMember] — current manifest snapshot (excludes local device)
// Pairing
client.generatePairingToken() // String — stable base64url token
client.joinGroup(token: String) throws
client.acceptPairingRequest(_ r: PairingRequest)
client.cancelPairing()
// Sync
client.publish(text: String) async throws
client.publish(blob: Data) async throws
// Group management
client.removeMember(_ member: SyncMember) throws
client.destroyGroup()
localDeviceName and localNodeId are synchronous computed properties — safe to call on any thread.
Entitlements
Outbound TCP connections require the network client entitlement on both macOS and iOS. Without it the relay connection silently fails.
<!-- YourApp.entitlements -->
<key>com.apple.security.network.client</key>
<true/>
iOS: background connectivity
On iOS, TCP connections are suspended when the app backgrounds:
connectionStatewill emit.disconnectedon background- The outbox replays automatically on next foreground + reconnection
- Do not surface
RELAY: OFFLINEas a user-facing error while the app is backgrounded
If you need a short window to finish an in-flight publish before suspension:
var backgroundTask: UIBackgroundTaskIdentifier = .invalid
func applicationDidEnterBackground(_ application: UIApplication) {
backgroundTask = application.beginBackgroundTask {
application.endBackgroundTask(self.backgroundTask)
}
}
macOS: menu bar apps
For LSUIElement = YES menu bar apps that also show a main window:
// When the main window opens
NSApp.setActivationPolicy(.regular)
// When the main window closes
NSApp.setActivationPolicy(.accessory)
This gives a Dock icon only while the window is visible — correct behaviour for a menu bar utility.
Non-blocking sockets
If building trueseal-sync from source, ensure the Rust TCP transport factories have non-blocking mode enabled. With blocking sockets the connection mutex is held during read(), starving concurrent sends — this manifests as pairing messages that are ack’d by the relay but never received by the host.
The fix is set_nonblocking(true) on both transport factory implementations in ffi.rs. The distributed xcframework has this applied.