Merge pull request #898 from Stremio/fix/apple-login-shell
Some checks failed
Build / build (push) Has been cancelled

Shell: Fix Apple ID login
This commit is contained in:
Timothy Z. 2025-04-25 14:39:17 +03:00 committed by GitHub
commit aeb9265e3b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 52 additions and 115 deletions

10
package-lock.json generated
View file

@ -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",

View file

@ -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",

View file

@ -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(() => {

View file

@ -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
View file

@ -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 {};