Merge pull request #6975 from thornbill/server-id-mismatch

This commit is contained in:
Bill Thornton
2025-07-01 23:11:02 -04:00
committed by GitHub
6 changed files with 83 additions and 18 deletions
+1
View File
@@ -336,6 +336,7 @@ declare module 'jellyfin-apiclient' {
handleMessageReceived(msg: any): void;
logout(): Promise<void>;
minServerVersion(val?: string): string;
updateSavedServerId(server: any): Promise<void>;
user(apiClient: ApiClient): Promise<any>;
}
+41 -4
View File
@@ -1,11 +1,13 @@
import React, { FC, useEffect, useState } from 'react';
import DOMPurify from 'dompurify';
import React, { FC, useCallback, useEffect, useState } from 'react';
import { appHost } from 'components/apphost';
import Page from 'components/Page';
import toast from 'components/toast/toast';
import { AppFeature } from 'constants/appFeature';
import LinkButton from 'elements/emby-button/LinkButton';
import globalize from 'lib/globalize';
import { ConnectionState } from 'lib/jellyfin-apiclient';
import { ConnectionState, ServerConnections } from 'lib/jellyfin-apiclient';
interface ConnectionErrorPageProps {
state: ConnectionState
@@ -17,9 +19,29 @@ const ConnectionErrorPage: FC<ConnectionErrorPageProps> = ({
const [ title, setTitle ] = useState<string>();
const [ htmlMessage, setHtmlMessage ] = useState<string>();
const [ message, setMessage ] = useState<string>();
const [ isConnectDisabled, setIsConnectDisabled ] = useState(false);
const onForceConnect = useCallback(async () => {
setIsConnectDisabled(true);
try {
const server = ServerConnections.getLastUsedServer();
await ServerConnections.updateSavedServerId(server);
window.location.reload();
} catch (err) {
console.error('[ConnectionErrorPage] Failed to force connect to server', err);
toast(globalize.translate('HeaderConnectionFailure'));
setIsConnectDisabled(false);
}
}, []);
useEffect(() => {
switch (state) {
case ConnectionState.ServerMismatch:
setTitle(globalize.translate('HeaderServerMismatch'));
setHtmlMessage(undefined);
setMessage(globalize.translate('MessageServerMismatch'));
return;
case ConnectionState.ServerUpdateNeeded:
setTitle(globalize.translate('HeaderUpdateRequired'));
setHtmlMessage(globalize.translate(
@@ -46,11 +68,17 @@ const ConnectionErrorPage: FC<ConnectionErrorPageProps> = ({
<div className='padded-left padded-right'>
<h1>{title}</h1>
{htmlMessage && (
<p dangerouslySetInnerHTML={{ __html: htmlMessage }} />
<p
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(htmlMessage) }}
style={{ maxWidth: '80ch' }}
/>
)}
{message && (
<p>{message}</p>
<p style={{ maxWidth: '80ch' }}>
{message}
</p>
)}
{appHost.supports(AppFeature.MultiServer) && (
<LinkButton
className='raised'
@@ -59,6 +87,15 @@ const ConnectionErrorPage: FC<ConnectionErrorPageProps> = ({
{globalize.translate('ButtonChangeServer')}
</LinkButton>
)}
{state === ConnectionState.ServerMismatch && (
<LinkButton
onClick={onForceConnect}
style={ isConnectDisabled ? { pointerEvents: 'none' } : undefined }
>
{globalize.translate('ConnectAnyway')}
</LinkButton>
)}
</div>
</Page>
);
+7 -1
View File
@@ -31,6 +31,12 @@ type ConnectionRequiredProps = {
level?: AccessLevelValue
};
const ERROR_STATES = [
ConnectionState.ServerMismatch,
ConnectionState.ServerUpdateNeeded,
ConnectionState.Unavailable
];
const fetchPublicSystemInfo = async (apiClient: ApiClient) => {
const infoResponse = await fetch(
`${apiClient.serverAddress()}/System/Info/Public`,
@@ -184,7 +190,7 @@ const ConnectionRequired: FunctionComponent<ConnectionRequiredProps> = ({
console.debug('[ConnectionRequired] connection state', firstConnection?.State);
ServerConnections.firstConnection = true;
if ([ ConnectionState.ServerUpdateNeeded, ConnectionState.Unavailable ].includes(firstConnection?.State)) {
if (ERROR_STATES.includes(firstConnection?.State)) {
setErrorState(firstConnection.State);
} else if (level === AccessLevel.Wizard) {
handleWizard(firstConnection)
+30 -13
View File
@@ -49,6 +49,10 @@ function normalizeAddress(address) {
return address;
}
function sortByAccess(a, b) {
return (b.DateLastAccessed || 0) - (a.DateLastAccessed || 0);
}
export default class ConnectionManager {
constructor(credentialProvider, appName, appVersion, deviceName, deviceId, capabilities) {
console.log('Begin ConnectionManager constructor');
@@ -78,7 +82,7 @@ export default class ConnectionManager {
self.getLastUsedServer = () => {
const servers = credentialProvider.credentials().Servers;
servers.sort((a, b) => (b.DateLastAccessed || 0) - (a.DateLastAccessed || 0));
servers.sort(sortByAccess);
if (!servers.length) {
return null;
@@ -345,25 +349,24 @@ export default class ConnectionManager {
const servers = credentials.Servers.slice(0);
servers.sort((a, b) => (b.DateLastAccessed || 0) - (a.DateLastAccessed || 0));
servers.sort(sortByAccess);
return servers;
};
self.getAvailableServers = () => {
console.log('Begin getAvailableServers');
console.debug('[ConnectionManager] Begin getAvailableServers');
// Clone the array
const credentials = credentialProvider.credentials();
return Promise.all([findServers()]).then((responses) => {
const foundServers = responses[0];
return findServers().then(foundServers => {
const servers = credentials.Servers.slice(0);
foundServers.forEach(server => {
credentialProvider.addOrUpdateServer(servers, server);
});
servers.sort((a, b) => (b.DateLastAccessed || 0) - (a.DateLastAccessed || 0));
servers.sort(sortByAccess);
credentials.Servers = servers;
credentialProvider.credentials(credentials);
@@ -508,7 +511,7 @@ export default class ConnectionManager {
addressesStrings.push(addresses[addresses.length - 1].url);
}
console.log('tryReconnect: ' + addressesStrings.join('|'));
console.info('[ConnectionManager] tryReconnect addresses', addressesStrings);
return new Promise((resolve, reject) => {
const state = {};
@@ -523,10 +526,10 @@ export default class ConnectionManager {
}, url.timeout);
});
});
}
};
self.connectToServer = (server, options) => {
console.log('begin connectToServer');
console.debug('[ConnectionManager] begin connectToServer');
return new Promise((resolve) => {
options = options || {};
@@ -538,17 +541,17 @@ export default class ConnectionManager {
result = result.data;
if (compareVersions(self.minServerVersion(), result.Version) === 1) {
console.log('minServerVersion requirement not met. Server version: ' + result.Version);
console.warn('[ConnectionManager] minServerVersion requirement not met. Server version:', result.Version);
resolve({
State: ConnectionState.ServerUpdateNeeded,
Servers: [server]
});
} else if (server.Id && result.Id !== server.Id) {
console.log(
'http request succeeded, but found a different server Id than what was expected'
console.warn(
'[ConnectionManager] http request succeeded, but found a different server Id than what was expected'
);
resolve({
State: ConnectionState.Unavailable
State: ConnectionState.ServerMismatch
});
} else {
onSuccessfulConnection(server, result, connectionMode, serverUrl, true, resolve, options);
@@ -622,6 +625,20 @@ export default class ConnectionManager {
}
}
self.updateSavedServerId = async (server) => {
const { data: serverResponse } = await tryReconnect(server);
// Update the server ID to match the new value
server.Id = serverResponse.Id;
// Force the user to login again
server.AccessToken = null;
server.UserId = null;
// Save the updated server in the credential provider
const credentials = credentialProvider.credentials();
credentialProvider.addOrUpdateServer(credentials.Servers, server);
credentialProvider.credentials(credentials);
};
function tryConnectToAddress(address, options) {
const server = {
ManualAddress: address,
@@ -3,6 +3,7 @@
*/
export const enum ConnectionState {
SignedIn = 'SignedIn',
ServerMismatch = 'ServerMismatch',
ServerSignIn = 'ServerSignIn',
ServerSelection = 'ServerSelection',
ServerUpdateNeeded = 'ServerUpdateNeeded',
+3
View File
@@ -184,6 +184,7 @@
"ConfirmDeletion": "Confirm Deletion",
"ConfirmEndPlayerSession": "Would you like to shut Jellyfin down on {0}?",
"Connect": "Connect",
"ConnectAnyway": "Connect Anyway",
"Console": "Console",
"ContinueWatching": "Continue watching",
"Continuing": "Continuing",
@@ -523,6 +524,7 @@
"HeaderSeriesOptions": "Series Options",
"HeaderSeriesStatus": "Series Status",
"HeaderServerAddressSettings": "Server Address Settings",
"HeaderServerMismatch": "Server Mismatch",
"HeaderServerUnavailable": "Server Unavailable",
"HeaderSetupLibrary": "Set up your media libraries",
"HeaderSortBy": "Sort By",
@@ -1182,6 +1184,7 @@
"MessageRestoreInProgress": "Restore in progress",
"MessageRestoreSuccess": "Restore successfully completed",
"MessageSent": "Message sent.",
"MessageServerMismatch": "The server you are connecting to is not the same server that you previously connected to at this address. If this is expected, please select \"Connect Anyway\". Otherwise, your connection or the server might be compromised.",
"MessageSplitVersionsError": "An error occurred while splitting versions",
"MessageSyncPlayCreateGroupDenied": "Permission required to create a group.",
"MessageSyncPlayDisabled": "SyncPlay disabled.",