Onboard iPhone password-store clone

This commit is contained in:
2026-08-11 16:33:08 +02:00
parent db898152bf
commit daf122aff3
11 changed files with 2770 additions and 38 deletions

View File

@@ -462,6 +462,46 @@ fileprivate final class UniffiHandleMap<T>: @unchecked Sendable {
// Public interface members begin here.
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
fileprivate struct FfiConverterUInt32: FfiConverterPrimitive {
typealias FfiType = UInt32
typealias SwiftType = UInt32
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt32 {
return try lift(readInt(&buf))
}
public static func write(_ value: SwiftType, into buf: inout [UInt8]) {
writeInt(&buf, lower(value))
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
fileprivate struct FfiConverterBool : FfiConverter {
typealias FfiType = Int8
typealias SwiftType = Bool
public static func lift(_ value: Int8) throws -> Bool {
return value != 0
}
public static func lower(_ value: Bool) -> Int8 {
return value ? 1 : 0
}
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Bool {
return try lift(readInt(&buf))
}
public static func write(_ value: Bool, into buf: inout [UInt8]) {
writeInt(&buf, lower(value))
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
@@ -509,6 +549,323 @@ fileprivate struct FfiConverterString: FfiConverter {
}
public protocol MobileOnboardingOperationProtocol: AnyObject, Sendable {
func cancel()
func discover() throws -> MobileOnboardingDiscovery
func progress() -> MobileOnboardingProgress
func setup(branch: String, useExisting: Bool) throws -> MobileOnboardingOutcome
}
open class MobileOnboardingOperation: MobileOnboardingOperationProtocol, @unchecked Sendable {
fileprivate let handle: UInt64
/// Used to instantiate a [FFIObject] without an actual handle, for fakes in tests, mostly.
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct NoHandle {
public init() {}
}
// TODO: We'd like this to be `private` but for Swifty reasons,
// we can't implement `FfiConverter` without making this `required` and we can't
// make it `required` without making it `public`.
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
required public init(unsafeFromHandle handle: UInt64) {
self.handle = handle
}
// This constructor can be used to instantiate a fake object.
// - Parameter noHandle: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject].
//
// - Warning:
// Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing handle the FFI lower functions will crash.
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public init(noHandle: NoHandle) {
self.handle = 0
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func uniffiCloneHandle() -> UInt64 {
return try! rustCall { uniffi_ironstorage_apple_fn_clone_mobileonboardingoperation(self.handle, $0) }
}
// No primary constructor declared for this class.
deinit {
if handle == 0 {
// Mock objects have handle=0 don't try to free them
return
}
try! rustCall { uniffi_ironstorage_apple_fn_free_mobileonboardingoperation(handle, $0) }
}
open func cancel() {try! rustCall() {
uniffiCallStatus in
uniffi_ironstorage_apple_fn_method_mobileonboardingoperation_cancel(
self.uniffiCloneHandle(),uniffiCallStatus
)
}
}
open func discover()throws -> MobileOnboardingDiscovery {
return try FfiConverterTypeMobileOnboardingDiscovery_lift(try rustCallWithError(FfiConverterTypeMobileOnboardingFfiError_lift) {
uniffiCallStatus in
uniffi_ironstorage_apple_fn_method_mobileonboardingoperation_discover(
self.uniffiCloneHandle(),uniffiCallStatus
)
})
}
open func progress() -> MobileOnboardingProgress {
return try! FfiConverterTypeMobileOnboardingProgress_lift(try! rustCall() {
uniffiCallStatus in
uniffi_ironstorage_apple_fn_method_mobileonboardingoperation_progress(
self.uniffiCloneHandle(),uniffiCallStatus
)
})
}
open func setup(branch: String, useExisting: Bool)throws -> MobileOnboardingOutcome {
return try FfiConverterTypeMobileOnboardingOutcome_lift(try rustCallWithError(FfiConverterTypeMobileOnboardingFfiError_lift) {
uniffiCallStatus in
uniffi_ironstorage_apple_fn_method_mobileonboardingoperation_setup(
self.uniffiCloneHandle(),
FfiConverterString.lower(branch),
FfiConverterBool.lower(useExisting),uniffiCallStatus
)
})
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct FfiConverterTypeMobileOnboardingOperation: FfiConverter {
typealias FfiType = UInt64
typealias SwiftType = MobileOnboardingOperation
public static func lift(_ handle: UInt64) throws -> MobileOnboardingOperation {
return MobileOnboardingOperation(unsafeFromHandle: handle)
}
public static func lower(_ value: MobileOnboardingOperation) -> UInt64 {
return value.uniffiCloneHandle()
}
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileOnboardingOperation {
let handle: UInt64 = try readInt(&buf)
return try lift(handle)
}
public static func write(_ value: MobileOnboardingOperation, into buf: inout [UInt8]) {
writeInt(&buf, lower(value))
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileOnboardingOperation_lift(_ handle: UInt64) throws -> MobileOnboardingOperation {
return try FfiConverterTypeMobileOnboardingOperation.lift(handle)
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileOnboardingOperation_lower(_ value: MobileOnboardingOperation) -> UInt64 {
return FfiConverterTypeMobileOnboardingOperation.lower(value)
}
public struct MobileOnboardingDiscovery: Equatable, Hashable {
public var branches: [String]
public var selectedBranch: UInt32
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(branches: [String], selectedBranch: UInt32) {
self.branches = branches
self.selectedBranch = selectedBranch
}
}
#if compiler(>=6)
extension MobileOnboardingDiscovery: Sendable {}
#endif
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct FfiConverterTypeMobileOnboardingDiscovery: FfiConverterRustBuffer {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileOnboardingDiscovery {
return
try MobileOnboardingDiscovery(
branches: FfiConverterSequenceString.read(from: &buf),
selectedBranch: FfiConverterUInt32.read(from: &buf)
)
}
public static func write(_ value: MobileOnboardingDiscovery, into buf: inout [UInt8]) {
FfiConverterSequenceString.write(value.branches, into: &buf)
FfiConverterUInt32.write(value.selectedBranch, into: &buf)
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileOnboardingDiscovery_lift(_ buf: RustBuffer) throws -> MobileOnboardingDiscovery {
return try FfiConverterTypeMobileOnboardingDiscovery.lift(buf)
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileOnboardingDiscovery_lower(_ value: MobileOnboardingDiscovery) -> RustBuffer {
return FfiConverterTypeMobileOnboardingDiscovery.lower(value)
}
public struct MobileOnboardingOutcome: Equatable, Hashable {
public var title: String
public var detail: String
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(title: String, detail: String) {
self.title = title
self.detail = detail
}
}
#if compiler(>=6)
extension MobileOnboardingOutcome: Sendable {}
#endif
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct FfiConverterTypeMobileOnboardingOutcome: FfiConverterRustBuffer {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileOnboardingOutcome {
return
try MobileOnboardingOutcome(
title: FfiConverterString.read(from: &buf),
detail: FfiConverterString.read(from: &buf)
)
}
public static func write(_ value: MobileOnboardingOutcome, into buf: inout [UInt8]) {
FfiConverterString.write(value.title, into: &buf)
FfiConverterString.write(value.detail, into: &buf)
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileOnboardingOutcome_lift(_ buf: RustBuffer) throws -> MobileOnboardingOutcome {
return try FfiConverterTypeMobileOnboardingOutcome.lift(buf)
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileOnboardingOutcome_lower(_ value: MobileOnboardingOutcome) -> RustBuffer {
return FfiConverterTypeMobileOnboardingOutcome.lower(value)
}
public struct MobileOnboardingProgress: Equatable, Hashable {
public var phase: MobileOnboardingPhase
public var title: String
public var detail: String
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(phase: MobileOnboardingPhase, title: String, detail: String) {
self.phase = phase
self.title = title
self.detail = detail
}
}
#if compiler(>=6)
extension MobileOnboardingProgress: Sendable {}
#endif
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct FfiConverterTypeMobileOnboardingProgress: FfiConverterRustBuffer {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileOnboardingProgress {
return
try MobileOnboardingProgress(
phase: FfiConverterTypeMobileOnboardingPhase.read(from: &buf),
title: FfiConverterString.read(from: &buf),
detail: FfiConverterString.read(from: &buf)
)
}
public static func write(_ value: MobileOnboardingProgress, into buf: inout [UInt8]) {
FfiConverterTypeMobileOnboardingPhase.write(value.phase, into: &buf)
FfiConverterString.write(value.title, into: &buf)
FfiConverterString.write(value.detail, into: &buf)
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileOnboardingProgress_lift(_ buf: RustBuffer) throws -> MobileOnboardingProgress {
return try FfiConverterTypeMobileOnboardingProgress.lift(buf)
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileOnboardingProgress_lower(_ value: MobileOnboardingProgress) -> RustBuffer {
return FfiConverterTypeMobileOnboardingProgress.lower(value)
}
public struct MobilePage: Equatable, Hashable {
public var tab: MobileTab
public var title: String
@@ -637,6 +994,287 @@ public func FfiConverterTypeMobileShell_lower(_ value: MobileShell) -> RustBuffe
}
public enum MobileOnboardingErrorKind: Equatable, Hashable {
case invalidInput
case unsupportedRemote
case authentication
case repository
case existingClone
case interrupted
case secureStorage
case configuration
case alreadyConfigured
}
#if compiler(>=6)
extension MobileOnboardingErrorKind: Sendable {}
#endif
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct FfiConverterTypeMobileOnboardingErrorKind: FfiConverterRustBuffer {
typealias SwiftType = MobileOnboardingErrorKind
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileOnboardingErrorKind {
let variant: Int32 = try readInt(&buf)
switch variant {
case 1: return .invalidInput
case 2: return .unsupportedRemote
case 3: return .authentication
case 4: return .repository
case 5: return .existingClone
case 6: return .interrupted
case 7: return .secureStorage
case 8: return .configuration
case 9: return .alreadyConfigured
default: throw UniffiInternalError.unexpectedEnumCase
}
}
public static func write(_ value: MobileOnboardingErrorKind, into buf: inout [UInt8]) {
switch value {
case .invalidInput:
writeInt(&buf, Int32(1))
case .unsupportedRemote:
writeInt(&buf, Int32(2))
case .authentication:
writeInt(&buf, Int32(3))
case .repository:
writeInt(&buf, Int32(4))
case .existingClone:
writeInt(&buf, Int32(5))
case .interrupted:
writeInt(&buf, Int32(6))
case .secureStorage:
writeInt(&buf, Int32(7))
case .configuration:
writeInt(&buf, Int32(8))
case .alreadyConfigured:
writeInt(&buf, Int32(9))
}
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileOnboardingErrorKind_lift(_ buf: RustBuffer) throws -> MobileOnboardingErrorKind {
return try FfiConverterTypeMobileOnboardingErrorKind.lift(buf)
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileOnboardingErrorKind_lower(_ value: MobileOnboardingErrorKind) -> RustBuffer {
return FfiConverterTypeMobileOnboardingErrorKind.lower(value)
}
public
enum MobileOnboardingFfiError: Swift.Error, Equatable, Hashable, Foundation.LocalizedError {
case Failed(kind: MobileOnboardingErrorKind, title: String, detail: String
)
public var errorDescription: String? {
String(reflecting: self)
}
}
#if compiler(>=6)
extension MobileOnboardingFfiError: Sendable {}
#endif
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct FfiConverterTypeMobileOnboardingFfiError: FfiConverterRustBuffer {
typealias SwiftType = MobileOnboardingFfiError
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileOnboardingFfiError {
let variant: Int32 = try readInt(&buf)
switch variant {
case 1: return .Failed(
kind: try FfiConverterTypeMobileOnboardingErrorKind.read(from: &buf),
title: try FfiConverterString.read(from: &buf),
detail: try FfiConverterString.read(from: &buf)
)
default: throw UniffiInternalError.unexpectedEnumCase
}
}
public static func write(_ value: MobileOnboardingFfiError, into buf: inout [UInt8]) {
switch value {
case let .Failed(kind,title,detail):
writeInt(&buf, Int32(1))
FfiConverterTypeMobileOnboardingErrorKind.write(kind, into: &buf)
FfiConverterString.write(title, into: &buf)
FfiConverterString.write(detail, into: &buf)
}
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileOnboardingFfiError_lift(_ buf: RustBuffer) throws -> MobileOnboardingFfiError {
return try FfiConverterTypeMobileOnboardingFfiError.lift(buf)
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileOnboardingFfiError_lower(_ value: MobileOnboardingFfiError) -> RustBuffer {
return FfiConverterTypeMobileOnboardingFfiError.lower(value)
}
public enum MobileOnboardingPhase: Equatable, Hashable {
case validating
case authenticating
case receiving
case integrating
case finishing
}
#if compiler(>=6)
extension MobileOnboardingPhase: Sendable {}
#endif
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct FfiConverterTypeMobileOnboardingPhase: FfiConverterRustBuffer {
typealias SwiftType = MobileOnboardingPhase
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileOnboardingPhase {
let variant: Int32 = try readInt(&buf)
switch variant {
case 1: return .validating
case 2: return .authenticating
case 3: return .receiving
case 4: return .integrating
case 5: return .finishing
default: throw UniffiInternalError.unexpectedEnumCase
}
}
public static func write(_ value: MobileOnboardingPhase, into buf: inout [UInt8]) {
switch value {
case .validating:
writeInt(&buf, Int32(1))
case .authenticating:
writeInt(&buf, Int32(2))
case .receiving:
writeInt(&buf, Int32(3))
case .integrating:
writeInt(&buf, Int32(4))
case .finishing:
writeInt(&buf, Int32(5))
}
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileOnboardingPhase_lift(_ buf: RustBuffer) throws -> MobileOnboardingPhase {
return try FfiConverterTypeMobileOnboardingPhase.lift(buf)
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileOnboardingPhase_lower(_ value: MobileOnboardingPhase) -> RustBuffer {
return FfiConverterTypeMobileOnboardingPhase.lower(value)
}
public
enum MobilePreferenceError: Swift.Error, Equatable, Hashable, Foundation.LocalizedError {
@@ -878,6 +1516,31 @@ public func FfiConverterTypeMobileTab_lower(_ value: MobileTab) -> RustBuffer {
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
fileprivate struct FfiConverterSequenceString: FfiConverterRustBuffer {
typealias SwiftType = [String]
public static func write(_ value: [String], into buf: inout [UInt8]) {
let len = Int32(value.count)
writeInt(&buf, len)
for item in value {
FfiConverterString.write(item, into: &buf)
}
}
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [String] {
let len: Int32 = try readInt(&buf)
var seq = [String]()
seq.reserveCapacity(Int(len))
for _ in 0 ..< len {
seq.append(try FfiConverterString.read(from: &buf))
}
return seq
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
@@ -902,6 +1565,17 @@ fileprivate struct FfiConverterSequenceTypeMobilePage: FfiConverterRustBuffer {
return seq
}
}
public func mobileOnboardingOperation(serverUrl: String, account: String, repositoryPath: String, applicationToken: String)throws -> MobileOnboardingOperation {
return try FfiConverterTypeMobileOnboardingOperation_lift(try rustCallWithError(FfiConverterTypeMobileOnboardingFfiError_lift) {
uniffiCallStatus in
uniffi_ironstorage_apple_fn_func_mobile_onboarding_operation(
FfiConverterString.lower(serverUrl),
FfiConverterString.lower(account),
FfiConverterString.lower(repositoryPath),
FfiConverterString.lower(applicationToken),uniffiCallStatus
)
})
}
public func mobileShell() -> MobileShell {
return try! FfiConverterTypeMobileShell_lift(try! rustCall() {
uniffiCallStatus in
@@ -924,6 +1598,14 @@ public func productName() -> String {
)
})
}
public func replaceConfiguredMobileApplicationToken(account: String, applicationToken: String)throws {try rustCallWithError(FfiConverterTypeMobileOnboardingFfiError_lift) {
uniffiCallStatus in
uniffi_ironstorage_apple_fn_func_replace_configured_mobile_application_token(
FfiConverterString.lower(account),
FfiConverterString.lower(applicationToken),uniffiCallStatus
)
}
}
public func setSelectedMobileTab(tab: MobileTab)throws {try rustCallWithError(FfiConverterTypeMobilePreferenceError_lift) {
uniffiCallStatus in
uniffi_ironstorage_apple_fn_func_set_selected_mobile_tab(
@@ -947,6 +1629,9 @@ private let initializationResult: InitializationResult = {
if bindings_contract_version != scaffolding_contract_version {
return InitializationResult.contractVersionMismatch
}
if (uniffi_ironstorage_apple_checksum_func_mobile_onboarding_operation() != 8354) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_apple_checksum_func_mobile_shell() != 42687) {
return InitializationResult.apiChecksumMismatch
}
@@ -956,9 +1641,24 @@ private let initializationResult: InitializationResult = {
if (uniffi_ironstorage_apple_checksum_func_product_name() != 43533) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_apple_checksum_func_replace_configured_mobile_application_token() != 32386) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_apple_checksum_func_set_selected_mobile_tab() != 65280) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_apple_checksum_method_mobileonboardingoperation_cancel() != 49755) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_apple_checksum_method_mobileonboardingoperation_discover() != 2309) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_apple_checksum_method_mobileonboardingoperation_progress() != 28201) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_apple_checksum_method_mobileonboardingoperation_setup() != 3525) {
return InitializationResult.apiChecksumMismatch
}
return InitializationResult.ok
}()

View File

@@ -242,6 +242,41 @@ typedef struct UniffiForeignFutureResultVoid {
typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureResultVoid
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_CLONE_MOBILEONBOARDINGOPERATION
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_CLONE_MOBILEONBOARDINGOPERATION
uint64_t uniffi_ironstorage_apple_fn_clone_mobileonboardingoperation(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FREE_MOBILEONBOARDINGOPERATION
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FREE_MOBILEONBOARDINGOPERATION
void uniffi_ironstorage_apple_fn_free_mobileonboardingoperation(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEONBOARDINGOPERATION_CANCEL
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEONBOARDINGOPERATION_CANCEL
void uniffi_ironstorage_apple_fn_method_mobileonboardingoperation_cancel(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEONBOARDINGOPERATION_DISCOVER
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEONBOARDINGOPERATION_DISCOVER
RustBuffer uniffi_ironstorage_apple_fn_method_mobileonboardingoperation_discover(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEONBOARDINGOPERATION_PROGRESS
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEONBOARDINGOPERATION_PROGRESS
RustBuffer uniffi_ironstorage_apple_fn_method_mobileonboardingoperation_progress(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEONBOARDINGOPERATION_SETUP
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEONBOARDINGOPERATION_SETUP
RustBuffer uniffi_ironstorage_apple_fn_method_mobileonboardingoperation_setup(uint64_t ptr, RustBuffer branch, int8_t use_existing, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FUNC_MOBILE_ONBOARDING_OPERATION
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FUNC_MOBILE_ONBOARDING_OPERATION
uint64_t uniffi_ironstorage_apple_fn_func_mobile_onboarding_operation(RustBuffer server_url, RustBuffer account, RustBuffer repository_path, RustBuffer application_token, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FUNC_MOBILE_SHELL
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FUNC_MOBILE_SHELL
@@ -258,6 +293,11 @@ RustBuffer uniffi_ironstorage_apple_fn_func_mobile_shell_fixture(RustBuffer stat
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FUNC_PRODUCT_NAME
RustBuffer uniffi_ironstorage_apple_fn_func_product_name(RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FUNC_REPLACE_CONFIGURED_MOBILE_APPLICATION_TOKEN
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FUNC_REPLACE_CONFIGURED_MOBILE_APPLICATION_TOKEN
void uniffi_ironstorage_apple_fn_func_replace_configured_mobile_application_token(RustBuffer account, RustBuffer application_token, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FUNC_SET_SELECTED_MOBILE_TAB
@@ -523,6 +563,12 @@ void ffi_ironstorage_apple_rust_future_free_void(uint64_t handle
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_APPLE_RUST_FUTURE_COMPLETE_VOID
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_APPLE_RUST_FUTURE_COMPLETE_VOID
void ffi_ironstorage_apple_rust_future_complete_void(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_FUNC_MOBILE_ONBOARDING_OPERATION
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_FUNC_MOBILE_ONBOARDING_OPERATION
uint16_t uniffi_ironstorage_apple_checksum_func_mobile_onboarding_operation(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_FUNC_MOBILE_SHELL
@@ -541,12 +587,42 @@ uint16_t uniffi_ironstorage_apple_checksum_func_mobile_shell_fixture(void
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_FUNC_PRODUCT_NAME
uint16_t uniffi_ironstorage_apple_checksum_func_product_name(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_FUNC_REPLACE_CONFIGURED_MOBILE_APPLICATION_TOKEN
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_FUNC_REPLACE_CONFIGURED_MOBILE_APPLICATION_TOKEN
uint16_t uniffi_ironstorage_apple_checksum_func_replace_configured_mobile_application_token(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_FUNC_SET_SELECTED_MOBILE_TAB
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_FUNC_SET_SELECTED_MOBILE_TAB
uint16_t uniffi_ironstorage_apple_checksum_func_set_selected_mobile_tab(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEONBOARDINGOPERATION_CANCEL
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEONBOARDINGOPERATION_CANCEL
uint16_t uniffi_ironstorage_apple_checksum_method_mobileonboardingoperation_cancel(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEONBOARDINGOPERATION_DISCOVER
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEONBOARDINGOPERATION_DISCOVER
uint16_t uniffi_ironstorage_apple_checksum_method_mobileonboardingoperation_discover(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEONBOARDINGOPERATION_PROGRESS
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEONBOARDINGOPERATION_PROGRESS
uint16_t uniffi_ironstorage_apple_checksum_method_mobileonboardingoperation_progress(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEONBOARDINGOPERATION_SETUP
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEONBOARDINGOPERATION_SETUP
uint16_t uniffi_ironstorage_apple_checksum_method_mobileonboardingoperation_setup(void
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_APPLE_UNIFFI_CONTRACT_VERSION

View File

@@ -167,6 +167,23 @@ private final class ShellViewController: UITableViewController {
private func apply(_ page: MobilePage) {
self.page = page
title = page.title
if page.state == .empty, shellTab == .home {
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: "Set Up",
style: .done,
target: self,
action: #selector(setupRequested)
)
} else if page.state == .ready, shellTab == .preferences {
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: "Update Token",
style: .plain,
target: self,
action: #selector(tokenUpdateRequested)
)
} else {
navigationItem.rightBarButtonItem = nil
}
refreshControl?.endRefreshing()
tableView.reloadData()
@@ -183,6 +200,14 @@ private final class ShellViewController: UITableViewController {
contentUnavailableConfiguration = configuration
}
@objc private func setupRequested() {
navigationController?.pushViewController(OnboardingViewController(), animated: true)
}
@objc private func tokenUpdateRequested() {
navigationController?.pushViewController(TokenUpdateViewController(), animated: true)
}
private func stateImage(_ state: MobileShellState) -> String {
switch state {
case .loading: "hourglass"
@@ -193,3 +218,482 @@ private final class ShellViewController: UITableViewController {
}
}
}
@MainActor
private final class TokenUpdateViewController: UITableViewController {
private let accountField = UITextField()
private let tokenField = UITextField()
private var updateTask: Task<Void, Never>?
private var generation = 0
private var isWorking = false
init() {
super.init(style: .insetGrouped)
title = "Update Token"
navigationItem.largeTitleDisplayMode = .never
for field in [accountField, tokenField] {
field.borderStyle = .none
field.clearButtonMode = .whileEditing
field.autocorrectionType = .no
field.autocapitalizationType = .none
field.adjustsFontForContentSizeCategory = true
field.font = .preferredFont(forTextStyle: .body)
}
accountField.placeholder = "Account name"
tokenField.placeholder = "New application token"
tokenField.textContentType = .oneTimeCode
tokenField.isSecureTextEntry = true
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) is not supported")
}
deinit {
updateTask?.cancel()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
generation += 1
updateTask?.cancel()
}
override func numberOfSections(in tableView: UITableView) -> Int { 2 }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
section == 0 ? 2 : 1
}
override func tableView(
_ tableView: UITableView,
titleForHeaderInSection section: Int
) -> String? {
section == 0 ? "HTTPS Credential" : nil
}
override func tableView(
_ tableView: UITableView,
titleForFooterInSection section: Int
) -> String? {
section == 0
? "The existing token is never displayed. Updating replaces it only in protected system storage."
: nil
}
override func tableView(
_ tableView: UITableView,
cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
if indexPath.section == 1 {
cell.textLabel?.text = isWorking ? "Updating…" : "Replace Token"
cell.textLabel?.textColor = isWorking ? .secondaryLabel : view.tintColor
cell.textLabel?.textAlignment = .center
cell.isUserInteractionEnabled = !isWorking
return cell
}
let field = indexPath.row == 0 ? accountField : tokenField
field.translatesAutoresizingMaskIntoConstraints = false
field.isEnabled = !isWorking
cell.contentView.addSubview(field)
NSLayoutConstraint.activate([
field.leadingAnchor.constraint(equalTo: cell.contentView.layoutMarginsGuide.leadingAnchor),
field.trailingAnchor.constraint(equalTo: cell.contentView.layoutMarginsGuide.trailingAnchor),
field.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 10),
field.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -10)
])
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
guard indexPath.section == 1, !isWorking else { return }
replaceToken()
}
private func replaceToken() {
generation += 1
let current = generation
isWorking = true
navigationItem.prompt = "Saving in protected system storage."
tableView.reloadData()
let account = accountField.text ?? ""
let token = tokenField.text ?? ""
updateTask = Task { [weak self] in
let result = await Task.detached(priority: .userInitiated) {
do {
try replaceConfiguredMobileApplicationToken(
account: account,
applicationToken: token
)
return Result<Void, OnboardingFailure>.success(())
} catch let error as MobileOnboardingFfiError {
return .failure(OnboardingFailure(error))
} catch {
return .failure(.unexpected)
}
}.value
guard let self, current == generation else { return }
isWorking = false
navigationItem.prompt = nil
tableView.reloadData()
switch result {
case .success:
tokenField.text = nil
let alert = UIAlertController(
title: "Token Updated",
message: "The new application token is ready for HTTPS Git operations.",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "Done", style: .default) { [weak self] _ in
self?.navigationController?.popViewController(animated: true)
})
present(alert, animated: true)
case let .failure(failure):
let alert = UIAlertController(
title: failure.title,
message: failure.detail,
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "OK", style: .cancel))
present(alert, animated: true)
}
}
}
}
@MainActor
private final class OnboardingViewController: UITableViewController {
private let serverField = UITextField()
private let accountField = UITextField()
private let repositoryField = UITextField()
private let tokenField = UITextField()
private var branches: [String] = []
private var selectedBranch = 0
private var operation: MobileOnboardingOperation?
private var workTask: Task<Void, Never>?
private var progressTask: Task<Void, Never>?
private var generation = 0
private var isWorking = false
init() {
super.init(style: .insetGrouped)
title = "Connect Store"
navigationItem.largeTitleDisplayMode = .never
configureFields()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) is not supported")
}
deinit {
operation?.cancel()
workTask?.cancel()
progressTask?.cancel()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
cancelWork()
}
override func numberOfSections(in tableView: UITableView) -> Int {
branches.isEmpty ? 2 : 3
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0: 4
case 1: 1
default: 2
}
}
override func tableView(
_ tableView: UITableView,
titleForHeaderInSection section: Int
) -> String? {
switch section {
case 0: "HTTPS Repository"
case 1: branches.isEmpty ? nil : "Remote"
default: "Local Clone"
}
}
override func tableView(
_ tableView: UITableView,
titleForFooterInSection section: Int
) -> String? {
section == 0 ? "The application token is stored in protected system storage, never in configuration." : nil
}
override func tableView(
_ tableView: UITableView,
cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
if indexPath.section == 0 {
return fieldCell(indexPath.row)
}
let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
if branches.isEmpty {
cell.textLabel?.text = "Discover Branches"
cell.textLabel?.textColor = view.tintColor
cell.accessoryType = .disclosureIndicator
} else if indexPath.section == 1 {
cell.textLabel?.text = "Branch"
cell.detailTextLabel?.text = branches[selectedBranch]
cell.accessoryType = .disclosureIndicator
} else if indexPath.row == 0 {
cell.textLabel?.text = "Clone Password Store"
cell.textLabel?.textColor = view.tintColor
} else {
cell.textLabel?.text = "Use Existing Clone"
cell.textLabel?.textColor = .secondaryLabel
}
cell.isUserInteractionEnabled = !isWorking
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
guard !isWorking else { return }
if branches.isEmpty, indexPath.section == 1 {
discoverBranches()
} else if indexPath.section == 1 {
chooseBranch(from: tableView.cellForRow(at: indexPath))
} else if indexPath.section == 2 {
runSetup(useExisting: indexPath.row == 1)
}
}
private func configureFields() {
for field in [serverField, accountField, repositoryField, tokenField] {
field.borderStyle = .none
field.clearButtonMode = .whileEditing
field.autocorrectionType = .no
field.returnKeyType = .next
field.adjustsFontForContentSizeCategory = true
field.font = .preferredFont(forTextStyle: .body)
}
serverField.placeholder = "https://git.example.com"
serverField.textContentType = .URL
serverField.keyboardType = .URL
serverField.autocapitalizationType = .none
accountField.placeholder = "Account name"
accountField.autocapitalizationType = .none
repositoryField.placeholder = "owner/password-store"
repositoryField.autocapitalizationType = .none
tokenField.placeholder = "Application token"
tokenField.textContentType = .oneTimeCode
tokenField.isSecureTextEntry = true
}
private func fieldCell(_ row: Int) -> UITableViewCell {
let field = [serverField, accountField, repositoryField, tokenField][row]
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
field.translatesAutoresizingMaskIntoConstraints = false
cell.contentView.addSubview(field)
NSLayoutConstraint.activate([
field.leadingAnchor.constraint(equalTo: cell.contentView.layoutMarginsGuide.leadingAnchor),
field.trailingAnchor.constraint(equalTo: cell.contentView.layoutMarginsGuide.trailingAnchor),
field.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 10),
field.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -10)
])
return cell
}
private func makeOperation() throws -> MobileOnboardingOperation {
try mobileOnboardingOperation(
serverUrl: serverField.text ?? "",
account: accountField.text ?? "",
repositoryPath: repositoryField.text ?? "",
applicationToken: tokenField.text ?? ""
)
}
private func discoverBranches() {
do {
let operation = try makeOperation()
begin(operation, title: "Checking Repository") { operation in
let discovery = try operation.discover()
return .discovered(discovery)
}
} catch {
present(error)
}
}
private func runSetup(useExisting: Bool) {
guard branches.indices.contains(selectedBranch) else { return }
do {
let operation = try makeOperation()
let branch = branches[selectedBranch]
begin(operation, title: useExisting ? "Opening Store" : "Cloning Store") { operation in
let outcome = try operation.setup(branch: branch, useExisting: useExisting)
return .completed(outcome)
}
} catch {
present(error)
}
}
private func begin(
_ operation: MobileOnboardingOperation,
title: String,
work: @escaping @Sendable (MobileOnboardingOperation) throws -> WorkResult
) {
cancelWork()
generation += 1
let current = generation
self.operation = operation
isWorking = true
showProgress(title: title, detail: "Preparing secure storage.")
tableView.reloadData()
progressTask = Task { [weak self] in
while !Task.isCancelled {
do {
try await Task.sleep(for: .milliseconds(150))
} catch {
return
}
guard !Task.isCancelled, let self, current == generation else { return }
let progress = operation.progress()
showProgress(title: progress.title, detail: progress.detail)
}
}
workTask = Task { [weak self] in
let result = await Task.detached(priority: .userInitiated) {
do {
return Result<WorkResult, OnboardingFailure>.success(try work(operation))
} catch let error as MobileOnboardingFfiError {
return .failure(OnboardingFailure(error))
} catch {
return .failure(.unexpected)
}
}.value
guard let self, current == generation else { return }
finish(result)
}
}
private func finish(_ result: Result<WorkResult, OnboardingFailure>) {
progressTask?.cancel()
progressTask = nil
operation = nil
isWorking = false
navigationItem.titleView = nil
navigationItem.prompt = nil
switch result {
case let .success(.discovered(discovery)):
branches = discovery.branches
selectedBranch = min(Int(discovery.selectedBranch), max(branches.count - 1, 0))
tableView.reloadData()
case let .success(.completed(outcome)):
tokenField.text = nil
let alert = UIAlertController(title: outcome.title, message: outcome.detail, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Done", style: .default) { [weak self] _ in
self?.navigationController?.popViewController(animated: true)
})
present(alert, animated: true)
case let .failure(failure):
tableView.reloadData()
present(failure)
}
}
private func chooseBranch(from source: UIView?) {
let sheet = UIAlertController(title: "Branch", message: nil, preferredStyle: .actionSheet)
for (index, branch) in branches.enumerated() {
sheet.addAction(UIAlertAction(title: branch, style: .default) { [weak self] _ in
self?.selectedBranch = index
self?.tableView.reloadSections(IndexSet(integer: 1), with: .automatic)
})
}
sheet.addAction(UIAlertAction(title: "Cancel", style: .cancel))
sheet.popoverPresentationController?.sourceView = source
sheet.popoverPresentationController?.sourceRect = source?.bounds ?? .zero
present(sheet, animated: true)
}
private func present(_ error: Error) {
if let error = error as? MobileOnboardingFfiError {
present(OnboardingFailure(error))
} else {
present(.unexpected)
}
}
private func present(_ failure: OnboardingFailure) {
let alert = UIAlertController(title: failure.title, message: failure.detail, preferredStyle: .alert)
if failure.kind == .existingClone, !branches.isEmpty {
alert.addAction(UIAlertAction(title: "Use Existing", style: .default) { [weak self] _ in
self?.runSetup(useExisting: true)
})
}
alert.addAction(UIAlertAction(title: "OK", style: .cancel))
present(alert, animated: true)
}
private func showProgress(title: String, detail: String) {
let spinner = UIActivityIndicatorView(style: .medium)
spinner.startAnimating()
spinner.accessibilityLabel = "In progress"
let label = UILabel()
label.text = title
label.font = .preferredFont(forTextStyle: .headline)
label.adjustsFontForContentSizeCategory = true
let stack = UIStackView(arrangedSubviews: [spinner, label])
stack.spacing = 8
navigationItem.titleView = stack
navigationItem.prompt = detail
}
private func cancelWork() {
generation += 1
operation?.cancel()
operation = nil
workTask?.cancel()
workTask = nil
progressTask?.cancel()
progressTask = nil
navigationItem.prompt = nil
navigationItem.titleView = nil
isWorking = false
}
}
private enum WorkResult: Sendable {
case discovered(MobileOnboardingDiscovery)
case completed(MobileOnboardingOutcome)
}
private struct OnboardingFailure: Error, Sendable {
let kind: MobileOnboardingErrorKind?
let title: String
let detail: String
init(_ error: MobileOnboardingFfiError) {
switch error {
case let .Failed(kind, title, detail):
self.kind = kind
self.title = title
self.detail = detail
}
}
static let unexpected = OnboardingFailure(
kind: nil,
title: "Setup Failed",
detail: "IronStorage could not complete setup."
)
private init(kind: MobileOnboardingErrorKind?, title: String, detail: String) {
self.kind = kind
self.title = title
self.detail = detail
}
}