Skip to content

feat(device-link): let iOS link a desktop through SharedCore - #1850

Closed
bmc08gt wants to merge 4 commits into
feat/desktop-spike-a3from
feat/desktop-spike-ios-link
Closed

bmc08gt wants to merge 4 commits into
feat/desktop-spike-a3from
feat/desktop-spike-ios-link

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator

Lets iOS drive the phone side of linking a desktop, through SharedCore. Stacked on #1849.

Split. The protocol, crypto, LinkInvite, PhoneLinker and PhoneLinkController move to a new UI-free :libs:device-link (commonMain only, flipcash.kmp.library). The Compose screens, SeedVault and the keyring vault stay in :apps:flipcash:shared:device-link, which depends on the core with api. Package names didn't change, so the desktop and Android call sites are untouched.

Targets. The core builds for android, jvm, iosArm64, iosSimulatorArm64, iosX64, and the macOS targets the plugin adds. ktor-network 3.4.1 and ionspin libsodium 0.9.5 both resolve for the Apple targets. Nothing in the core needed changing for Kotlin/Native.

Tests. LinkProtocolTest moved to commonTest and runs over real ktor loopback sockets on jvm and iosSimulatorArm64. DeviceLinkVectorTest checks the code, session keys and sealed seed against device_link.json, generated independently with PyNaCl in the orchestrator's test-vectors/. Like chat-cipher, the android target has no host test: the libsodium .so doesn't load on a host JVM.

iOS API. PhoneLinkSession wraps the controller for Swift with a state callback instead of a flow and suspend functions:

PhoneLinkSession(onState: (PhoneLinkControllerState) -> Void)
func scanned(text: String) -> Bool
func confirm(entropyB64: String)
func cancel()
func close()

The seed is an argument to confirm, since iOS already holds it, and is kept only until the controller reads it after the user confirms. PhoneLinker is now internal, so no ktor or Result types reach SharedCore.h. SharedCoreKit adds SharedDeviceLink, which maps the states to a Swift enum on the main actor, and DeviceLinkTests against the same fixture through DeviceLinkVectors.

…vice-link

iOS needs the protocol through SharedCore, which can't take Compose or :ui:theme. The protocol,
crypto, invite, PhoneLinker and PhoneLinkController move as-is, same package, so callers in
apps/desktop and features/myaccount don't change.

:apps:flipcash:shared:device-link keeps the screens, SeedVault and the keyring store. SeedVault
isn't UI, but it is desktop storage; iOS passes its seed in rather than reading a vault.

The fake-vault test moves with SeedVault into its own file.
LinkProtocolTest moves from jvmTest to commonTest and now also runs on
iosSimulatorArm64 (7/7, real loopback sockets and the native libsodium).
Android gets a device-test source set instead of a host test, because
libsodium's Android binding loads its .so from the APK; chat-cipher has the
same limit. shared-core-tests runs the iOS target.
device_link.json (canonical copy in the orchestrator's test-vectors/, from
gen_device_link.py over PyNaCl) fixes keys, secret, transcript, both session
keys, the six-digit code, and the sealed seed and ack frames for three
cases, plus tampered seeds and bad invites. DeviceLinkVectorTest reproduces
all of it on jvm and iosSimulatorArm64. LinkCrypto.seal gains an internal
fixed-nonce overload for the test; production still draws a fresh nonce.
PhoneLinkSession wraps PhoneLinkController for Swift: scanned(text:), confirm(entropyB64:),
cancel(), close(), and an onState callback instead of a StateFlow and suspend functions. The
seed is an argument to confirm, since iOS already holds it, and is kept only until the
controller reads it after the user confirms the codes. PhoneLinker is now internal, so no ktor
or Result types reach SharedCore.h.

SharedCoreKit adds SharedDeviceLink, which maps the Kotlin states to a Swift enum on the main
actor, and DeviceLinkTests, which check the code and seed opening against device_link.json
through DeviceLinkVectors.
@bmc08gt bmc08gt self-assigned this Oct 24, 2026
@github-actions github-actions Bot added type: feature New functionality area: build-system Gradle, convention plugins, build-logic and removed type: feature New functionality labels Oct 24, 2026
@bmc08gt

bmc08gt commented Oct 30, 2026

Copy link
Copy Markdown
Collaborator Author

Landed in #1894 as one squash against code/cash.

@bmc08gt bmc08gt closed this Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: build-system Gradle, convention plugins, build-logic

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant