mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-01-11 22:40:31 +00:00
Merge pull request #898 from Stremio/fix/apple-login-shell
Some checks failed
Build / build (push) Has been cancelled
Some checks failed
Build / build (push) Has been cancelled
Shell: Fix Apple ID login
This commit is contained in:
commit
aeb9265e3b
5 changed files with 52 additions and 115 deletions
10
package-lock.json
generated
10
package-lock.json
generated
|
|
@ -23,7 +23,6 @@
|
|||
"filter-invalid-dom-props": "3.0.1",
|
||||
"hat": "^0.0.3",
|
||||
"i18next": "^24.0.5",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"langs": "github:Stremio/nodejs-langs",
|
||||
"lodash.debounce": "4.0.8",
|
||||
"lodash.intersection": "4.4.0",
|
||||
|
|
@ -10235,15 +10234,6 @@
|
|||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jwt-decode": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
|
||||
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@
|
|||
"filter-invalid-dom-props": "3.0.1",
|
||||
"hat": "^0.0.3",
|
||||
"i18next": "^24.0.5",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"langs": "github:Stremio/nodejs-langs",
|
||||
"lodash.debounce": "4.0.8",
|
||||
"lodash.intersection": "4.4.0",
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ const Intro = ({ queryParams }) => {
|
|||
const loginWithApple = React.useCallback(() => {
|
||||
openLoaderModal();
|
||||
startAppleLogin()
|
||||
.then(({ email, token, sub, name }) => {
|
||||
.then(({ token, sub, email, name }) => {
|
||||
core.transport.dispatch({
|
||||
action: 'Ctx',
|
||||
args: {
|
||||
|
|
@ -129,11 +129,7 @@ const Intro = ({ queryParams }) => {
|
|||
})
|
||||
.catch((error) => {
|
||||
closeLoaderModal();
|
||||
if (error.error === 'popup_closed_by_user') {
|
||||
dispatch({ type: 'error', error: 'Apple login popup was closed.' });
|
||||
} else {
|
||||
dispatch({ type: 'error', error: error.error });
|
||||
}
|
||||
dispatch({ type: 'error', error: error.message });
|
||||
});
|
||||
}, []);
|
||||
const cancelLoginWithApple = React.useCallback(() => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
// Copyright (C) 2017-2025 Smart code 203358507
|
||||
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { jwtDecode, JwtPayload } from 'jwt-decode';
|
||||
import { usePlatform } from 'stremio/common';
|
||||
import hat from 'hat';
|
||||
|
||||
type AppleLoginResponse = {
|
||||
token: string;
|
||||
|
|
@ -8,97 +11,71 @@ type AppleLoginResponse = {
|
|||
name: string;
|
||||
};
|
||||
|
||||
type AppleSignInResponse = {
|
||||
authorization: {
|
||||
code?: string;
|
||||
id_token: string;
|
||||
state?: string;
|
||||
};
|
||||
email?: string;
|
||||
fullName?: {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
};
|
||||
};
|
||||
const STREMIO_URL = 'https://www.strem.io';
|
||||
const MAX_TRIES = 25;
|
||||
|
||||
type CustomJWTPayload = JwtPayload & {
|
||||
email?: string;
|
||||
};
|
||||
const getCredentials = async (state: string): Promise<AppleLoginResponse> => {
|
||||
try {
|
||||
const response = await fetch(`${STREMIO_URL}/login-apple-get-acc/${state}`);
|
||||
const { user } = await response.json();
|
||||
|
||||
const CLIENT_ID = 'com.stremio.services';
|
||||
return Promise.resolve({
|
||||
token: user.token,
|
||||
sub: user.sub,
|
||||
email: user.email,
|
||||
// We might not receive a name from Apple, so we use an empty string as a fallback
|
||||
name: user.name ?? '',
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Failed to get credentials from Apple auth', e);
|
||||
return Promise.reject(e);
|
||||
}
|
||||
};
|
||||
|
||||
const useAppleLogin = (): [() => Promise<AppleLoginResponse>, () => void] => {
|
||||
const platform = usePlatform();
|
||||
const started = useRef(false);
|
||||
const timeout = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const start = useCallback((): Promise<AppleLoginResponse> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (typeof window.AppleID === 'undefined') {
|
||||
reject(new Error('Apple Sign-In not loaded'));
|
||||
return;
|
||||
}
|
||||
const start = useCallback(() => new Promise<AppleLoginResponse>((resolve, reject) => {
|
||||
started.current = true;
|
||||
const state = hat(128);
|
||||
let tries = 0;
|
||||
|
||||
platform.openExternal(`${STREMIO_URL}/login-apple/${state}`);
|
||||
|
||||
const waitForCredentials = () => {
|
||||
if (started.current) {
|
||||
reject(new Error('Apple login already in progress'));
|
||||
return;
|
||||
timeout.current && clearTimeout(timeout.current);
|
||||
timeout.current = setTimeout(() => {
|
||||
if (tries >= MAX_TRIES)
|
||||
return reject(new Error('Failed to authenticate with Apple'));
|
||||
|
||||
tries++;
|
||||
|
||||
getCredentials(state)
|
||||
.then(resolve)
|
||||
.catch(waitForCredentials);
|
||||
}, 2000);
|
||||
}
|
||||
};
|
||||
|
||||
started.current = true;
|
||||
|
||||
window.AppleID.auth.init({
|
||||
clientId: CLIENT_ID,
|
||||
scope: 'name email',
|
||||
redirectURI: 'https://web.stremio.com/',
|
||||
state: 'signin',
|
||||
usePopup: true,
|
||||
});
|
||||
|
||||
window.AppleID.auth.signIn().then((response: AppleSignInResponse) => {
|
||||
if (response.authorization) {
|
||||
try {
|
||||
const idToken = response.authorization.id_token;
|
||||
const payload: CustomJWTPayload = jwtDecode(idToken);
|
||||
const sub = payload.sub;
|
||||
const email = payload.email ?? response.email ?? '';
|
||||
|
||||
let name = '';
|
||||
if (response.fullName) {
|
||||
const firstName = response.fullName.firstName || '';
|
||||
const lastName = response.fullName.lastName || '';
|
||||
name = [firstName, lastName].filter(Boolean).join(' ');
|
||||
}
|
||||
|
||||
if (!sub) {
|
||||
reject(new Error('No sub token received from Apple'));
|
||||
return;
|
||||
}
|
||||
|
||||
resolve({
|
||||
token: idToken,
|
||||
sub: sub,
|
||||
email: email,
|
||||
name: name,
|
||||
});
|
||||
} catch (error) {
|
||||
reject(new Error(`Failed to parse id_token: ${error}`));
|
||||
}
|
||||
} else {
|
||||
reject(new Error('No authorization received from Apple'));
|
||||
}
|
||||
}).catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
waitForCredentials();
|
||||
}), []);
|
||||
|
||||
const stop = useCallback(() => {
|
||||
started.current = false;
|
||||
timeout.current && clearTimeout(timeout.current);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
return () => stop();
|
||||
}, []);
|
||||
|
||||
return [start, stop];
|
||||
return [
|
||||
start,
|
||||
stop,
|
||||
];
|
||||
};
|
||||
|
||||
export default useAppleLogin;
|
||||
|
|
|
|||
25
src/types/global.d.ts
vendored
25
src/types/global.d.ts
vendored
|
|
@ -26,31 +26,6 @@ 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;
|
||||
};
|
||||
email?: string;
|
||||
fullName?: {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
|
|
|
|||
Loading…
Reference in a new issue