Skip to content

Instantly share code, notes, and snippets.

@nandorojo
Last active December 9, 2021 17:47
Show Gist options
  • Save nandorojo/f1ccae6711f37e1352583f6a557b8cb8 to your computer and use it in GitHub Desktop.
Save nandorojo/f1ccae6711f37e1352583f6a557b8cb8 to your computer and use it in GitHub Desktop.
Expo Apple Auth
import type {
AppleAuthenticationButtonStyle,
AppleAuthenticationButtonType,
AppleAuthenticationUserDetectionStatus,
AppleAuthenticationButtonProps,
AppleAuthenticationCredential,
AppleAuthenticationSignInOptions,
} from 'expo-apple-authentication'
import { Image, Platform, Pressable, StyleSheet, Text } from 'react-native'
import { useEffect } from 'react'
// downloaded from https://developer.apple.com/design/resources/
import logoWhite from './logos/Logo - SIWA - Logo-only - [email protected]'
import logoBlack from './logos/Logo - SIWA - Logo-only - [email protected]'
const signInAsync = async (
options?: AppleAuthenticationSignInOptions
): Promise<AppleAuthenticationCredential> => {
const { nonce, requestedScopes, state } = options || {}
const scope = requestedScopes?.join(' ')
const { authorization, user } = await AppleID.auth.signIn({
nonce,
scope,
state,
})
return {
authorizationCode: authorization.code,
identityToken: authorization.id_token,
state: authorization.state,
email: user?.email || null,
fullName: {
familyName: user?.name.lastName || null,
givenName: user?.name.firstName || null,
nickname: null,
middleName: null,
namePrefix: null,
nameSuffix: null,
},
realUserStatus: AppleAuthenticationUserDetectionStatus.UNSUPPORTED,
user: '', // TODO document that this is iOS-only
}
}
// https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple/overview/buttons/#custom-buttons-with-a-logo-and-text
const getFontSize = (buttonHeight: number) => Math.round(buttonHeight * 0.43)
// https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple/overview/buttons/
const buttonHeight = 44
const buttonWidth = 200
const AppleAuthenticationButton = (
props: AppleAuthenticationButtonProps & {
web?: Parameters<typeof AppleID.auth.init>[0]
}
) => {
const {
onPress,
buttonStyle,
buttonType,
cornerRadius = 5,
web,
style,
accessibilityRole,
...rest
} = props
useEffect(() => {
if (Platform.OS === 'web') {
const script = document.createElement('script')
script.src =
'https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js'
document.body.appendChild(script)
const onLoad = () => {
if (!web) {
console.error('AppleAuthenticationButton is missing "web" prop.')
}
AppleID.auth.init(web || {})
}
script.addEventListener('load', onLoad)
return () => {
script.removeEventListener('load', onLoad)
document.body.removeChild(script)
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
let flatStyle = StyleSheet.flatten([style]) as any
flatStyle = flatStyle && typeof flatStyle == 'object' ? flatStyle : undefined
const height: number = flatStyle?.height || buttonHeight
const fontSize = getFontSize(height)
const lineHeight = height
// https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple/overview/buttons/
return (
<Pressable
// @ts-expect-error Pressable & View have different accessiblityRole types (View's is just string)
accessibilityRole={accessibilityRole}
style={[
styles.button,
styles[buttonStyle],
{ borderRadius: cornerRadius },
style,
]}
onPress={onPress}
{...rest}
>
<Image
source={
buttonStyle === AppleAuthenticationButtonStyle.BLACK
? logoWhite
: logoBlack
}
height={height}
width={height}
style={{
height,
width: height,
}}
/>
<Text
style={[
textStyles.text,
{ lineHeight, fontSize },
textStyles[buttonType],
]}
>
{ButtonCopy[buttonType]}
</Text>
</Pressable>
)
}
export const ButtonCopy = {
[AppleAuthenticationButtonType.CONTINUE]: 'Continue with Apple',
[AppleAuthenticationButtonType.SIGN_IN]: 'Sign in with Apple',
[AppleAuthenticationButtonType.SIGN_UP]: 'Sign up with Apple',
}
const styles = StyleSheet.create({
button: {
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
width: buttonWidth,
height: buttonHeight,
minWidth: 140,
minHeight: 30,
overflow: 'hidden',
},
[AppleAuthenticationButtonStyle.WHITE]: {
backgroundColor: '#fff',
},
[AppleAuthenticationButtonStyle.WHITE_OUTLINE]: {
backgroundColor: '#fff',
borderWidth: 1,
borderColor: '#000',
},
[AppleAuthenticationButtonStyle.BLACK]: {
backgroundColor: '#000',
},
})
const textStyles = StyleSheet.create({
text: {
fontSize: 15,
},
[AppleAuthenticationButtonStyle.WHITE]: {
color: '#000',
},
[AppleAuthenticationButtonStyle.WHITE_OUTLINE]: {
color: '#000',
},
[AppleAuthenticationButtonStyle.BLACK]: {
color: '#fff',
},
})
export { AppleAuthenticationButton, signInAsync }
// Type definitions for non-npm package Apple Sign in API 1.5
// Project: https://developer.apple.com/documentation/signinwithapplejs
// Definitions by: Julius Lungys <https://github.com/voidpumpkin>
// Koen Punt <https://github.com/koenpunt>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
declare const AppleID: AppleSignInAPI.AppleID
declare namespace AppleSignInAPI {
// https://developer.apple.com/documentation/signinwithapplejs/authorizationi
interface AuthorizationI {
code: string
id_token: string
state: string
nonce?: string | undefined
}
// https://developer.apple.com/documentation/signinwithapplejs/namei
interface NameI {
firstName: string
lastName: string
}
// https://developer.apple.com/documentation/signinwithapplejs/signinerrori
interface SignInErrorI {
error: string
}
// https://developer.apple.com/documentation/signinwithapplejs/signinresponsei
interface SignInResponseI {
authorization: AuthorizationI
user?: UserI | undefined
}
// https://developer.apple.com/documentation/signinwithapplejs/useri
interface UserI {
email: string
name: NameI
}
// https://developer.apple.com/documentation/signinwithapplejs/authi
interface AuthI {
init: (config: ClientConfigI) => void
signIn: (signInConfig?: ClientConfigI) => Promise<SignInResponseI>
renderButton: () => void
}
// https://developer.apple.com/documentation/signinwithapplejs/clientconfigi
interface ClientConfigI {
clientId?: string | undefined
redirectURI?: string | undefined
scope?: string | undefined
state?: string | undefined
nonce?: string | undefined
usePopup?: boolean | undefined
}
interface AppleID {
auth: AuthI
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment