1import { CodedError, EventEmitter, Subscription, UnavailabilityError } from 'expo-modules-core';
2
3import {
4  AppleAuthenticationCredential,
5  AppleAuthenticationCredentialState,
6  AppleAuthenticationOperation,
7  AppleAuthenticationRefreshOptions,
8  AppleAuthenticationSignInOptions,
9  AppleAuthenticationSignOutOptions,
10} from './AppleAuthentication.types';
11import ExpoAppleAuthentication from './ExpoAppleAuthentication';
12
13// @needsAudit
14/**
15 * Determine if the current device's operating system supports Apple authentication.
16 * @return A promise that fulfills with `true` if the system supports Apple authentication, and `false` otherwise.
17 */
18export async function isAvailableAsync(): Promise<boolean> {
19  if (!ExpoAppleAuthentication || !ExpoAppleAuthentication.isAvailableAsync) {
20    return false;
21  }
22  return ExpoAppleAuthentication.isAvailableAsync();
23}
24
25// @needsAudit
26/**
27 * Sends a request to the operating system to initiate the Apple authentication flow, which will
28 * present a modal to the user over your app and allow them to sign in.
29 *
30 * You can request access to the user's full name and email address in this method, which allows you
31 * to personalize your UI for signed in users. However, users can deny access to either or both
32 * of these options at runtime.
33 *
34 * Additionally, you will only receive Apple Authentication Credentials the first time users sign
35 * into your app, so you must store it for later use. It's best to store this information either
36 * server-side, or using [SecureStore](./securestore), so that the data persists across app installs.
37 * You can use [`AppleAuthenticationCredential.user`](#appleauthenticationcredential) to identify
38 * the user, since this remains the same for apps released by the same developer.
39 *
40 * @param options An optional [`AppleAuthenticationSignInOptions`](#appleauthenticationsigninoptions) object
41 * @return A promise that fulfills with an [`AppleAuthenticationCredential`](#appleauthenticationcredential)
42 * object after a successful authentication, and rejects with `ERR_REQUEST_CANCELED` if the user cancels the
43 * sign-in operation.
44 */
45export async function signInAsync(
46  options?: AppleAuthenticationSignInOptions
47): Promise<AppleAuthenticationCredential> {
48  if (!ExpoAppleAuthentication || !ExpoAppleAuthentication.requestAsync) {
49    throw new UnavailabilityError('expo-apple-authentication', 'signInAsync');
50  }
51  const requestOptions = {
52    ...options,
53    requestedOperation: AppleAuthenticationOperation.LOGIN,
54  };
55  const credential = await ExpoAppleAuthentication.requestAsync(requestOptions);
56  if (!credential.authorizationCode || !credential.identityToken || !credential.user) {
57    throw new CodedError(
58      'ERR_REQUEST_FAILED',
59      'The credential returned by `signInAsync` is missing one or more required fields.'
60    );
61  }
62  return credential;
63}
64
65// @needsAudit
66/**
67 * An operation that refreshes the logged-in user’s credentials.
68 * Calling this method will show the sign in modal before actually refreshing the user credentials.
69 *
70 * @param options An [`AppleAuthenticationRefreshOptions`](#appleauthenticationrefreshoptions) object
71 * @returns A promise that fulfills with an [`AppleAuthenticationCredential`](#appleauthenticationcredential)
72 * object after a successful authentication, and rejects with `ERR_REQUEST_CANCELED` if the user cancels the
73 * refresh operation.
74 */
75export async function refreshAsync(
76  options: AppleAuthenticationRefreshOptions
77): Promise<AppleAuthenticationCredential> {
78  if (!ExpoAppleAuthentication || !ExpoAppleAuthentication.requestAsync) {
79    throw new UnavailabilityError('expo-apple-authentication', 'refreshAsync');
80  }
81  const requestOptions = {
82    ...options,
83    requestedOperation: AppleAuthenticationOperation.REFRESH,
84  };
85  const credential = await ExpoAppleAuthentication.requestAsync(requestOptions);
86  if (!credential.authorizationCode || !credential.identityToken || !credential.user) {
87    throw new CodedError(
88      'ERR_REQUEST_FAILED',
89      'The credential returned by `refreshAsync` is missing one or more required fields.'
90    );
91  }
92  return credential;
93}
94
95// @needsAudit
96/**
97 * An operation that ends the authenticated session.
98 * Calling this method will show the sign in modal before actually signing the user out.
99 *
100 * It is not recommended to use this method to sign out the user as it works counterintuitively.
101 * Instead of using this method it is recommended to simply clear all the user's data collected
102 * from using [`signInAsync`](./#signinasync) or [`refreshAsync`](./#refreshasync) methods.
103 *
104 * @param options An [`AppleAuthenticationSignOutOptions`](#appleauthenticationsignoutoptions) object
105 * @returns A promise that fulfills with an [`AppleAuthenticationCredential`](#appleauthenticationcredential)
106 * object after a successful authentication, and rejects with `ERR_REQUEST_CANCELED` if the user cancels the
107 * sign-out operation.
108 */
109export async function signOutAsync(
110  options: AppleAuthenticationSignOutOptions
111): Promise<AppleAuthenticationCredential> {
112  if (!ExpoAppleAuthentication || !ExpoAppleAuthentication.requestAsync) {
113    throw new UnavailabilityError('expo-apple-authentication', 'signOutAsync');
114  }
115  const requestOptions = {
116    ...options,
117    requestedOperation: AppleAuthenticationOperation.LOGOUT,
118  };
119  return ExpoAppleAuthentication.requestAsync(requestOptions);
120}
121
122// @needsAudit
123/**
124 * Queries the current state of a user credential, to determine if it is still valid or if it has been revoked.
125 * > **Note:** This method must be tested on a real device. On the iOS simulator it always throws an error.
126 *
127 * @param user The unique identifier for the user whose credential state you'd like to check.
128 * This should come from the user field of an [`AppleAuthenticationCredential`](#appleauthenticationcredentialstate) object.
129 * @return A promise that fulfills with an [`AppleAuthenticationCredentialState`](#appleauthenticationcredentialstate)
130 * value depending on the state of the credential.
131 */
132export async function getCredentialStateAsync(
133  user: string
134): Promise<AppleAuthenticationCredentialState> {
135  if (!ExpoAppleAuthentication || !ExpoAppleAuthentication.getCredentialStateAsync) {
136    throw new UnavailabilityError('expo-apple-authentication', 'getCredentialStateAsync');
137  }
138  return ExpoAppleAuthentication.getCredentialStateAsync(user);
139}
140
141const ExpoAppleAuthenticationEventEmitter = new EventEmitter(ExpoAppleAuthentication);
142
143// @docsMissing
144export function addRevokeListener(listener: () => void): Subscription {
145  return ExpoAppleAuthenticationEventEmitter.addListener('Expo.appleIdCredentialRevoked', listener);
146}
147
148export { Subscription };
149