diff --git a/src/screens/modals/SetJellyfinServer/components/CredentialGenerator.tsx b/src/screens/modals/SetJellyfinServer/components/CredentialGenerator.tsx index daa6618..44fbc84 100644 --- a/src/screens/modals/SetJellyfinServer/components/CredentialGenerator.tsx +++ b/src/screens/modals/SetJellyfinServer/components/CredentialGenerator.tsx @@ -8,6 +8,47 @@ interface Props { onCredentialsRetrieved: (credentials: AppState['settings']['jellyfin']) => void; } +type CredentialEventData = { + credentials: { + Servers: { + ManualAddress: string, + ManualAddressOnly: boolean, + IsLocalServer: boolean, + DateLastAccessed: number, + LastConnectionMode: number, + Type: string, + Name: string, + Id: string, + UserId: string | null, + AccessToken: string | null, + Users: { + UserId: string, + AccessToken: string, + }[] + LocalAddress: string, + RemoteAddress: string, + }[] + }, + deviceId: string, + type: 'emby', +} | { + credentials: { + Servers: { + ManualAddress: string, + manualAddressOnly: boolean, + DateLastAccessed: number, + LastConnectionMode: number, + Name: string, + Id: string, + UserId: string | null, + AccessToken: string | null, + LocalAddress: string, + }[] + }, + deviceId: string, + type: 'jellyfin', +} | undefined; + class CredentialGenerator extends Component { ref = createRef(); @@ -18,12 +59,18 @@ class CredentialGenerator extends Component { checkIfCredentialsAreThere = debounce(() => { // Inject some javascript to check if the credentials can be extracted - // from localstore + // from localstore. We simultaneously attempt to extract credentials for + // any back-end. this.ref.current?.injectJavaScript(` try { let credentials = JSON.parse(window.localStorage.getItem('jellyfin_credentials')); let deviceId = window.localStorage.getItem('_deviceId2'); - window.ReactNativeWebView.postMessage(JSON.stringify({ credentials, deviceId })) + window.ReactNativeWebView.postMessage(JSON.stringify({ credentials, deviceId, type: 'jellyfin' })) + } catch(e) { }; true; + try { + let credentials = JSON.parse(window.localStorage.getItem('servercredentials3')); + let deviceId = window.localStorage.getItem('_deviceId2'); + window.ReactNativeWebView.postMessage(JSON.stringify({ credentials, deviceId, type: 'emby' })) } catch(e) { }; true; `); }, 500); @@ -35,35 +82,71 @@ class CredentialGenerator extends Component { } // Parse the content - const data = JSON.parse(event.nativeEvent.data); + const data = JSON.parse(event.nativeEvent.data) as CredentialEventData; + if (__DEV__) { + console.log('Received credential event data: ', JSON.stringify(data)); + } - if (!data.deviceId - || !data.credentials?.Servers?.length - || !data.credentials?.Servers[0]?.UserId - || !data.credentials?.Servers[0]?.AccessToken) { + // Since Jellyfin and Emby are similar, we'll attempt to extract the + // credentials in a generic way. + let userId: string | undefined, accessToken: string | undefined; + + // GUARD: Attempt to extract emby format credentials + if (data?.type === 'emby' + && data.credentials?.Servers?.length + && data.credentials?.Servers[0]?.Users?.length + ) { + userId = data.credentials.Servers[0].Users[0].UserId; + accessToken = data.credentials.Servers[0].Users[0].AccessToken; + // GUARD: Attempt to extract jellyfin format credentials + } else if (data?.type === 'jellyfin' + && data.credentials?.Servers?.length + ) { + userId = data.credentials.Servers[0].UserId || undefined; + accessToken = data.credentials.Servers[0].AccessToken || undefined; + } + + // We can extract the deviceId and server address in the same way for + // both Jellyfin and Emby. + const deviceId = data?.deviceId; + const address = data?.credentials?.Servers?.length + && data?.credentials.Servers[0].ManualAddress; + + // GUARD: log extract credentials in dev + if (__DEV__) { + console.log('Extracted the following credentials:', { userId, accessToken, deviceId, address }); + } + + // GUARD: Check that all the required credentials are available + if (!userId || !accessToken || !deviceId || !address) { + if (__DEV__) { + console.error('Failed to extract credentials from event'); + } return; } - const { credentials: { Servers: [ credentials ] }, deviceId } = data; - // Attempt to perform a request using the credentials to see if they're // good - const response = await fetch(`${credentials.ManualAddress}/Users/Me`, { + const response = await fetch(`${address}/Users/${userId}`, { headers: { - 'X-Emby-Authorization': `MediaBrowser Client="", Device="", DeviceId="", Version="", Token="${credentials.AccessToken}"` + 'X-Emby-Authorization': `MediaBrowser Client="", Device="", DeviceId="", Version="", Token="${accessToken}"` } }); // GUARD: The request must succeed if (response.status !== 200) { + if (__DEV__) { + const body = await response.text(); + console.error('Failed to retrieve user object using credentials:', response.status, body); + } return; } // If a message is received, the credentials should be there this.props.onCredentialsRetrieved({ - uri: credentials.ManualAddress, - user_id: credentials.UserId, - access_token: credentials.AccessToken, + uri: address, + user_id: userId, + access_token: accessToken, device_id: deviceId, }); };