Implement Apple Watch TOTP interface (#57)

This commit is contained in:
2026-08-16 14:38:16 +02:00
parent df1d49339c
commit e54d91a83a
6 changed files with 789 additions and 126 deletions

View File

@@ -398,7 +398,7 @@ private func uniffiTraitInterfaceCallWithError<T, E>(
callStatus.pointee.errorBuf = FfiConverterString.lower(String(describing: error))
}
}
// Initial value and increment amount for handles.
// Initial value and increment amount for handles.
// These ensure that SWIFT handles always have the lowest bit set
fileprivate let UNIFFI_HANDLEMAP_INITIAL: UInt64 = 1
fileprivate let UNIFFI_HANDLEMAP_DELTA: UInt64 = 2
@@ -562,15 +562,23 @@ fileprivate struct FfiConverterData: FfiConverterRustBuffer {
public protocol WatchCoreProtocol: AnyObject, Sendable {
func applySnapshot(snapshot: Data) throws -> WatchSnapshotUpdate
func noPersistedSnapshot() throws
func protectedDataUnavailable() throws
func recordsAt(unixSeconds: UInt64) throws -> [WatchTotpRecord]
func noPersistedSnapshot() throws
func presentationAt(unixSeconds: UInt64) throws -> WatchPresentation
func protectedDataUnavailable() throws
func syncFailed() throws
func syncFinished() throws
func syncStarted() throws
func syncUnavailable() throws
}
open class WatchCore: WatchCoreProtocol, @unchecked Sendable {
fileprivate let handle: UInt64
@@ -622,9 +630,9 @@ open class WatchCore: WatchCoreProtocol, @unchecked Sendable {
try! rustCall { uniffi_ironstorage_watch_fn_free_watchcore(handle, $0) }
}
open func applySnapshot(snapshot: Data)throws -> WatchSnapshotUpdate {
return try FfiConverterTypeWatchSnapshotUpdate_lift(try rustCallWithError(FfiConverterTypeWatchFfiError_lift) {
uniffiCallStatus in
@@ -634,7 +642,7 @@ open func applySnapshot(snapshot: Data)throws -> WatchSnapshotUpdate {
)
})
}
open func noPersistedSnapshot()throws {try rustCallWithError(FfiConverterTypeWatchFfiError_lift) {
uniffiCallStatus in
uniffi_ironstorage_watch_fn_method_watchcore_no_persisted_snapshot(
@@ -642,7 +650,17 @@ open func noPersistedSnapshot()throws {try rustCallWithError(FfiConverterTypeW
)
}
}
open func presentationAt(unixSeconds: UInt64)throws -> WatchPresentation {
return try FfiConverterTypeWatchPresentation_lift(try rustCallWithError(FfiConverterTypeWatchFfiError_lift) {
uniffiCallStatus in
uniffi_ironstorage_watch_fn_method_watchcore_presentation_at(
self.uniffiCloneHandle(),
FfiConverterUInt64.lower(unixSeconds),uniffiCallStatus
)
})
}
open func protectedDataUnavailable()throws {try rustCallWithError(FfiConverterTypeWatchFfiError_lift) {
uniffiCallStatus in
uniffi_ironstorage_watch_fn_method_watchcore_protected_data_unavailable(
@@ -650,19 +668,41 @@ open func protectedDataUnavailable()throws {try rustCallWithError(FfiConverter
)
}
}
open func recordsAt(unixSeconds: UInt64)throws -> [WatchTotpRecord] {
return try FfiConverterSequenceTypeWatchTotpRecord.lift(try rustCallWithError(FfiConverterTypeWatchFfiError_lift) {
uniffiCallStatus in
uniffi_ironstorage_watch_fn_method_watchcore_records_at(
self.uniffiCloneHandle(),
FfiConverterUInt64.lower(unixSeconds),uniffiCallStatus
)
})
}
open func syncFailed()throws {try rustCallWithError(FfiConverterTypeWatchFfiError_lift) {
uniffiCallStatus in
uniffi_ironstorage_watch_fn_method_watchcore_sync_failed(
self.uniffiCloneHandle(),uniffiCallStatus
)
}
}
open func syncFinished()throws {try rustCallWithError(FfiConverterTypeWatchFfiError_lift) {
uniffiCallStatus in
uniffi_ironstorage_watch_fn_method_watchcore_sync_finished(
self.uniffiCloneHandle(),uniffiCallStatus
)
}
}
open func syncStarted()throws {try rustCallWithError(FfiConverterTypeWatchFfiError_lift) {
uniffiCallStatus in
uniffi_ironstorage_watch_fn_method_watchcore_sync_started(
self.uniffiCloneHandle(),uniffiCallStatus
)
}
}
open func syncUnavailable()throws {try rustCallWithError(FfiConverterTypeWatchFfiError_lift) {
uniffiCallStatus in
uniffi_ironstorage_watch_fn_method_watchcore_sync_unavailable(
self.uniffiCloneHandle(),uniffiCallStatus
)
}
}
}
@@ -709,6 +749,68 @@ public func FfiConverterTypeWatchCore_lower(_ value: WatchCore) -> UInt64 {
public struct WatchPresentation: Equatable, Hashable {
public var state: WatchPresentationState
public var title: String
public var detail: String
public var records: [WatchTotpRecord]
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(state: WatchPresentationState, title: String, detail: String, records: [WatchTotpRecord]) {
self.state = state
self.title = title
self.detail = detail
self.records = records
}
}
#if compiler(>=6)
extension WatchPresentation: Sendable {}
#endif
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct FfiConverterTypeWatchPresentation: FfiConverterRustBuffer {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WatchPresentation {
return
try WatchPresentation(
state: FfiConverterTypeWatchPresentationState.read(from: &buf),
title: FfiConverterString.read(from: &buf),
detail: FfiConverterString.read(from: &buf),
records: FfiConverterSequenceTypeWatchTotpRecord.read(from: &buf)
)
}
public static func write(_ value: WatchPresentation, into buf: inout [UInt8]) {
FfiConverterTypeWatchPresentationState.write(value.state, into: &buf)
FfiConverterString.write(value.title, into: &buf)
FfiConverterString.write(value.detail, into: &buf)
FfiConverterSequenceTypeWatchTotpRecord.write(value.records, into: &buf)
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeWatchPresentation_lift(_ buf: RustBuffer) throws -> WatchPresentation {
return try FfiConverterTypeWatchPresentation.lift(buf)
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeWatchPresentation_lower(_ value: WatchPresentation) -> RustBuffer {
return FfiConverterTypeWatchPresentation.lower(value)
}
public struct WatchSnapshotUpdate: Equatable, Hashable {
public var apply: WatchSnapshotApply
public var persistence: WatchPersistenceAction
@@ -726,9 +828,9 @@ public struct WatchSnapshotUpdate: Equatable, Hashable {
self.receipt = receipt
}
}
#if compiler(>=6)
@@ -742,10 +844,10 @@ public struct FfiConverterTypeWatchSnapshotUpdate: FfiConverterRustBuffer {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WatchSnapshotUpdate {
return
try WatchSnapshotUpdate(
apply: FfiConverterTypeWatchSnapshotApply.read(from: &buf),
persistence: FfiConverterTypeWatchPersistenceAction.read(from: &buf),
revision: FfiConverterOptionUInt64.read(from: &buf),
selectedEntries: FfiConverterUInt32.read(from: &buf),
apply: FfiConverterTypeWatchSnapshotApply.read(from: &buf),
persistence: FfiConverterTypeWatchPersistenceAction.read(from: &buf),
revision: FfiConverterOptionUInt64.read(from: &buf),
selectedEntries: FfiConverterUInt32.read(from: &buf),
receipt: FfiConverterData.read(from: &buf)
)
}
@@ -782,21 +884,23 @@ public struct WatchTotpRecord: Equatable, Hashable {
public var code: String
public var period: UInt64
public var validUntil: UInt64
public var remaining: UInt64
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(path: String, issuer: String?, account: String, code: String, period: UInt64, validUntil: UInt64) {
public init(path: String, issuer: String?, account: String, code: String, period: UInt64, validUntil: UInt64, remaining: UInt64) {
self.path = path
self.issuer = issuer
self.account = account
self.code = code
self.period = period
self.validUntil = validUntil
self.remaining = remaining
}
}
#if compiler(>=6)
@@ -810,12 +914,13 @@ public struct FfiConverterTypeWatchTotpRecord: FfiConverterRustBuffer {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WatchTotpRecord {
return
try WatchTotpRecord(
path: FfiConverterString.read(from: &buf),
issuer: FfiConverterOptionString.read(from: &buf),
account: FfiConverterString.read(from: &buf),
code: FfiConverterString.read(from: &buf),
period: FfiConverterUInt64.read(from: &buf),
validUntil: FfiConverterUInt64.read(from: &buf)
path: FfiConverterString.read(from: &buf),
issuer: FfiConverterOptionString.read(from: &buf),
account: FfiConverterString.read(from: &buf),
code: FfiConverterString.read(from: &buf),
period: FfiConverterUInt64.read(from: &buf),
validUntil: FfiConverterUInt64.read(from: &buf),
remaining: FfiConverterUInt64.read(from: &buf)
)
}
@@ -826,6 +931,7 @@ public struct FfiConverterTypeWatchTotpRecord: FfiConverterRustBuffer {
FfiConverterString.write(value.code, into: &buf)
FfiConverterUInt64.write(value.period, into: &buf)
FfiConverterUInt64.write(value.validUntil, into: &buf)
FfiConverterUInt64.write(value.remaining, into: &buf)
}
}
@@ -845,23 +951,23 @@ public func FfiConverterTypeWatchTotpRecord_lower(_ value: WatchTotpRecord) -> R
}
public
public
enum WatchFfiError: Swift.Error, Equatable, Hashable, Foundation.LocalizedError {
case Failed(message: String
)
public var errorDescription: String? {
String(reflecting: self)
}
}
#if compiler(>=6)
@@ -878,9 +984,9 @@ public struct FfiConverterTypeWatchFfiError: FfiConverterRustBuffer {
let variant: Int32 = try readInt(&buf)
switch variant {
case 1: return .Failed(
message: try FfiConverterString.read(from: &buf)
)
@@ -892,14 +998,14 @@ public struct FfiConverterTypeWatchFfiError: FfiConverterRustBuffer {
public static func write(_ value: WatchFfiError, into buf: inout [UInt8]) {
switch value {
case let .Failed(message):
writeInt(&buf, Int32(1))
FfiConverterString.write(message, into: &buf)
}
}
}
@@ -922,7 +1028,7 @@ public func FfiConverterTypeWatchFfiError_lower(_ value: WatchFfiError) -> RustB
public enum WatchPersistenceAction: Equatable, Hashable {
case keep
case replace
case delete
@@ -946,32 +1052,32 @@ public struct FfiConverterTypeWatchPersistenceAction: FfiConverterRustBuffer {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WatchPersistenceAction {
let variant: Int32 = try readInt(&buf)
switch variant {
case 1: return .keep
case 2: return .replace
case 3: return .delete
default: throw UniffiInternalError.unexpectedEnumCase
}
}
public static func write(_ value: WatchPersistenceAction, into buf: inout [UInt8]) {
switch value {
case .keep:
writeInt(&buf, Int32(1))
case .replace:
writeInt(&buf, Int32(2))
case .delete:
writeInt(&buf, Int32(3))
}
}
}
@@ -994,8 +1100,109 @@ public func FfiConverterTypeWatchPersistenceAction_lower(_ value: WatchPersisten
public enum WatchPresentationState: Equatable, Hashable {
case ready
case empty
case syncing
case stale
case locked
case unavailable
case error
}
#if compiler(>=6)
extension WatchPresentationState: Sendable {}
#endif
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct FfiConverterTypeWatchPresentationState: FfiConverterRustBuffer {
typealias SwiftType = WatchPresentationState
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WatchPresentationState {
let variant: Int32 = try readInt(&buf)
switch variant {
case 1: return .ready
case 2: return .empty
case 3: return .syncing
case 4: return .stale
case 5: return .locked
case 6: return .unavailable
case 7: return .error
default: throw UniffiInternalError.unexpectedEnumCase
}
}
public static func write(_ value: WatchPresentationState, into buf: inout [UInt8]) {
switch value {
case .ready:
writeInt(&buf, Int32(1))
case .empty:
writeInt(&buf, Int32(2))
case .syncing:
writeInt(&buf, Int32(3))
case .stale:
writeInt(&buf, Int32(4))
case .locked:
writeInt(&buf, Int32(5))
case .unavailable:
writeInt(&buf, Int32(6))
case .error:
writeInt(&buf, Int32(7))
}
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeWatchPresentationState_lift(_ buf: RustBuffer) throws -> WatchPresentationState {
return try FfiConverterTypeWatchPresentationState.lift(buf)
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeWatchPresentationState_lower(_ value: WatchPresentationState) -> RustBuffer {
return FfiConverterTypeWatchPresentationState.lower(value)
}
public enum WatchSnapshotApply: Equatable, Hashable {
case replaced
case revoked
case duplicate
@@ -1021,44 +1228,44 @@ public struct FfiConverterTypeWatchSnapshotApply: FfiConverterRustBuffer {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WatchSnapshotApply {
let variant: Int32 = try readInt(&buf)
switch variant {
case 1: return .replaced
case 2: return .revoked
case 3: return .duplicate
case 4: return .stale
case 5: return .pairingChanged
default: throw UniffiInternalError.unexpectedEnumCase
}
}
public static func write(_ value: WatchSnapshotApply, into buf: inout [UInt8]) {
switch value {
case .replaced:
writeInt(&buf, Int32(1))
case .revoked:
writeInt(&buf, Int32(2))
case .duplicate:
writeInt(&buf, Int32(3))
case .stale:
writeInt(&buf, Int32(4))
case .pairingChanged:
writeInt(&buf, Int32(5))
}
}
}
@@ -1183,10 +1390,22 @@ private let initializationResult: InitializationResult = {
if (uniffi_ironstorage_watch_checksum_method_watchcore_no_persisted_snapshot() != 32214) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_watch_checksum_method_watchcore_presentation_at() != 27053) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_watch_checksum_method_watchcore_protected_data_unavailable() != 42217) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_watch_checksum_method_watchcore_records_at() != 48946) {
if (uniffi_ironstorage_watch_checksum_method_watchcore_sync_failed() != 27399) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_watch_checksum_method_watchcore_sync_finished() != 42146) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_watch_checksum_method_watchcore_sync_started() != 37956) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_watch_checksum_method_watchcore_sync_unavailable() != 38367) {
return InitializationResult.apiChecksumMismatch
}

View File

@@ -263,20 +263,40 @@ RustBuffer uniffi_ironstorage_watch_fn_method_watchcore_apply_snapshot(uint64_t
void uniffi_ironstorage_watch_fn_method_watchcore_no_persisted_snapshot(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_PRESENTATION_AT
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_PRESENTATION_AT
RustBuffer uniffi_ironstorage_watch_fn_method_watchcore_presentation_at(uint64_t ptr, uint64_t unix_seconds, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_PROTECTED_DATA_UNAVAILABLE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_PROTECTED_DATA_UNAVAILABLE
void uniffi_ironstorage_watch_fn_method_watchcore_protected_data_unavailable(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_RECORDS_AT
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_RECORDS_AT
RustBuffer uniffi_ironstorage_watch_fn_method_watchcore_records_at(uint64_t ptr, uint64_t unix_seconds, RustCallStatus *_Nonnull out_status
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_SYNC_FAILED
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_SYNC_FAILED
void uniffi_ironstorage_watch_fn_method_watchcore_sync_failed(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_SYNC_FINISHED
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_SYNC_FINISHED
void uniffi_ironstorage_watch_fn_method_watchcore_sync_finished(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_SYNC_STARTED
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_SYNC_STARTED
void uniffi_ironstorage_watch_fn_method_watchcore_sync_started(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_SYNC_UNAVAILABLE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_SYNC_UNAVAILABLE
void uniffi_ironstorage_watch_fn_method_watchcore_sync_unavailable(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_FUNC_WATCH_CORE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_FUNC_WATCH_CORE
uint64_t uniffi_ironstorage_watch_fn_func_watch_core(RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUSTBUFFER_ALLOC
@@ -542,37 +562,61 @@ void ffi_ironstorage_watch_rust_future_complete_void(uint64_t handle, RustCallSt
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_FUNC_WATCH_CORE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_FUNC_WATCH_CORE
uint16_t uniffi_ironstorage_watch_checksum_func_watch_core(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_APPLY_SNAPSHOT
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_APPLY_SNAPSHOT
uint16_t uniffi_ironstorage_watch_checksum_method_watchcore_apply_snapshot(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_NO_PERSISTED_SNAPSHOT
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_NO_PERSISTED_SNAPSHOT
uint16_t uniffi_ironstorage_watch_checksum_method_watchcore_no_persisted_snapshot(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_PRESENTATION_AT
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_PRESENTATION_AT
uint16_t uniffi_ironstorage_watch_checksum_method_watchcore_presentation_at(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_PROTECTED_DATA_UNAVAILABLE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_PROTECTED_DATA_UNAVAILABLE
uint16_t uniffi_ironstorage_watch_checksum_method_watchcore_protected_data_unavailable(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_RECORDS_AT
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_RECORDS_AT
uint16_t uniffi_ironstorage_watch_checksum_method_watchcore_records_at(void
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_SYNC_FAILED
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_SYNC_FAILED
uint16_t uniffi_ironstorage_watch_checksum_method_watchcore_sync_failed(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_SYNC_FINISHED
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_SYNC_FINISHED
uint16_t uniffi_ironstorage_watch_checksum_method_watchcore_sync_finished(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_SYNC_STARTED
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_SYNC_STARTED
uint16_t uniffi_ironstorage_watch_checksum_method_watchcore_sync_started(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_SYNC_UNAVAILABLE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_SYNC_UNAVAILABLE
uint16_t uniffi_ironstorage_watch_checksum_method_watchcore_sync_unavailable(void
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_UNIFFI_CONTRACT_VERSION
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_UNIFFI_CONTRACT_VERSION
uint32_t ffi_ironstorage_watch_uniffi_contract_version(void
);
#endif

View File

@@ -1,3 +1,4 @@
import Combine
import Security
import SwiftUI
import WatchConnectivity
@@ -64,15 +65,21 @@ private final class WatchSnapshotTransport: NSObject, ObservableObject, WCSessio
private static let snapshotKey = "de.rfc1437.ironstorage.watch.snapshot"
private static let receiptKey = "de.rfc1437.ironstorage.watch.delivered"
@Published private(set) var records: [WatchTotpRecord] = []
@Published private(set) var presentation: WatchPresentation?
private let core = watchCore()
override init() {
super.init()
restoreSnapshot()
guard WCSession.isSupported() else { return }
guard WCSession.isSupported() else {
try? core.syncUnavailable()
refreshPresentation()
return
}
let session = WCSession.default
session.delegate = self
try? core.syncStarted()
refreshPresentation()
session.activate()
}
@@ -82,7 +89,7 @@ private final class WatchSnapshotTransport: NSObject, ObservableObject, WCSessio
func sceneBecameInactive() {
try? core.protectedDataUnavailable()
records.removeAll(keepingCapacity: false)
refreshPresentation()
}
private func restoreSnapshot() {
@@ -94,15 +101,20 @@ private final class WatchSnapshotTransport: NSObject, ObservableObject, WCSessio
} else {
try core.noPersistedSnapshot()
}
refreshRecords()
refreshPresentation()
} catch {
try? core.protectedDataUnavailable()
records.removeAll(keepingCapacity: false)
try? core.syncFailed()
refreshPresentation()
}
}
private func receive(_ applicationContext: [String: Any], session: WCSession) {
guard var snapshot = applicationContext[Self.snapshotKey] as? Data else { return }
try? core.syncStarted()
guard var snapshot = applicationContext[Self.snapshotKey] as? Data else {
try? core.syncFinished()
refreshPresentation()
return
}
defer { snapshot.resetBytes(in: 0..<snapshot.count) }
do {
let update = try core.applySnapshot(snapshot: snapshot)
@@ -114,7 +126,7 @@ private final class WatchSnapshotTransport: NSObject, ObservableObject, WCSessio
case .delete:
try SecureSnapshotStore.delete()
}
refreshRecords()
refreshPresentation()
guard !update.receipt.isEmpty else { return }
let acknowledgement = [Self.receiptKey: update.receipt]
if session.isReachable {
@@ -125,15 +137,15 @@ private final class WatchSnapshotTransport: NSObject, ObservableObject, WCSessio
session.transferUserInfo(acknowledgement)
}
} catch {
try? core.noPersistedSnapshot()
records.removeAll(keepingCapacity: false)
try? core.syncFailed()
refreshPresentation()
}
}
private func refreshRecords() {
records = (try? core.recordsAt(
unixSeconds: UInt64(Date().timeIntervalSince1970)
)) ?? []
func refreshPresentation(at date: Date = .now) {
presentation = try? core.presentationAt(
unixSeconds: UInt64(date.timeIntervalSince1970)
)
}
nonisolated func session(
@@ -141,9 +153,14 @@ private final class WatchSnapshotTransport: NSObject, ObservableObject, WCSessio
activationDidCompleteWith activationState: WCSessionActivationState,
error: Error?
) {
guard activationState == .activated, error == nil else { return }
Task { @MainActor [weak self] in
self?.receive(session.receivedApplicationContext, session: session)
guard let self else { return }
guard activationState == .activated, error == nil else {
try? self.core.syncUnavailable()
self.refreshPresentation()
return
}
self.receive(session.receivedApplicationContext, session: session)
}
}
@@ -157,6 +174,131 @@ private final class WatchSnapshotTransport: NSObject, ObservableObject, WCSessio
}
}
private struct WatchRootView: View {
@ObservedObject var transport: WatchSnapshotTransport
private let clock = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View {
NavigationStack {
if let presentation = transport.presentation {
if presentation.records.isEmpty {
WatchStateView(presentation: presentation)
} else {
List {
if presentation.state == .stale {
WatchStatusRow(presentation: presentation)
}
ForEach(presentation.records, id: \.path) { record in
NavigationLink {
WatchTotpDetail(path: record.path, transport: transport)
} label: {
VStack(alignment: .leading, spacing: 2) {
Text(record.issuer ?? record.account)
.font(.headline)
Text(record.account)
.font(.caption)
.foregroundStyle(.secondary)
Text(record.code)
.font(.system(.title3, design: .rounded, weight: .semibold))
.monospacedDigit()
.privacySensitive()
}
}
}
}
.navigationTitle(presentation.title)
}
}
}
.onReceive(clock) { transport.refreshPresentation(at: $0) }
}
}
private struct WatchTotpDetail: View {
let path: String
@ObservedObject var transport: WatchSnapshotTransport
private var record: WatchTotpRecord? {
transport.presentation?.records.first { $0.path == path }
}
var body: some View {
if let record {
ScrollView {
VStack(spacing: 8) {
Text(record.issuer ?? record.account)
.font(.headline)
.multilineTextAlignment(.center)
Text(record.account)
.font(.caption)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
Text(record.code)
.font(.system(.largeTitle, design: .rounded, weight: .bold))
.monospacedDigit()
.minimumScaleFactor(0.7)
.lineLimit(1)
.privacySensitive()
ProgressView(
value: Double(record.period - min(record.remaining, record.period)),
total: Double(record.period)
)
.accessibilityLabel("Code validity")
.accessibilityValue("\(record.remaining) seconds remaining")
Text("\(record.remaining)s")
.font(.caption.monospacedDigit())
.foregroundStyle(.secondary)
}
.frame(maxWidth: .infinity)
}
.navigationTitle(record.account)
} else if let presentation = transport.presentation {
WatchStateView(presentation: presentation)
}
}
}
private struct WatchStateView: View {
let presentation: WatchPresentation
var body: some View {
ContentUnavailableView(
presentation.title,
systemImage: icon,
description: Text(presentation.detail)
)
}
private var icon: String {
switch presentation.state {
case .ready: "timer"
case .empty: "iphone.and.arrow.forward"
case .syncing: "arrow.triangle.2.circlepath"
case .stale: "clock.badge.exclamationmark"
case .locked: "lock"
case .unavailable: "iphone.slash"
case .error: "exclamationmark.triangle"
}
}
}
private struct WatchStatusRow: View {
let presentation: WatchPresentation
var body: some View {
Label {
VStack(alignment: .leading) {
Text(presentation.title).font(.headline)
Text(presentation.detail)
.font(.caption)
.foregroundStyle(.secondary)
}
} icon: {
Image(systemName: "clock.badge.exclamationmark")
}
}
}
@main
struct IronStorageWatchApp: App {
@Environment(\.scenePhase) private var scenePhase
@@ -164,7 +306,7 @@ struct IronStorageWatchApp: App {
var body: some Scene {
WindowGroup {
ContentUnavailableView("No TOTP Codes", systemImage: "timer")
WatchRootView(transport: transport)
.onChange(of: scenePhase) { _, phase in
if phase == .active {
transport.sceneBecameActive()