fix: support credential extraction from Emby

This commit is contained in:
Lei Nelissen
2024-07-25 23:16:20 +02:00
parent f2a8c4c9b2
commit b7436afabe

View File

@@ -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<Props> {
ref = createRef<WebView>();
@@ -18,12 +59,18 @@ class CredentialGenerator extends Component<Props> {
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<Props> {
}
// 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,
});
};