1 import AuthenticationServices
2 import ABI49_0_0ExpoModulesCore
3 
4 // A set of pending requests used to retain them until the callback is called
5 var pendingRequests = Set<AppleAuthenticationRequest>()
6 
7 final class AppleAuthenticationRequest: NSObject, ASAuthorizationControllerPresentationContextProviding, ASAuthorizationControllerDelegate {
8   typealias AuthenticationResponse = [AnyHashable: Any]
9   typealias AuthenticationRequestCallback = (AuthenticationResponse?, Exception?) -> Void
10 
11   let options: AppleAuthenticationRequestOptions
12   var callback: AuthenticationRequestCallback?
13   var authController: ASAuthorizationController?
14 
15   init(options: AppleAuthenticationRequestOptions) {
16     self.options = options
17   }
18 
19   func performRequest(callback: @escaping AuthenticationRequestCallback) {
20     self.callback = callback
21 
22     do {
23       let appleIdProvider = ASAuthorizationAppleIDProvider()
24       let request = appleIdProvider.createRequest()
25 
26       request.requestedScopes = try scopesFromInts(options.requestedScopes)
27       request.requestedOperation = try operationFromInt(options.requestedOperation)
28 
29       request.user = options.user
30       request.state = options.state
31       request.nonce = options.nonce
32 
33       authController = ASAuthorizationController(authorizationRequests: [request])
34       authController?.presentationContextProvider = self
35       authController?.delegate = self
36 
37       // Retain pending request
38       pendingRequests.insert(self)
39 
40       authController?.performRequests()
41     } catch {
42       self.callback?(nil, error as? Exception)
43       self.callback = nil
44     }
45   }
46 
47   // MARK: - ASAuthorizationControllerDelegate
48 
49   func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
50     let credential = authorization.credential as? ASAuthorizationAppleIDCredential
51     let response: AuthenticationResponse = [
52       "fullName": nameComponentsToDict(credential?.fullName),
53       "email": credential?.email,
54       "user": credential?.user,
55       "realUserStatus": realUserStatusToInt(credential?.realUserStatus),
56       "state": credential?.state,
57       "authorizationCode": dataToString(credential?.authorizationCode),
58       "identityToken": dataToString(credential?.identityToken)
59     ]
60 
61     callback?(response, nil)
62     callback = nil
63 
64     // Release pending request
65     pendingRequests.remove(self)
66   }
67 
68   func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
69     if let error = error as? ASAuthorizationError {
70       log.error("Apple authorization failed: \(error.localizedDescription)")
71       callback?(nil, exceptionForAuthorizationError(error))
72     } else {
73       callback?(nil, RequestUnknownException())
74     }
75     callback = nil
76 
77     // Release pending request
78     pendingRequests.remove(self)
79   }
80 
81   // MARK: - ASAuthorizationControllerPresentationContextProviding
82 
83   func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
84     guard let window = UIApplication.shared.keyWindow else {
85       fatalError("Unable to present authentication modal because UIApplication.shared.keyWindow is not available")
86     }
87     return window
88   }
89 }
90