feat(Auth): example apple login

This commit is contained in:
Timothy Z. 2025-03-24 16:18:29 +02:00
parent 970538b087
commit f5fb2ed37a
4 changed files with 138 additions and 2 deletions

View file

@ -15,6 +15,7 @@
<div id="app"></div>
<%= htmlWebpackPlugin.tags.bodyTags %>
<script src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>
<script async src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script>
</body>
</html>

View file

@ -12,6 +12,8 @@ const { Button, Image, Checkbox } = require('stremio/components');
const CredentialsTextInput = require('./CredentialsTextInput');
const PasswordResetModal = require('./PasswordResetModal');
const useFacebookLogin = require('./useFacebookLogin');
const { default: useAppleLogin } = require('./useAppleLogin');
const styles = require('./styles');
const SIGNUP_FORM = 'signup';
@ -22,6 +24,7 @@ const Intro = ({ queryParams }) => {
const { t } = useTranslation();
const routeFocused = useRouteFocused();
const [startFacebookLogin, stopFacebookLogin] = useFacebookLogin();
const [startAppleLogin, stopAppleLogin] = useAppleLogin();
const emailRef = React.useRef(null);
const passwordRef = React.useRef(null);
const confirmPasswordRef = React.useRef(null);
@ -106,6 +109,32 @@ const Intro = ({ queryParams }) => {
stopFacebookLogin();
closeLoaderModal();
}, []);
const loginWithApple = React.useCallback(() => {
openLoaderModal();
startAppleLogin()
.then(({ email, password }) => {
core.transport.dispatch({
action: 'Ctx',
args: {
action: 'Authenticate',
args: {
type: 'Login',
email,
password,
apple: true
}
}
});
})
.catch((error) => {
closeLoaderModal();
dispatch({ type: 'error', error: error.message });
});
}, []);
const cancelLoginWithApple = React.useCallback(() => {
stopAppleLogin();
closeLoaderModal();
}, []);
const loginWithEmail = React.useCallback(() => {
if (typeof state.email !== 'string' || state.email.length === 0 || !emailRef.current.validity.valid) {
dispatch({ type: 'error', error: 'Invalid email' });
@ -336,7 +365,7 @@ const Intro = ({ queryParams }) => {
</div>
}
{
state.error.length > 0 ?
state.error && state.error.length > 0 ?
<div ref={errorRef} className={styles['error-message']}>{state.error}</div>
:
null
@ -350,6 +379,10 @@ const Intro = ({ queryParams }) => {
<Icon className={styles['icon']} name={'facebook'} />
<div className={styles['label']}>Continue with Facebook</div>
</Button>
<Button className={classnames(styles['form-button'], styles['apple-button'])} onClick={loginWithApple}>
<Icon className={styles['icon']} name={'apple'} />
<div className={styles['label']}>Continue with Apple</div>
</Button>
{
state.form === SIGNUP_FORM ?
<Button className={classnames(styles['form-button'], styles['login-form-button'])} onClick={switchFormOnClick}>
@ -388,7 +421,7 @@ const Intro = ({ queryParams }) => {
<div className={styles['loader-container']}>
<Icon className={styles['icon']} name={'person'} />
<div className={styles['label']}>Authenticating...</div>
<Button className={styles['button']} onClick={cancelLoginWithFacebook}>
<Button className={styles['button']} onClick={cancelLoginWithFacebook && cancelLoginWithApple}>
{t('BUTTON_CANCEL')}
</Button>
</div>

View file

@ -0,0 +1,80 @@
import { useCallback, useRef } from 'react';
type AppleLoginResponse = {
email: string;
password: string;
};
type AppleSignInResponse = {
authorization: {
code: string;
id_token: string;
state: string;
};
user: string;
email?: string;
};
const CLIENT_ID = 'com.stremio.one';
const useAppleLogin = (): [() => Promise<AppleLoginResponse>, () => void] => {
const started = useRef(false);
const start = useCallback((): Promise<AppleLoginResponse> => {
return new Promise((resolve, reject) => {
if (typeof window.AppleID === 'undefined') {
reject(new Error('Apple Sign-In not loaded'));
return;
}
if (started.current) {
reject(new Error('Apple login already in progress'));
return;
}
started.current = true;
window.AppleID.auth.init({
clientId: CLIENT_ID,
scope: 'name email',
redirectURI: window.location.origin,
state: 'signin',
usePopup: true
});
window.AppleID.auth.signIn()
.then((response: AppleSignInResponse) => {
if (response.authorization) {
const userEmail = response.email || response.user;
if (!userEmail) {
reject(new Error('No email received from Apple'));
return;
}
resolve({
email: userEmail as string,
password: response.authorization.id_token
});
} else {
reject(new Error('No authorization received from Apple'));
}
})
.catch((error: Error) => {
console.error('Error during Apple Sign-In:', error);
reject(error);
})
.finally(() => {
started.current = false;
});
});
}, []);
const stop = useCallback(() => {
started.current = false;
}, []);
return [start, stop];
};
export default useAppleLogin;

22
src/types/global.d.ts vendored
View file

@ -26,6 +26,28 @@ interface Chrome {
declare global {
var qt: Qt | undefined;
var chrome: Chrome | undefined;
interface Window {
AppleID: {
auth: {
init: (config: {
clientId: string;
scope: string;
redirectURI: string;
state: string;
usePopup: boolean;
}) => void;
signIn: () => Promise<{
authorization: {
code: string;
id_token: string;
state: string;
};
user: string;
email?: string;
}>;
};
};
}
}
export {};