snapauthapp / sdk-swift

Swift SDK for SnapAuth (all Apple platforms)
https://www.snapauth.app
BSD 3-Clause "New" or "Revised" License
1 stars 0 forks source link

Initial version #1

Closed Firehed closed 4 months ago

Firehed commented 4 months ago

This creates a basic iOS/macOS (/tvOS/visionOS?) SDK for integrating SnapAuth.

So far:

It's been tested on-device for macOS and iOS, and in simulators for visionOS (seems ok-ish given the limitations) and tvOS (basically non-functional, but I think this is a simulator problem)

Logging is a hot mess, error handling needs (a lot of) work, and a lot of the internal names are pretty goofy. I'll file issues for all of that.

BUT the core integration experience is pretty decent:

I'd prefer a more direct async/await approach, but this is probably the more native experience and trying to work through threading the ASAuthorization callbacks back into async/await is probably not a good time.


import SwiftUI
import AuthenticationServices
import SnapAuth

struct ContentView: View {

    let snapAuth = SnapAuth(
        publishableKey: "pubkey_f73c62d953397a45edbf1e6a7a1ce6dc41d69a31")

    @State private var username: String = ""
    @State private var resultText: String = ""
    @State private var running = false

    var body: some View {
        VStack {
            Text("SnapAuth Example")
                .font(.title)

            Spacer()

            TextField("Username", text: $username)
                .textContentType(/*@START_MENU_TOKEN@*/.username/*@END_MENU_TOKEN@*/)
                .padding()

            Button("Sign In", systemImage: "person.badge.key") {
                running = true
                signIn()
            }.padding()

            Button("Register", systemImage: "person.badge.key.fill") {
                running = true
                register()
            }.padding()

            Text("Auth token: \(resultText)")

            if running {
                ProgressView()
            }

        }
//        .onAppear(perform: autoFill)
        .padding()
        .background(.purple)

    }

    func autoFill() {
        #if os(iOS)
        Task {
            snapAuth.delegate = self
            await snapAuth.handleAutoFill()
        }
        #endif
    }

    func register() {
        Task {
            snapAuth.delegate = self
            await snapAuth.startRegister(name: username)
        }
    }

    func signIn() {
        Task {
            snapAuth.delegate = self
            await snapAuth.startAuth(.handle(username))

        }
    }
}

extension ContentView: SnapAuthDelegate {

    func snapAuth(didFinishAuthentication result: SnapAuthResult) async {
        running = false
        guard case .success(let auth) = result else {
            resultText = "AUTH FAILED"
            return
        }
        resultText = "Auth succeeded: \(auth.token)"
    }

    func snapAuth(didFinishRegistration result: SnapAuthResult) async {
        running = false
        switch result {
        case .success(let registration):
            resultText = "Reg success \(registration.token)"
        case .failure(let error):
            resultText = "REG FAILED"
        }
    }
}

Specifically with loading it like this in SwiftUI and a single view, it's a bit error-prone, since you can easily forget to bind the delegate - and then everything silently fails.

There are also some Apple bugs where the internal ASAuthorization delegate methods aren't called in some failure scenarios, and the whole domain linking process is a pain. They're a bit tricky to repro but I'll file radars for all of them once I get it figured out.