From a93287367fde48ba16b9fd2f4e73641e00360226 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Sat, 22 Nov 2025 07:35:26 +0100 Subject: [PATCH 1/4] Concurrency adjustments, SPM: platforms, Demo: use LOCAL Package --- DfnsDemo/DfnsDemo.xcodeproj/project.pbxproj | 31 +- .../xcshareddata/swiftpm/Package.resolved | 15 - DfnsDemo/DfnsDemo/DfnsDemoApp.swift | 2 +- DfnsDemo/DfnsDemo/MyBusinessLogic.swift | 2 +- DfnsDemo/DfnsDemo/MyServer.swift | 2 +- DfnsDemo/Package.swift | 13 + Package.swift | 8 + Sources/DfnsSdk/DfnsApi.swift | 351 ++++++++++++------ Sources/DfnsSdk/Imported/Passkey.swift | 12 +- .../DfnsSdk/Imported/PasskeyDelegate.swift | 2 +- Sources/DfnsSdk/PasskeysSigner.swift | 191 +++++----- 11 files changed, 370 insertions(+), 259 deletions(-) delete mode 100644 DfnsDemo/DfnsDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 DfnsDemo/Package.swift diff --git a/DfnsDemo/DfnsDemo.xcodeproj/project.pbxproj b/DfnsDemo/DfnsDemo.xcodeproj/project.pbxproj index dc8f1cc..b103d71 100644 --- a/DfnsDemo/DfnsDemo.xcodeproj/project.pbxproj +++ b/DfnsDemo/DfnsDemo.xcodeproj/project.pbxproj @@ -3,17 +3,17 @@ archiveVersion = 1; classes = { }; - objectVersion = 56; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ + 48F0B3B02ED061F300500085 /* DfnsSdk in Frameworks */ = {isa = PBXBuildFile; productRef = 48F0B3AF2ED061F300500085 /* DfnsSdk */; }; 7A1A68702BBEA54B00167ED0 /* DfnsDemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1A686F2BBEA54B00167ED0 /* DfnsDemoApp.swift */; }; 7A1A68722BBEA54B00167ED0 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1A68712BBEA54B00167ED0 /* ContentView.swift */; }; 7A1A68742BBEA54C00167ED0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A1A68732BBEA54C00167ED0 /* Assets.xcassets */; }; 7A1A68772BBEA54C00167ED0 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A1A68762BBEA54C00167ED0 /* Preview Assets.xcassets */; }; 7A1A68A92BC5943700167ED0 /* MyServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1A68A82BC5943700167ED0 /* MyServer.swift */; }; 7A439BB12BCD22710022D861 /* MyBusinessLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A439BB02BCD22710022D861 /* MyBusinessLogic.swift */; }; - 7A439BB72BCD67270022D861 /* DfnsSdk in Frameworks */ = {isa = PBXBuildFile; productRef = 7A439BB62BCD67270022D861 /* DfnsSdk */; }; 7A5207E72BCD8B22003EC637 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5207E62BCD8B22003EC637 /* Config.swift */; }; /* End PBXBuildFile section */ @@ -34,7 +34,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 7A439BB72BCD67270022D861 /* DfnsSdk in Frameworks */, + 48F0B3B02ED061F300500085 /* DfnsSdk in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -97,7 +97,7 @@ ); name = DfnsDemo; packageProductDependencies = ( - 7A439BB62BCD67270022D861 /* DfnsSdk */, + 48F0B3AF2ED061F300500085 /* DfnsSdk */, ); productName = DfnsDemo; productReference = 7A1A686C2BBEA54B00167ED0 /* DfnsDemo.app */; @@ -128,7 +128,7 @@ ); mainGroup = 7A1A68632BBEA54B00167ED0; packageReferences = ( - 7A439BB52BCD67270022D861 /* XCRemoteSwiftPackageReference "dfns-sdk-swift" */, + 48F0B3AE2ED061F300500085 /* XCLocalSwiftPackageReference "../../dfns-sdk-swift" */, ); productRefGroup = 7A1A686D2BBEA54B00167ED0 /* Products */; projectDirPath = ""; @@ -227,6 +227,7 @@ SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -282,6 +283,7 @@ MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_VERSION = 5.0; VALIDATE_PRODUCT = YES; }; name = Release; @@ -313,7 +315,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -345,7 +346,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -373,21 +373,16 @@ }; /* End XCConfigurationList section */ -/* Begin XCRemoteSwiftPackageReference section */ - 7A439BB52BCD67270022D861 /* XCRemoteSwiftPackageReference "dfns-sdk-swift" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/dfns/dfns-sdk-swift"; - requirement = { - branch = m; - kind = branch; - }; +/* Begin XCLocalSwiftPackageReference section */ + 48F0B3AE2ED061F300500085 /* XCLocalSwiftPackageReference "../../dfns-sdk-swift" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = "../../dfns-sdk-swift"; }; -/* End XCRemoteSwiftPackageReference section */ +/* End XCLocalSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 7A439BB62BCD67270022D861 /* DfnsSdk */ = { + 48F0B3AF2ED061F300500085 /* DfnsSdk */ = { isa = XCSwiftPackageProductDependency; - package = 7A439BB52BCD67270022D861 /* XCRemoteSwiftPackageReference "dfns-sdk-swift" */; productName = DfnsSdk; }; /* End XCSwiftPackageProductDependency section */ diff --git a/DfnsDemo/DfnsDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DfnsDemo/DfnsDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index 163bf7e..0000000 --- a/DfnsDemo/DfnsDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,15 +0,0 @@ -{ - "originHash" : "4a21ff1bb0c8364c763bf1da609a1d60bf5c031cbce0665704b506df6d3af9a7", - "pins" : [ - { - "identity" : "dfns-sdk-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/dfns/dfns-sdk-swift", - "state" : { - "branch" : "m", - "revision" : "96b0ad2d93d967aa73893ea19f9b7f5b8eccaa98" - } - } - ], - "version" : 3 -} diff --git a/DfnsDemo/DfnsDemo/DfnsDemoApp.swift b/DfnsDemo/DfnsDemo/DfnsDemoApp.swift index 81baa45..57cad3a 100644 --- a/DfnsDemo/DfnsDemo/DfnsDemoApp.swift +++ b/DfnsDemo/DfnsDemo/DfnsDemoApp.swift @@ -1,4 +1,4 @@ -import DfnsSdk +@preconcurrency import DfnsSdk import SwiftUI class UserConfig: ObservableObject { diff --git a/DfnsDemo/DfnsDemo/MyBusinessLogic.swift b/DfnsDemo/DfnsDemo/MyBusinessLogic.swift index 5f6fa05..8aeecb3 100644 --- a/DfnsDemo/DfnsDemo/MyBusinessLogic.swift +++ b/DfnsDemo/DfnsDemo/MyBusinessLogic.swift @@ -1,4 +1,4 @@ -import DfnsSdk +@preconcurrency import DfnsSdk import Foundation /** diff --git a/DfnsDemo/DfnsDemo/MyServer.swift b/DfnsDemo/DfnsDemo/MyServer.swift index 6efdc8d..4aa7eae 100644 --- a/DfnsDemo/DfnsDemo/MyServer.swift +++ b/DfnsDemo/DfnsDemo/MyServer.swift @@ -1,4 +1,4 @@ -import DfnsSdk +@preconcurrency import DfnsSdk import Foundation /** diff --git a/DfnsDemo/Package.swift b/DfnsDemo/Package.swift new file mode 100644 index 0000000..88cb976 --- /dev/null +++ b/DfnsDemo/Package.swift @@ -0,0 +1,13 @@ +// swift-tools-version:6.2 + +// This is a HACKY workaround the fact that SPM does not allow for Package level exclusion +// of files/folders. SPM actually HAD support for it but it was removed in 2017, in PR +// https://github.com/apple/swift-package-manager/commit/cb69accf41da55386f9703308958aa49ca2a4c5f +// +// So instead we have to add an empty dummy Package.swift to each folder we wanna hide, as per: +// See: https://github.com/apple/swift-package-manager/issues/4460#issuecomment-1475025748 +// And: https://stackoverflow.com/questions/69382302/swift-package-how-to-exclude-files-in-root-git-directory-from-the-actual-swift/70990534#70990534 +// And: https://github.com/tuist/tuist/pull/2058 +import PackageDescription + +let package = Package(name: "HIDDEN") diff --git a/Package.swift b/Package.swift index 545668b..927d915 100644 --- a/Package.swift +++ b/Package.swift @@ -1,10 +1,18 @@ // swift-tools-version: 5.10 // The swift-tools-version declares the minimum version of Swift required to build this package. +// later change swift-tools-version to 6.2 + import PackageDescription let package = Package( name: "DfnsSdk", + platforms: [ + .macOS(.v10_15), + .iOS(.v15), + .watchOS(.v6), + .tvOS(.v13) + ], products: [ // Products define the executables and libraries a package produces, making them visible to other packages. .library( diff --git a/Sources/DfnsSdk/DfnsApi.swift b/Sources/DfnsSdk/DfnsApi.swift index 092a5b1..22da994 100644 --- a/Sources/DfnsSdk/DfnsApi.swift +++ b/Sources/DfnsSdk/DfnsApi.swift @@ -1,9 +1,28 @@ /** - Types defined in the Dfns API that might be arguments or return values of the demo server - */ -public enum DfnsApi { - public struct UserActionChallenge: Codable { - public init(attestation: String, userVerification: String, externalAuthenticationUrl: String, challenge: String, challengeIdentifier: String, supportedCredentialKinds: [DfnsApi.SupportedCredentialKind], allowCredentials: DfnsApi.AllowCredentials) { + Types defined in the Dfns API that might be arguments or return values of the demo server +*/ +public enum DfnsApi {} // just a namespace + +// MARK: - UserActionChallenge +extension DfnsApi { + public struct UserActionChallenge: Sendable, Codable { + public let attestation: String + public let userVerification: String + public let externalAuthenticationUrl: String + public let challenge: String + public let challengeIdentifier: String + public let supportedCredentialKinds: [SupportedCredentialKind] + public let allowCredentials: AllowCredentials + + public init( + attestation: String, + userVerification: String, + externalAuthenticationUrl: String, + challenge: String, + challengeIdentifier: String, + supportedCredentialKinds: [SupportedCredentialKind], + allowCredentials: AllowCredentials + ) { self.attestation = attestation self.userVerification = userVerification self.externalAuthenticationUrl = externalAuthenticationUrl @@ -12,18 +31,33 @@ public enum DfnsApi { self.supportedCredentialKinds = supportedCredentialKinds self.allowCredentials = allowCredentials } + } +} - public let attestation: String - public let userVerification: String - public let externalAuthenticationUrl: String - public let challenge: String - public let challengeIdentifier: String - public let supportedCredentialKinds: [SupportedCredentialKind] - public let allowCredentials: AllowCredentials - } +// MARK: - UserRegistrationChallenge +extension DfnsApi { + public struct UserRegistrationChallenge: Sendable, Codable { + public let temporaryAuthenticationToken: String + public let user: UserInformation + public let supportedCredentialKinds: SupportedCredentialKinds + public let otpUrl: String + public let challenge: String + public let authenticatorSelection: AuthenticatorSelectionCriteria + public let attestation: String + public let pubKeyCredParams: [PublicKeyCredentialParameters] + public let excludeCredentials: [PublicKeyCredentialDescriptor] - public struct UserRegistrationChallenge: Codable { - public init(temporaryAuthenticationToken: String, user: DfnsApi.UserInformation, supportedCredentialKinds: DfnsApi.SupportedCredentialKinds, otpUrl: String, challenge: String, authenticatorSelection: DfnsApi.AuthenticatorSelectionCriteria, attestation: String, pubKeyCredParams: [DfnsApi.PublicKeyCredentialParameters], excludeCredentials: [DfnsApi.PublicKeyCredentialDescriptor]) { + public init( + temporaryAuthenticationToken: String, + user: UserInformation, + supportedCredentialKinds: SupportedCredentialKinds, + otpUrl: String, + challenge: String, + authenticatorSelection: AuthenticatorSelectionCriteria, + attestation: String, + pubKeyCredParams: [PublicKeyCredentialParameters], + excludeCredentials: [PublicKeyCredentialDescriptor] + ) { self.temporaryAuthenticationToken = temporaryAuthenticationToken self.user = user self.supportedCredentialKinds = supportedCredentialKinds @@ -34,174 +68,257 @@ public enum DfnsApi { self.pubKeyCredParams = pubKeyCredParams self.excludeCredentials = excludeCredentials } + } +} - public let temporaryAuthenticationToken: String - public let user: UserInformation - public let supportedCredentialKinds: SupportedCredentialKinds - public let otpUrl: String - public let challenge: String - public let authenticatorSelection: AuthenticatorSelectionCriteria - public let attestation: String - public let pubKeyCredParams: [PublicKeyCredentialParameters] - public let excludeCredentials: [PublicKeyCredentialDescriptor] - } +// MARK: - RelyingParty +extension DfnsApi { + public struct RelyingParty: Sendable, Codable { + public let id: String + public let name: String - public struct RelyingParty: Codable { - public init(id: String, name: String) { + public init( + id: String, + name: String + ) { self.id = id self.name = name } + } +} - public let id: String - public let name: String - } - public struct SupportedCredentialKind: Codable { - public init(kind: String, factor: String, requiresSecondFactor: Bool) { +// MARK: - SupportedCredentialKind +extension DfnsApi { + public struct SupportedCredentialKind: Sendable, Codable { + public let kind: String + public let factor: String + public let requiresSecondFactor: Bool + + public init( + kind: String, + factor: String, + requiresSecondFactor: Bool + ) { self.kind = kind self.factor = factor self.requiresSecondFactor = requiresSecondFactor } + } +} - public let kind: String - public let factor: String - public let requiresSecondFactor: Bool - } +// MARK: - AllowCredentials +extension DfnsApi { + public struct AllowCredentials: Sendable, Codable { + public let webauthn: [PublicKeyCredentialDescriptor] + public let key: [PublicKeyCredentialDescriptor] - public struct AllowCredentials: Codable { - public init(webauthn: [DfnsApi.PublicKeyCredentialDescriptor], key: [DfnsApi.PublicKeyCredentialDescriptor]) { + public init( + webauthn: [PublicKeyCredentialDescriptor], + key: [PublicKeyCredentialDescriptor] + ) { self.webauthn = webauthn self.key = key } + } +} - public let webauthn: [PublicKeyCredentialDescriptor] - public let key: [PublicKeyCredentialDescriptor] - } +// MARK: - PublicKeyCredentialDescriptor +extension DfnsApi { + public struct PublicKeyCredentialDescriptor: Sendable, Codable { + public let type: String + public let id: String - public struct PublicKeyCredentialDescriptor: Codable { - public init(type: String, id: String) { + public init( + type: String, + id: String + ) { self.type = type self.id = id } + } +} - public let type: String - public let id: String - } +// MARK: - Fido2Assertion +extension DfnsApi { + public struct Fido2Assertion: Sendable, Codable { + public let kind: String + public let credentialAssertion: Fido2AssertionData - public struct Fido2Assertion: Codable { - public init(kind: String, credentialAssertion: DfnsApi.Fido2AssertionData) { + public init( + kind: String, + credentialAssertion: Fido2AssertionData + ) { self.kind = kind self.credentialAssertion = credentialAssertion } + } +} - public let kind: String - public let credentialAssertion: Fido2AssertionData - } +// MARK: - UserActionAssertion +extension DfnsApi { + public struct UserActionAssertion: Sendable, Codable { + public let challengeIdentifier: String + public let firstFactor: Fido2Assertion - public struct UserActionAssertion: Codable { - public init(challengeIdentifier: String, firstFactor: DfnsApi.Fido2Assertion) { + public init( + challengeIdentifier: String, + firstFactor: Fido2Assertion + ) { self.challengeIdentifier = challengeIdentifier self.firstFactor = firstFactor } + } +} - public let challengeIdentifier: String - public let firstFactor: Fido2Assertion - } +// MARK: - ClientData +extension DfnsApi { + public struct ClientData: Sendable, Codable { + public let type: String + public let challenge: String + public let origin: String - public struct ClientData: Codable { - public init(type: String, challenge: String, origin: String) { + public init( + type: String, + challenge: String, + origin: String + ) { self.type = type self.challenge = challenge self.origin = origin } - - public let type: String - public let challenge: String - public let origin: String - // public let crossOrigin: Bool } +} - public struct Fido2AssertionData: Codable { - public init(clientData: String, credId: String, signature: String, authenticatorData: String, userHandle: String? = nil) { - self.clientData = clientData - self.credId = credId - self.signature = signature - self.authenticatorData = authenticatorData - self.userHandle = userHandle - } +// MARK: - Fido2AssertionData +extension DfnsApi { + public struct Fido2AssertionData: Sendable, Codable { + public let clientData: String + public let credId: String + public let signature: String + public var authenticatorData: String + public var userHandle: String? + + public init( + clientData: String, + credId: String, + signature: String, + authenticatorData: String, + userHandle: String? = nil + ) { + self.clientData = clientData + self.credId = credId + self.signature = signature + self.authenticatorData = authenticatorData + self.userHandle = userHandle + } + } +} - public let clientData: String - public let credId: String - public let signature: String - public var authenticatorData: String - public var userHandle: String? - } +// MARK: - PublicKeyCredentialParameters +extension DfnsApi { + public struct PublicKeyCredentialParameters: Sendable, Codable { + public let type: String + public let alg: Int - public struct PublicKeyCredentialParameters: Codable { - public init(type: String, alg: Int) { + public init( + type: String, + alg: Int + ) { self.type = type self.alg = alg } + } +} - public let type: String - public let alg: Int - } +// MARK: - SupportedCredentialKinds +extension DfnsApi { + public struct SupportedCredentialKinds: Sendable, Codable { + public let firstFactor: [String] + public let secondFactor: [String] - public struct SupportedCredentialKinds: Codable { - public init(firstFactor: [String], secondFactor: [String]) { + public init( + firstFactor: [String], + secondFactor: [String] + ) { self.firstFactor = firstFactor self.secondFactor = secondFactor } + } +} - public let firstFactor: [String] - public let secondFactor: [String] - } +// MARK: - UserInformation +extension DfnsApi { + public struct UserInformation: Sendable, Codable { + public let id: String + public let displayName: String + public let name: String - public struct UserInformation: Codable { - public init(id: String, displayName: String, name: String) { + public init( + id: String, + displayName: String, + name: String + ) { self.id = id self.displayName = displayName self.name = name } + } +} - public let id: String - public let displayName: String - public let name: String - } - - public struct AuthenticatorSelectionCriteria: Codable { - public init(authenticatorAttachment: String? = nil, residentKey: String, requireResidentKey: Bool, userVerification: String) { - self.authenticatorAttachment = authenticatorAttachment - self.residentKey = residentKey - self.requireResidentKey = requireResidentKey - self.userVerification = userVerification - } +// MARK: - AuthenticatorSelectionCriteria +extension DfnsApi { + public struct AuthenticatorSelectionCriteria: Sendable, Codable { + public let authenticatorAttachment: String? + public let residentKey: String + public let requireResidentKey: Bool + public let userVerification: String + + public init( + authenticatorAttachment: String? = nil, + residentKey: String, + requireResidentKey: Bool, + userVerification: String + ) { + self.authenticatorAttachment = authenticatorAttachment + self.residentKey = residentKey + self.requireResidentKey = requireResidentKey + self.userVerification = userVerification + } + } +} - public let authenticatorAttachment: String? - public let residentKey: String - public let requireResidentKey: Bool - public let userVerification: String - } +// MARK: - Fido2Attestation +extension DfnsApi { + public struct Fido2Attestation: Sendable, Codable { + public let credentialInfo: Fido2AttestationData + public let credentialKind: String - public struct Fido2Attestation: Codable { - public init(credentialInfo: DfnsApi.Fido2AttestationData, credentialKind: String) { + public init( + credentialInfo: Fido2AttestationData, + credentialKind: String + ) { self.credentialInfo = credentialInfo self.credentialKind = credentialKind } + } +} - public let credentialInfo: Fido2AttestationData - public let credentialKind: String - } +// MARK: - Fido2AttestationData +extension DfnsApi { + public struct Fido2AttestationData: Sendable, Codable { + public let attestationData: String + public let clientData: String + public let credId: String - public struct Fido2AttestationData: Codable { - public init(attestationData: String, clientData: String, credId: String) { + public init( + attestationData: String, + clientData: String, + credId: String + ) { self.attestationData = attestationData self.clientData = clientData self.credId = credId } - - public let attestationData: String - public let clientData: String - public let credId: String } } diff --git a/Sources/DfnsSdk/Imported/Passkey.swift b/Sources/DfnsSdk/Imported/Passkey.swift index 58b1a57..e5e8a5c 100644 --- a/Sources/DfnsSdk/Imported/Passkey.swift +++ b/Sources/DfnsSdk/Imported/Passkey.swift @@ -163,28 +163,28 @@ enum PassKeyError: String, Error { case unknown = "UnknownError" } -struct AuthRegistrationResult { +struct AuthRegistrationResult: Sendable { var passkey: PassKeyRegistrationResult var type: PasskeyOperation } -struct AuthAssertionResult { +struct AuthAssertionResult: Sendable { var passkey: PassKeyAssertionResult var type: PasskeyOperation } -struct PassKeyResult { +struct PassKeyResult: Sendable { var registrationResult: PassKeyRegistrationResult? var assertionResult: PassKeyAssertionResult? } -struct PassKeyRegistrationResult { +struct PassKeyRegistrationResult: Sendable { var credentialID: Data var rawAttestationObject: Data var rawClientDataJSON: Data } -struct PassKeyAssertionResult { +struct PassKeyAssertionResult: Sendable { var credentialID: Data var rawAuthenticatorData: Data var rawClientDataJSON: Data @@ -192,7 +192,7 @@ struct PassKeyAssertionResult { var userID: Data } -enum PasskeyOperation { +enum PasskeyOperation: Sendable { case Registration case Assertion } diff --git a/Sources/DfnsSdk/Imported/PasskeyDelegate.swift b/Sources/DfnsSdk/Imported/PasskeyDelegate.swift index ea044f2..037788d 100644 --- a/Sources/DfnsSdk/Imported/PasskeyDelegate.swift +++ b/Sources/DfnsSdk/Imported/PasskeyDelegate.swift @@ -10,7 +10,7 @@ class PasskeyDelegate: NSObject, ASAuthorizationControllerDelegate, ASAuthorizat private var _completion: (_ error: Error?, _ result: PassKeyResult?) -> Void; // Initializes delegate with a completion handler (callback function) - init(completionHandler: @escaping (_ error: Error?, _ result: PassKeyResult?) -> Void) { + init(completionHandler: @escaping @Sendable (_ error: Error?, _ result: PassKeyResult?) -> Void) { self._completion = completionHandler; } diff --git a/Sources/DfnsSdk/PasskeysSigner.swift b/Sources/DfnsSdk/PasskeysSigner.swift index 52dc874..93ad867 100644 --- a/Sources/DfnsSdk/PasskeysSigner.swift +++ b/Sources/DfnsSdk/PasskeysSigner.swift @@ -11,103 +11,96 @@ public enum PasskeysSignerError: Error { Converts completion handlers into async functions and make the necessary conversion to work with Dfns API */ public final class PasskeysSigner { - private let passkey = Passkey() - - /** - The relying party ID identifies your application to users, when users create/use passkeys. (Read more [here](https://www.w3.org/TR/webauthn-2/#relying-party)). - It is a valid domain string identifying the WebAuthn Relying Party. In other words, its the domain your application is running on, which will be tied to the passkeys that users create. - We advise to use the root domain, not the full domain (eg `acme.com`, not `app.acme.com` nor `foo.app.acme.com`), that way, passkeys created - by your users can be re-used on other subdomains (eg. on `foo.acme.com` and `bar.acme.com`) in the future. Read more [here](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredentialCreationOptions#rp). - */ - private let relyingPartyId: String - - public init(relyingPartyId: String) { - self.relyingPartyId = relyingPartyId - } - - public func register(challenge: DfnsApi.UserRegistrationChallenge) async throws -> DfnsApi.Fido2Attestation { - if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) { - let result = await withCheckedContinuation { continuation in - register(challenge: challenge) { fido2Attestation, exception in - continuation.resume(returning: (fido2Attestation: fido2Attestation, exception: exception)) - } - } - - if result.exception != nil { - throw result.exception! - } - - return result.fido2Attestation! - } else { - throw PasskeysSignerError.unexpected(code: PassKeyError.notSupported.rawValue, message: PassKeyError.notSupported.rawValue, error: nil) - } - } - - private func register(challenge: DfnsApi.UserRegistrationChallenge, completion: @escaping (DfnsApi.Fido2Attestation?, Error?) -> Void) { - let userId = challenge.user.id - let displayName = challenge.user.displayName - let challengeBase64url = Utils.base64URLUnescaped(challenge.challenge) - - passkey.register(self.relyingPartyId, challenge: challengeBase64url, displayName: displayName, userId: userId, securityKey: false, - resolve: { authResult in - let credentialInfo = DfnsApi.Fido2AttestationData( - attestationData: self.extractFromAuthResultValue(authResult, path: ["response", "rawAttestationObject"]), - clientData: self.extractFromAuthResultValue(authResult, path: ["response", "rawClientDataJSON"]), - credId: self.extractFromAuthResultValue(authResult, path: ["credentialID"]) - ) - let fido2Attestation = DfnsApi.Fido2Attestation(credentialInfo: credentialInfo, credentialKind: "Fido2") - completion(fido2Attestation, nil) - }, reject: { code, message, error in - let exception = PasskeysSignerError.unexpected(code: code, message: message, error: error) - completion(nil, exception) - }) - } - - public func sign(challenge: DfnsApi.UserActionChallenge) async throws -> DfnsApi.Fido2Assertion { - if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) { - let result = await withCheckedContinuation { continuation in - sign(challenge: challenge) { fido2Assertion, exception in - continuation.resume(returning: (fido2Assertion: fido2Assertion, exception: exception)) - } - } - - if result.exception != nil { - throw result.exception! - } - - return result.fido2Assertion! - } else { - throw PasskeysSignerError.unexpected(code: PassKeyError.notSupported.rawValue, message: PassKeyError.notSupported.rawValue, error: nil) - } - } - - private func sign(challenge: DfnsApi.UserActionChallenge, completion: @escaping (DfnsApi.Fido2Assertion?, Error?) -> Void) { - let challengeBase64url = Utils.base64URLUnescaped(challenge.challenge) - - passkey.authenticate(self.relyingPartyId, challenge: challengeBase64url, securityKey: false, resolve: { authResult in - let credentialAssertion = DfnsApi.Fido2AssertionData( - clientData: self.extractFromAuthResultValue(authResult, path: ["response", "rawClientDataJSON"]), - credId: self.extractFromAuthResultValue(authResult, path: ["credentialID"]), - signature: self.extractFromAuthResultValue(authResult, path: ["response", "signature"]), - authenticatorData: self.extractFromAuthResultValue(authResult, path: ["response", "rawAuthenticatorData"]), - userHandle: Utils.base64URLEscape((authResult["userID"] as! String).data(using: .utf8)!.base64EncodedString()) - ) - - let fido2Assertion = DfnsApi.Fido2Assertion(kind: "Fido2", credentialAssertion: credentialAssertion) - - completion(fido2Assertion, nil) - }, reject: { code, message, error in - let exception = PasskeysSignerError.unexpected(code: code, message: message, error: error) - completion(nil, exception) - }) - } - - private func extractFromAuthResultValue(_ authResult: NSDictionary, path: [String]) -> String { - var path = path - if path.count == 1 { - return Utils.base64URLEscape(authResult[path.removeFirst()] as! String) - } else { - return extractFromAuthResultValue(authResult[path.removeFirst()] as! NSDictionary, path: path) - } - } + private let passkey = Passkey() + + /** + The relying party ID identifies your application to users, when users create/use passkeys. (Read more [here](https://www.w3.org/TR/webauthn-2/#relying-party)). + It is a valid domain string identifying the WebAuthn Relying Party. In other words, its the domain your application is running on, which will be tied to the passkeys that users create. + We advise to use the root domain, not the full domain (eg `acme.com`, not `app.acme.com` nor `foo.app.acme.com`), that way, passkeys created + by your users can be re-used on other subdomains (eg. on `foo.acme.com` and `bar.acme.com`) in the future. Read more [here](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredentialCreationOptions#rp). + */ + private let relyingPartyId: String + + public init(relyingPartyId: String) { + self.relyingPartyId = relyingPartyId + } + + public func register(challenge: DfnsApi.UserRegistrationChallenge) async throws -> DfnsApi.Fido2Attestation { + let result = await withCheckedContinuation { continuation in + register(challenge: challenge) { fido2Attestation, exception in + continuation.resume(returning: (fido2Attestation: fido2Attestation, exception: exception)) + } + } + + if result.exception != nil { + throw result.exception! + } + + return result.fido2Attestation! + } + + private func register(challenge: DfnsApi.UserRegistrationChallenge, completion: @escaping (DfnsApi.Fido2Attestation?, Error?) -> Void) { + let userId = challenge.user.id + let displayName = challenge.user.displayName + let challengeBase64url = Utils.base64URLUnescaped(challenge.challenge) + + passkey.register(self.relyingPartyId, challenge: challengeBase64url, displayName: displayName, userId: userId, securityKey: false, + resolve: { authResult in + let credentialInfo = DfnsApi.Fido2AttestationData( + attestationData: self.extractFromAuthResultValue(authResult, path: ["response", "rawAttestationObject"]), + clientData: self.extractFromAuthResultValue(authResult, path: ["response", "rawClientDataJSON"]), + credId: self.extractFromAuthResultValue(authResult, path: ["credentialID"]) + ) + let fido2Attestation = DfnsApi.Fido2Attestation(credentialInfo: credentialInfo, credentialKind: "Fido2") + completion(fido2Attestation, nil) + }, reject: { code, message, error in + let exception = PasskeysSignerError.unexpected(code: code, message: message, error: error) + completion(nil, exception) + }) + } + + public func sign(challenge: DfnsApi.UserActionChallenge) async throws -> DfnsApi.Fido2Assertion { + let result = await withCheckedContinuation { continuation in + sign(challenge: challenge) { fido2Assertion, exception in + continuation.resume(returning: (fido2Assertion: fido2Assertion, exception: exception)) + } + } + + if result.exception != nil { + throw result.exception! + } + + return result.fido2Assertion! + + } + + private func sign(challenge: DfnsApi.UserActionChallenge, completion: @escaping (DfnsApi.Fido2Assertion?, Error?) -> Void) { + let challengeBase64url = Utils.base64URLUnescaped(challenge.challenge) + + passkey.authenticate(self.relyingPartyId, challenge: challengeBase64url, securityKey: false, resolve: { authResult in + let credentialAssertion = DfnsApi.Fido2AssertionData( + clientData: self.extractFromAuthResultValue(authResult, path: ["response", "rawClientDataJSON"]), + credId: self.extractFromAuthResultValue(authResult, path: ["credentialID"]), + signature: self.extractFromAuthResultValue(authResult, path: ["response", "signature"]), + authenticatorData: self.extractFromAuthResultValue(authResult, path: ["response", "rawAuthenticatorData"]), + userHandle: Utils.base64URLEscape((authResult["userID"] as! String).data(using: .utf8)!.base64EncodedString()) + ) + + let fido2Assertion = DfnsApi.Fido2Assertion(kind: "Fido2", credentialAssertion: credentialAssertion) + + completion(fido2Assertion, nil) + }, reject: { code, message, error in + let exception = PasskeysSignerError.unexpected(code: code, message: message, error: error) + completion(nil, exception) + }) + } + + private func extractFromAuthResultValue(_ authResult: NSDictionary, path: [String]) -> String { + var path = path + if path.count == 1 { + return Utils.base64URLEscape(authResult[path.removeFirst()] as! String) + } else { + return extractFromAuthResultValue(authResult[path.removeFirst()] as! NSDictionary, path: path) + } + } } From d08f93221b24828ed1005b2abbcd074c245229d1 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Sat, 22 Nov 2025 08:22:41 +0100 Subject: [PATCH 2/4] switch to using withCheckedThrowingContinuation and Result --- Sources/DfnsSdk/PasskeysSigner.swift | 43 ++++++++++++---------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/Sources/DfnsSdk/PasskeysSigner.swift b/Sources/DfnsSdk/PasskeysSigner.swift index 93ad867..a873cb4 100644 --- a/Sources/DfnsSdk/PasskeysSigner.swift +++ b/Sources/DfnsSdk/PasskeysSigner.swift @@ -26,20 +26,17 @@ public final class PasskeysSigner { } public func register(challenge: DfnsApi.UserRegistrationChallenge) async throws -> DfnsApi.Fido2Attestation { - let result = await withCheckedContinuation { continuation in - register(challenge: challenge) { fido2Attestation, exception in - continuation.resume(returning: (fido2Attestation: fido2Attestation, exception: exception)) + try await withCheckedThrowingContinuation { continuation in + register(challenge: challenge) { result in + continuation.resume(with: result) } } - - if result.exception != nil { - throw result.exception! - } - - return result.fido2Attestation! } - private func register(challenge: DfnsApi.UserRegistrationChallenge, completion: @escaping (DfnsApi.Fido2Attestation?, Error?) -> Void) { + private func register( + challenge: DfnsApi.UserRegistrationChallenge, + completion: @escaping (Result) -> Void + ) { let userId = challenge.user.id let displayName = challenge.user.displayName let challengeBase64url = Utils.base64URLUnescaped(challenge.challenge) @@ -52,29 +49,25 @@ public final class PasskeysSigner { credId: self.extractFromAuthResultValue(authResult, path: ["credentialID"]) ) let fido2Attestation = DfnsApi.Fido2Attestation(credentialInfo: credentialInfo, credentialKind: "Fido2") - completion(fido2Attestation, nil) + completion(.success(fido2Attestation)) }, reject: { code, message, error in let exception = PasskeysSignerError.unexpected(code: code, message: message, error: error) - completion(nil, exception) + completion(.failure(exception)) }) } public func sign(challenge: DfnsApi.UserActionChallenge) async throws -> DfnsApi.Fido2Assertion { - let result = await withCheckedContinuation { continuation in - sign(challenge: challenge) { fido2Assertion, exception in - continuation.resume(returning: (fido2Assertion: fido2Assertion, exception: exception)) + try await withCheckedThrowingContinuation { continuation in + sign(challenge: challenge) { result in + continuation.resume(with: result) } } - - if result.exception != nil { - throw result.exception! - } - - return result.fido2Assertion! - } - private func sign(challenge: DfnsApi.UserActionChallenge, completion: @escaping (DfnsApi.Fido2Assertion?, Error?) -> Void) { + private func sign( + challenge: DfnsApi.UserActionChallenge, + completion: @escaping (Result) -> Void + ) { let challengeBase64url = Utils.base64URLUnescaped(challenge.challenge) passkey.authenticate(self.relyingPartyId, challenge: challengeBase64url, securityKey: false, resolve: { authResult in @@ -88,10 +81,10 @@ public final class PasskeysSigner { let fido2Assertion = DfnsApi.Fido2Assertion(kind: "Fido2", credentialAssertion: credentialAssertion) - completion(fido2Assertion, nil) + completion(.success(fido2Assertion)) }, reject: { code, message, error in let exception = PasskeysSignerError.unexpected(code: code, message: message, error: error) - completion(nil, exception) + completion(.failure(exception)) }) } From 5a12cbb69be325b09b1acf9365580eb58e730fb0 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Sat, 22 Nov 2025 08:23:52 +0100 Subject: [PATCH 3/4] formatting --- Sources/DfnsSdk/PasskeysSigner.swift | 33 +++++++++++++++++----------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/Sources/DfnsSdk/PasskeysSigner.swift b/Sources/DfnsSdk/PasskeysSigner.swift index a873cb4..872b10d 100644 --- a/Sources/DfnsSdk/PasskeysSigner.swift +++ b/Sources/DfnsSdk/PasskeysSigner.swift @@ -41,19 +41,26 @@ public final class PasskeysSigner { let displayName = challenge.user.displayName let challengeBase64url = Utils.base64URLUnescaped(challenge.challenge) - passkey.register(self.relyingPartyId, challenge: challengeBase64url, displayName: displayName, userId: userId, securityKey: false, - resolve: { authResult in - let credentialInfo = DfnsApi.Fido2AttestationData( - attestationData: self.extractFromAuthResultValue(authResult, path: ["response", "rawAttestationObject"]), - clientData: self.extractFromAuthResultValue(authResult, path: ["response", "rawClientDataJSON"]), - credId: self.extractFromAuthResultValue(authResult, path: ["credentialID"]) - ) - let fido2Attestation = DfnsApi.Fido2Attestation(credentialInfo: credentialInfo, credentialKind: "Fido2") - completion(.success(fido2Attestation)) - }, reject: { code, message, error in - let exception = PasskeysSignerError.unexpected(code: code, message: message, error: error) - completion(.failure(exception)) - }) + passkey.register( + self.relyingPartyId, + challenge: challengeBase64url, + displayName: displayName, + userId: userId, + securityKey: false, + resolve: { authResult in + let credentialInfo = DfnsApi.Fido2AttestationData( + attestationData: self.extractFromAuthResultValue(authResult, path: ["response", "rawAttestationObject"]), + clientData: self.extractFromAuthResultValue(authResult, path: ["response", "rawClientDataJSON"]), + credId: self.extractFromAuthResultValue(authResult, path: ["credentialID"]) + ) + let fido2Attestation = DfnsApi.Fido2Attestation(credentialInfo: credentialInfo, credentialKind: "Fido2") + completion(.success(fido2Attestation)) + }, + reject: { code, message, error in + let exception = PasskeysSignerError.unexpected(code: code, message: message, error: error) + completion(.failure(exception)) + } + ) } public func sign(challenge: DfnsApi.UserActionChallenge) async throws -> DfnsApi.Fido2Assertion { From d5f691ac08458e2c3c5b09f1cb3720685673ef40 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Sun, 23 Nov 2025 11:31:40 +0100 Subject: [PATCH 4/4] Make SDK and demo work with Swift 6 --- DfnsDemo/DfnsDemo.xcodeproj/project.pbxproj | 8 +++-- DfnsDemo/DfnsDemo/Config.swift | 1 + DfnsDemo/DfnsDemo/ContentView.swift | 34 +++++++++++++-------- DfnsDemo/DfnsDemo/DfnsDemoApp.swift | 20 +++++++----- DfnsDemo/DfnsDemo/MyBusinessLogic.swift | 11 ++++--- DfnsDemo/DfnsDemo/MyServer.swift | 7 +++-- Package.swift | 4 +-- Sources/DfnsSdk/PasskeysSigner.swift | 1 + 8 files changed, 54 insertions(+), 32 deletions(-) diff --git a/DfnsDemo/DfnsDemo.xcodeproj/project.pbxproj b/DfnsDemo/DfnsDemo.xcodeproj/project.pbxproj index b103d71..3e33b68 100644 --- a/DfnsDemo/DfnsDemo.xcodeproj/project.pbxproj +++ b/DfnsDemo/DfnsDemo.xcodeproj/project.pbxproj @@ -227,7 +227,8 @@ SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; + SWIFT_STRICT_CONCURRENCY = complete; + SWIFT_VERSION = 6.0; }; name = Debug; }; @@ -283,7 +284,8 @@ MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_VERSION = 5.0; + SWIFT_STRICT_CONCURRENCY = complete; + SWIFT_VERSION = 6.0; VALIDATE_PRODUCT = YES; }; name = Release; @@ -315,6 +317,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_STRICT_CONCURRENCY = minimal; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -346,6 +349,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_STRICT_CONCURRENCY = minimal; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; diff --git a/DfnsDemo/DfnsDemo/Config.swift b/DfnsDemo/DfnsDemo/Config.swift index 0b2537c..1178b5e 100644 --- a/DfnsDemo/DfnsDemo/Config.swift +++ b/DfnsDemo/DfnsDemo/Config.swift @@ -1,3 +1,4 @@ +// The url below should match the value in .entitlements file enum Config { public static let serverUrl: String = "https://airedale-finer-baboon.ngrok-free.app" public static let passkeyRelyingPartyId: String = "airedale-finer-baboon.ngrok-free.app" diff --git a/DfnsDemo/DfnsDemo/ContentView.swift b/DfnsDemo/DfnsDemo/ContentView.swift index 62f3f64..04edf19 100644 --- a/DfnsDemo/DfnsDemo/ContentView.swift +++ b/DfnsDemo/DfnsDemo/ContentView.swift @@ -1,8 +1,8 @@ import SwiftUI struct ContentView: View { - @ObservedObject var userConfig: UserConfig - @ObservedObject var myBusinessLogic: MyBusinessLogic + @Binding var userConfig: UserConfig + let myBusinessLogic: MyBusinessLogic var body: some View { NavigationView { @@ -31,7 +31,7 @@ struct ContentView: View { Text("Your customers, either new or existing, must register with Dfns first and have credential(s) in our system in order to own and be able to interact with their blockchain wallets.\n\nThe delegated registration flow allows you to initiate and and complete the registration process on your customers behalf, without them being aware that the wallets infrastructure is powered by Dfns, i.e. they will not receive an registration email from Dfns directly unlike the normal registration process for your employees. Their WebAuthn credentials are still completely under their control.") - NavigationLink("Go to Delegated Registration", destination: DelegatedRegistrationView(userConfig: userConfig, myBusinessLogic: myBusinessLogic)).buttonStyle(.borderedProminent).padding(.vertical, 15) + NavigationLink("Go to Delegated Registration", destination: DelegatedRegistrationView(userConfig: $userConfig, myBusinessLogic: myBusinessLogic)).buttonStyle(.borderedProminent).padding(.vertical, 15) /// STEP 2 @@ -42,7 +42,7 @@ struct ContentView: View { Text("The delegated signing flow does not need the end user sign with the WebAuthn credential. The login can be performed on the server side transparent to the end users and obtain a readonly auth token. For example, your server can choose to automatically login the end users upon the completion of delegated registration. In this tutorial, this step is shown as explicit in order to more clearly demonstrate how the interaction works.") - NavigationLink("Go to Delegated Login", destination: DelegatedLoginView(userConfig: userConfig, myBusinessLogic: myBusinessLogic)).buttonStyle(.borderedProminent).padding(.vertical, 15) + NavigationLink("Go to Delegated Login", destination: DelegatedLoginView(userConfig: $userConfig, myBusinessLogic: myBusinessLogic)).buttonStyle(.borderedProminent).padding(.vertical, 15) /// STEP 3 @@ -69,8 +69,8 @@ struct ContentView: View { } struct DelegatedRegistrationView: View { - @ObservedObject var userConfig: UserConfig - @ObservedObject var myBusinessLogic: MyBusinessLogic + @Binding var userConfig: UserConfig + let myBusinessLogic: MyBusinessLogic @State var registerResponse: String = "" var body: some View { @@ -90,13 +90,20 @@ struct DelegatedRegistrationView: View { .frame(maxWidth: .infinity, alignment: .leading) TextField("Choose a username", text: $userConfig.email).textFieldStyle(.roundedBorder).padding(.vertical) + .textFieldStyle(.roundedBorder) + .padding(.vertical) + .keyboardType(.emailAddress) + .textInputAutocapitalization(.never) Button("Register EndUser") { Task { let result = await myBusinessLogic.registerUser(userConfig: userConfig) registerResponse = result } - }.buttonStyle(.borderedProminent).frame(maxWidth: .infinity).padding(.bottom) + } + .buttonStyle(.borderedProminent) + .frame(maxWidth: .infinity) + .padding(.bottom) JSONText(registerResponse) }.padding() @@ -106,8 +113,8 @@ struct DelegatedRegistrationView: View { } struct DelegatedLoginView: View { - @ObservedObject var userConfig: UserConfig - @ObservedObject var myBusinessLogic: MyBusinessLogic + @Binding var userConfig: UserConfig + let myBusinessLogic: MyBusinessLogic @State var loginResponse: String = "" var body: some View { @@ -123,7 +130,10 @@ struct DelegatedLoginView: View { Text("This auth token is readonly and needs to be cached and passed along with all requests interacting with the Dfns API. To clearly demonstrate all the necessary components for each step, this example will cache the auth token in the application context and send it back with every sequently request to the server. You should however choose a more secure caching method.").padding(.vertical) - TextField("Enter the username", text: $userConfig.email).textFieldStyle(.roundedBorder) + TextField("Enter the username", text: $userConfig.email) + .textFieldStyle(.roundedBorder) + .keyboardType(.emailAddress) + .textInputAutocapitalization(.never) Button("Login EndUser") { Task { @@ -142,8 +152,8 @@ struct DelegatedLoginView: View { } struct EndUserWalletsView: View { - @ObservedObject var userConfig: UserConfig - @ObservedObject var myBusinessLogic: MyBusinessLogic + let userConfig: UserConfig + let myBusinessLogic: MyBusinessLogic @State var walletResponse: String = "" @State var messageToSign: String = "" @State var signingResponse: String = "" diff --git a/DfnsDemo/DfnsDemo/DfnsDemoApp.swift b/DfnsDemo/DfnsDemo/DfnsDemoApp.swift index 57cad3a..cb54da6 100644 --- a/DfnsDemo/DfnsDemo/DfnsDemoApp.swift +++ b/DfnsDemo/DfnsDemo/DfnsDemoApp.swift @@ -1,25 +1,29 @@ -@preconcurrency import DfnsSdk +import DfnsSdk import SwiftUI +import Observation -class UserConfig: ObservableObject { +@Observable +final class UserConfig { + var authToken: String? + var email: String + init() { - email = "" + email = "" } - @Published var authToken: String? - @Published var email: String + } @main struct DfnsDemoApp: App { - @StateObject private var userConfig = UserConfig() - @StateObject private var myBusinessLogic = MyBusinessLogic( + @State private var userConfig = UserConfig() + @State private var myBusinessLogic = MyBusinessLogic( url: Config.serverUrl, passkeyRelyingPartyId: Config.passkeyRelyingPartyId ) var body: some Scene { WindowGroup { - ContentView(userConfig: userConfig, myBusinessLogic: myBusinessLogic) + ContentView(userConfig: $userConfig, myBusinessLogic: myBusinessLogic) } } } diff --git a/DfnsDemo/DfnsDemo/MyBusinessLogic.swift b/DfnsDemo/DfnsDemo/MyBusinessLogic.swift index 8aeecb3..304979e 100644 --- a/DfnsDemo/DfnsDemo/MyBusinessLogic.swift +++ b/DfnsDemo/DfnsDemo/MyBusinessLogic.swift @@ -1,13 +1,16 @@ -@preconcurrency import DfnsSdk +import DfnsSdk import Foundation +import Observation /** Controller that is doing the interface between the UI, the Demo Server and the Passkey Signer */ -final class MyBusinessLogic: ObservableObject { +@Observable +@MainActor +final class MyBusinessLogic: @unchecked Sendable { private var passkeyRelyingPartyId: String - private var myServer: MyServer - private var passkeysSigner: PasskeysSigner + private let myServer: MyServer + private let passkeysSigner: PasskeysSigner init(url: String, passkeyRelyingPartyId: String) { self.passkeyRelyingPartyId = passkeyRelyingPartyId diff --git a/DfnsDemo/DfnsDemo/MyServer.swift b/DfnsDemo/DfnsDemo/MyServer.swift index 4aa7eae..190ccc3 100644 --- a/DfnsDemo/DfnsDemo/MyServer.swift +++ b/DfnsDemo/DfnsDemo/MyServer.swift @@ -1,11 +1,12 @@ -@preconcurrency import DfnsSdk +import DfnsSdk import Foundation /** Implement the API of the server */ -final class MyServer { - private var url: String = "" +@MainActor +final class MyServer: @unchecked Sendable { + private let url: String init(url: String) { self.url = url diff --git a/Package.swift b/Package.swift index 927d915..5f8e8ee 100644 --- a/Package.swift +++ b/Package.swift @@ -1,8 +1,6 @@ -// swift-tools-version: 5.10 +// swift-tools-version: 6.2 // The swift-tools-version declares the minimum version of Swift required to build this package. -// later change swift-tools-version to 6.2 - import PackageDescription let package = Package( diff --git a/Sources/DfnsSdk/PasskeysSigner.swift b/Sources/DfnsSdk/PasskeysSigner.swift index 872b10d..0d1fae9 100644 --- a/Sources/DfnsSdk/PasskeysSigner.swift +++ b/Sources/DfnsSdk/PasskeysSigner.swift @@ -10,6 +10,7 @@ public enum PasskeysSignerError: Error { Wrapper class for the Passkey class imported from the `react-native-passkey library` Converts completion handlers into async functions and make the necessary conversion to work with Dfns API */ +@MainActor public final class PasskeysSigner { private let passkey = Passkey()