diff --git a/README.md b/README.md
index f8fee50ee..4d054ac37 100644
--- a/README.md
+++ b/README.md
@@ -81,28 +81,29 @@ Jellyfin Web is the frontend used for most of the clients available for end user
.
└── src
├── apps
- │ ├── dashboard # Admin dashboard app
- │ ├── experimental # New experimental app
- │ ├── stable # Classic (stable) app
- │ └── wizard # Startup wizard app
- ├── assets # Static assets
- ├── components # Higher order visual components and React components
- ├── constants # Common constant values
- ├── controllers # Legacy page views and controllers 🧹 ❌
- ├── elements # Basic webcomponents and React equivalents 🧹
- ├── hooks # Custom React hooks
- ├── lib # Reusable libraries
- │ ├── globalize # Custom localization library
- │ ├── legacy # Polyfills for legacy browsers
- │ ├── navdrawer # Navigation drawer library for classic layout
- │ └── scroller # Content scrolling library
- ├── plugins # Client plugins (features dynamically loaded at runtime)
- ├── scripts # Random assortment of visual components and utilities 🐉 ❌
- ├── strings # Translation files (only commit changes to en-us.json)
- ├── styles # Common app Sass stylesheets
- ├── themes # Sass and MUI themes
- ├── types # Common TypeScript interfaces/types
- └── utils # Utility functions
+ │ ├── dashboard # Admin dashboard app
+ │ ├── experimental # New experimental app
+ │ ├── stable # Classic (stable) app
+ │ └── wizard # Startup wizard app
+ ├── assets # Static assets
+ ├── components # Higher order visual components and React components
+ ├── constants # Common constant values
+ ├── controllers # Legacy page views and controllers 🧹 ❌
+ ├── elements # Basic webcomponents and React equivalents 🧹
+ ├── hooks # Custom React hooks
+ ├── lib # Reusable libraries
+ │ ├── globalize # Custom localization library
+ │ ├── jellyfin-apiclient # Supporting code for the deprecated apiclient package
+ │ ├── legacy # Polyfills for legacy browsers
+ │ ├── navdrawer # Navigation drawer library for classic layout
+ │ └── scroller # Content scrolling library
+ ├── plugins # Client plugins (features dynamically loaded at runtime)
+ ├── scripts # Random assortment of visual components and utilities 🐉 ❌
+ ├── strings # Translation files (only commit changes to en-us.json)
+ ├── styles # Common app Sass stylesheets
+ ├── themes # Sass and MUI themes
+ ├── types # Common TypeScript interfaces/types
+ └── utils # Utility functions
```
- ❌ — Deprecated, do **not** create new files here
diff --git a/src/apiclient.d.ts b/src/apiclient.d.ts
index c2bc4a051..0d8729e7d 100644
--- a/src/apiclient.d.ts
+++ b/src/apiclient.d.ts
@@ -1,4 +1,3 @@
-// TODO: Move to jellyfin-apiclient
/* eslint-disable @typescript-eslint/no-explicit-any */
declare module 'jellyfin-apiclient' {
import type {
@@ -68,7 +67,7 @@ declare module 'jellyfin-apiclient' {
UtcTimeResponse,
VirtualFolderInfo
} from '@jellyfin/sdk/lib/generated-client';
- import { ConnectionState } from './utils/jellyfin-apiclient/ConnectionState';
+ import type { ConnectionState } from 'lib/jellyfin-apiclient';
class ApiClient {
constructor(serverAddress: string, appName: string, appVersion: string, deviceName: string, deviceId: string);
diff --git a/src/apps/dashboard/controllers/dashboard.js b/src/apps/dashboard/controllers/dashboard.js
index 334fb7e3e..2388ac717 100644
--- a/src/apps/dashboard/controllers/dashboard.js
+++ b/src/apps/dashboard/controllers/dashboard.js
@@ -17,12 +17,12 @@ import imageHelper from 'utils/image';
import indicators from 'components/indicators/indicators';
import taskButton from 'scripts/taskbutton';
import Dashboard from 'utils/dashboard';
-import ServerConnections from 'components/ServerConnections';
import alert from 'components/alert';
import confirm from 'components/confirm/confirm';
import { getDefaultBackgroundClass } from 'components/cardbuilder/cardBuilderUtils';
import { getSystemInfoQuery } from 'hooks/useSystemInfo';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import { toApi } from 'utils/jellyfin-apiclient/compat';
import { queryClient } from 'utils/query/queryClient';
diff --git a/src/apps/dashboard/routes/branding/index.tsx b/src/apps/dashboard/routes/branding/index.tsx
index 3d2d8d2c7..26595a50d 100644
--- a/src/apps/dashboard/routes/branding/index.tsx
+++ b/src/apps/dashboard/routes/branding/index.tsx
@@ -18,10 +18,10 @@ import { getBrandingOptionsQuery, QUERY_KEY, useBrandingOptions } from 'apps/das
import Loading from 'components/loading/LoadingComponent';
import Image from 'components/Image';
import Page from 'components/Page';
-import ServerConnections from 'components/ServerConnections';
import { SPLASHSCREEN_URL } from 'constants/branding';
import { useApi } from 'hooks/useApi';
import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import { queryClient } from 'utils/query/queryClient';
import { ActionData } from 'types/actionData';
diff --git a/src/apps/dashboard/routes/libraries/display.tsx b/src/apps/dashboard/routes/libraries/display.tsx
index d83f8d588..9cd0852c9 100644
--- a/src/apps/dashboard/routes/libraries/display.tsx
+++ b/src/apps/dashboard/routes/libraries/display.tsx
@@ -12,11 +12,11 @@ import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import Loading from 'components/loading/LoadingComponent';
import Page from 'components/Page';
-import ServerConnections from 'components/ServerConnections';
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
import { QUERY_KEY as CONFIG_QUERY_KEY, useConfiguration } from 'hooks/useConfiguration';
import { QUERY_KEY as NAMED_CONFIG_QUERY_KEY, NamedConfiguration, useNamedConfiguration } from 'hooks/useNamedConfiguration';
import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import { type ActionFunctionArgs, Form, useActionData, useNavigation } from 'react-router-dom';
import { ActionData } from 'types/actionData';
import { queryClient } from 'utils/query/queryClient';
diff --git a/src/apps/dashboard/routes/libraries/metadata.tsx b/src/apps/dashboard/routes/libraries/metadata.tsx
index 4ac74a960..122072d4e 100644
--- a/src/apps/dashboard/routes/libraries/metadata.tsx
+++ b/src/apps/dashboard/routes/libraries/metadata.tsx
@@ -12,9 +12,9 @@ import { useCultures } from 'apps/dashboard/features/libraries/api/useCultures';
import { getImageResolutionOptions } from 'apps/dashboard/features/libraries/utils/metadataOptions';
import Loading from 'components/loading/LoadingComponent';
import Page from 'components/Page';
-import ServerConnections from 'components/ServerConnections';
import { QUERY_KEY, useConfiguration } from 'hooks/useConfiguration';
import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import React from 'react';
import { type ActionFunctionArgs, Form, useActionData, useNavigation } from 'react-router-dom';
import { ActionData } from 'types/actionData';
diff --git a/src/apps/dashboard/routes/libraries/nfo.tsx b/src/apps/dashboard/routes/libraries/nfo.tsx
index 8d42be829..326834a83 100644
--- a/src/apps/dashboard/routes/libraries/nfo.tsx
+++ b/src/apps/dashboard/routes/libraries/nfo.tsx
@@ -12,11 +12,11 @@ import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import Loading from 'components/loading/LoadingComponent';
import Page from 'components/Page';
-import ServerConnections from 'components/ServerConnections';
import SimpleAlert from 'components/SimpleAlert';
import { QUERY_KEY, useNamedConfiguration } from 'hooks/useNamedConfiguration';
import { useUsers } from 'hooks/useUsers';
import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import React, { useCallback, useState } from 'react';
import { type ActionFunctionArgs, Form, useActionData, useNavigation } from 'react-router-dom';
import { ActionData } from 'types/actionData';
diff --git a/src/apps/dashboard/routes/logs/index.tsx b/src/apps/dashboard/routes/logs/index.tsx
index 84af1b244..b5d8b0967 100644
--- a/src/apps/dashboard/routes/logs/index.tsx
+++ b/src/apps/dashboard/routes/logs/index.tsx
@@ -3,6 +3,7 @@ import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-a
import Loading from 'components/loading/LoadingComponent';
import Page from 'components/Page';
import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import Alert from '@mui/material/Alert';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
@@ -12,7 +13,6 @@ import Checkbox from '@mui/material/Checkbox';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import { type ActionFunctionArgs, Form, useActionData, useNavigation } from 'react-router-dom';
-import ServerConnections from 'components/ServerConnections';
import { useServerLogs } from 'apps/dashboard/features/logs/api/useServerLogs';
import { useConfiguration } from 'hooks/useConfiguration';
import type { ServerConfiguration } from '@jellyfin/sdk/lib/generated-client/models/server-configuration';
diff --git a/src/apps/dashboard/routes/playback/resume.tsx b/src/apps/dashboard/routes/playback/resume.tsx
index f711ac82d..6bd23a8ce 100644
--- a/src/apps/dashboard/routes/playback/resume.tsx
+++ b/src/apps/dashboard/routes/playback/resume.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import Page from 'components/Page';
import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import Alert from '@mui/material/Alert';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
@@ -11,7 +12,6 @@ import { type ActionFunctionArgs, Form, useActionData, useNavigation } from 'rea
import { ActionData } from 'types/actionData';
import { QUERY_KEY, useConfiguration } from 'hooks/useConfiguration';
import Loading from 'components/loading/LoadingComponent';
-import ServerConnections from 'components/ServerConnections';
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
import { queryClient } from 'utils/query/queryClient';
diff --git a/src/apps/dashboard/routes/playback/streaming.tsx b/src/apps/dashboard/routes/playback/streaming.tsx
index fdb75b834..b3be899d6 100644
--- a/src/apps/dashboard/routes/playback/streaming.tsx
+++ b/src/apps/dashboard/routes/playback/streaming.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import Page from 'components/Page';
import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import Alert from '@mui/material/Alert';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
@@ -8,7 +9,6 @@ import Stack from '@mui/material/Stack';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import { type ActionFunctionArgs, Form, useActionData, useNavigation } from 'react-router-dom';
-import ServerConnections from 'components/ServerConnections';
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
import { QUERY_KEY, useConfiguration } from 'hooks/useConfiguration';
import Loading from 'components/loading/LoadingComponent';
diff --git a/src/apps/dashboard/routes/playback/trickplay.tsx b/src/apps/dashboard/routes/playback/trickplay.tsx
index 02591d216..d44814771 100644
--- a/src/apps/dashboard/routes/playback/trickplay.tsx
+++ b/src/apps/dashboard/routes/playback/trickplay.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import { type ActionFunctionArgs, Form, useActionData, useNavigation } from 'react-router-dom';
import { QUERY_KEY, useConfiguration } from 'hooks/useConfiguration';
import Page from 'components/Page';
@@ -16,7 +17,6 @@ import MenuItem from '@mui/material/MenuItem';
import TextField from '@mui/material/TextField';
import Button from '@mui/material/Button';
import Alert from '@mui/material/Alert';
-import ServerConnections from 'components/ServerConnections';
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
import { TrickplayScanBehavior } from '@jellyfin/sdk/lib/generated-client/models/trickplay-scan-behavior';
import { ProcessPriorityClass } from '@jellyfin/sdk/lib/generated-client/models/process-priority-class';
diff --git a/src/apps/dashboard/routes/settings/index.tsx b/src/apps/dashboard/routes/settings/index.tsx
index 2e4a31d0f..f86614600 100644
--- a/src/apps/dashboard/routes/settings/index.tsx
+++ b/src/apps/dashboard/routes/settings/index.tsx
@@ -11,6 +11,7 @@ import Page from 'components/Page';
import { QUERY_KEY, useConfiguration } from 'hooks/useConfiguration';
import { useSystemInfo } from 'hooks/useSystemInfo';
import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import React, { useCallback, useEffect, useState } from 'react';
import { type ActionFunctionArgs, Form, useActionData, useNavigation } from 'react-router-dom';
import SearchIcon from '@mui/icons-material/Search';
@@ -21,7 +22,6 @@ import Checkbox from '@mui/material/Checkbox';
import Button from '@mui/material/Button';
import Link from '@mui/material/Link';
import DirectoryBrowser from 'components/directorybrowser/directorybrowser';
-import ServerConnections from 'components/ServerConnections';
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
import { queryClient } from 'utils/query/queryClient';
import { ActionData } from 'types/actionData';
diff --git a/src/apps/dashboard/routes/users/parentalcontrol.tsx b/src/apps/dashboard/routes/users/parentalcontrol.tsx
index 2e4954259..c171719b0 100644
--- a/src/apps/dashboard/routes/users/parentalcontrol.tsx
+++ b/src/apps/dashboard/routes/users/parentalcontrol.tsx
@@ -17,7 +17,7 @@ import CheckBoxElement from '../../../../elements/CheckBoxElement';
import SelectElement from '../../../../elements/SelectElement';
import Page from '../../../../components/Page';
import prompt from '../../../../components/prompt/prompt';
-import ServerConnections from 'components/ServerConnections';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
type NamedItem = {
name: string;
diff --git a/src/apps/stable/features/playback/utils/image.ts b/src/apps/stable/features/playback/utils/image.ts
index 4bac595af..4497846d9 100644
--- a/src/apps/stable/features/playback/utils/image.ts
+++ b/src/apps/stable/features/playback/utils/image.ts
@@ -1,7 +1,7 @@
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type';
-import ServerConnections from 'components/ServerConnections';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import type { ItemDto } from 'types/base/models/item-dto';
interface ImageOptions {
diff --git a/src/apps/stable/features/playback/utils/mediaSegmentManager.ts b/src/apps/stable/features/playback/utils/mediaSegmentManager.ts
index 129eda65f..a3bf7e939 100644
--- a/src/apps/stable/features/playback/utils/mediaSegmentManager.ts
+++ b/src/apps/stable/features/playback/utils/mediaSegmentManager.ts
@@ -4,8 +4,8 @@ import { MediaSegmentType } from '@jellyfin/sdk/lib/generated-client/models/medi
import { getMediaSegmentsApi } from '@jellyfin/sdk/lib/utils/api/media-segments-api';
import type { PlaybackManager } from 'components/playback/playbackmanager';
-import ServerConnections from 'components/ServerConnections';
import { TICKS_PER_MILLISECOND, TICKS_PER_SECOND } from 'constants/time';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import { currentSettings as userSettings } from 'scripts/settings/userSettings';
import type { PlayerState } from 'types/playbackStopInfo';
import type { Event } from 'utils/events';
diff --git a/src/apps/wizard/controllers/finish/index.js b/src/apps/wizard/controllers/finish/index.js
index 4408da2dd..bea7e28e7 100644
--- a/src/apps/wizard/controllers/finish/index.js
+++ b/src/apps/wizard/controllers/finish/index.js
@@ -1,5 +1,5 @@
import loading from 'components/loading/loading';
-import ServerConnections from 'components/ServerConnections';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
function onFinish() {
loading.show();
diff --git a/src/apps/wizard/controllers/library.js b/src/apps/wizard/controllers/library.js
index ccdc42b13..dfa0f17e4 100644
--- a/src/apps/wizard/controllers/library.js
+++ b/src/apps/wizard/controllers/library.js
@@ -3,8 +3,8 @@ import escapeHtml from 'escape-html';
import { getDefaultBackgroundClass } from 'components/cardbuilder/cardBuilderUtils';
import confirm from 'components/confirm/confirm';
import loading from 'components/loading/loading';
-import ServerConnections from 'components/ServerConnections';
import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import dom from 'scripts/dom';
import taskButton from 'scripts/taskbutton';
import Dashboard, { pageClassOn, pageIdOn } from 'utils/dashboard';
diff --git a/src/apps/wizard/controllers/remote/index.js b/src/apps/wizard/controllers/remote/index.js
index f836f1fa9..7b35e2cb6 100644
--- a/src/apps/wizard/controllers/remote/index.js
+++ b/src/apps/wizard/controllers/remote/index.js
@@ -1,5 +1,5 @@
import loading from 'components/loading/loading';
-import ServerConnections from 'components/ServerConnections';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import Dashboard from 'utils/dashboard';
import 'elements/emby-checkbox/emby-checkbox';
diff --git a/src/apps/wizard/controllers/settings/index.js b/src/apps/wizard/controllers/settings/index.js
index f118d83ce..765530a2b 100644
--- a/src/apps/wizard/controllers/settings/index.js
+++ b/src/apps/wizard/controllers/settings/index.js
@@ -1,5 +1,5 @@
import loading from 'components/loading/loading';
-import ServerConnections from 'components/ServerConnections';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import Dashboard from 'utils/dashboard';
import 'elements/emby-button/emby-button';
diff --git a/src/apps/wizard/controllers/start/index.js b/src/apps/wizard/controllers/start/index.js
index 4bd9d68a9..9081a6c52 100644
--- a/src/apps/wizard/controllers/start/index.js
+++ b/src/apps/wizard/controllers/start/index.js
@@ -1,5 +1,5 @@
import loading from 'components/loading/loading';
-import ServerConnections from 'components/ServerConnections';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import Dashboard from 'utils/dashboard';
import dom from 'scripts/dom';
diff --git a/src/apps/wizard/controllers/user/index.js b/src/apps/wizard/controllers/user/index.js
index 1a13a790b..a9255754f 100644
--- a/src/apps/wizard/controllers/user/index.js
+++ b/src/apps/wizard/controllers/user/index.js
@@ -1,7 +1,7 @@
import loading from 'components/loading/loading';
-import ServerConnections from 'components/ServerConnections';
import toast from 'components/toast/toast';
import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import Dashboard from 'utils/dashboard';
import 'styles/dashboard.scss';
diff --git a/src/components/ConnectionRequired.tsx b/src/components/ConnectionRequired.tsx
index 61c7cbcd8..e84e86de4 100644
--- a/src/components/ConnectionRequired.tsx
+++ b/src/components/ConnectionRequired.tsx
@@ -3,11 +3,10 @@ import { Outlet, useLocation, useNavigate } from 'react-router-dom';
import type { ApiClient, ConnectResponse } from 'jellyfin-apiclient';
import globalize from 'lib/globalize';
-import { ConnectionState } from 'utils/jellyfin-apiclient/ConnectionState';
+import { ConnectionState, ServerConnections } from 'lib/jellyfin-apiclient';
import alert from './alert';
import Loading from './loading/LoadingComponent';
-import ServerConnections from './ServerConnections';
enum AccessLevel {
/** Requires a user with administrator access */
diff --git a/src/components/ServerContentPage.tsx b/src/components/ServerContentPage.tsx
index 2880e9a30..2400e4fd1 100644
--- a/src/components/ServerContentPage.tsx
+++ b/src/components/ServerContentPage.tsx
@@ -1,10 +1,10 @@
import { FunctionComponent, useEffect } from 'react';
import { useLocation } from 'react-router-dom';
-import ServerConnections from './ServerConnections';
import viewManager from './viewManager/viewManager';
-import globalize from '../lib/globalize';
-import type { RestoreViewFailResponse } from '../types/viewManager';
+import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
+import type { RestoreViewFailResponse } from 'types/viewManager';
interface ServerContentPageProps {
view: string
diff --git a/src/components/activitylog.js b/src/components/activitylog.js
index 36f17c880..cde6c81da 100644
--- a/src/components/activitylog.js
+++ b/src/components/activitylog.js
@@ -1,12 +1,12 @@
import escapeHtml from 'escape-html';
import Events from '../utils/events.ts';
import globalize from '../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import dom from '../scripts/dom';
import { formatRelative } from 'date-fns';
import serverNotifications from '../scripts/serverNotifications';
import '../elements/emby-button/emby-button';
import './listview/listview.scss';
-import ServerConnections from './ServerConnections';
import alert from './alert';
import { getLocale } from '../utils/dateFnsLocale.ts';
import { toBoolean } from '../utils/string.ts';
diff --git a/src/components/backdrop/backdrop.js b/src/components/backdrop/backdrop.js
index f99a46a5b..cc7346d5f 100644
--- a/src/components/backdrop/backdrop.js
+++ b/src/components/backdrop/backdrop.js
@@ -1,9 +1,9 @@
import isEqual from 'lodash-es/isEqual';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import browser from '../../scripts/browser';
import { playbackManager } from '../playback/playbackmanager';
import dom from '../../scripts/dom';
import * as userSettings from '../../scripts/settings/userSettings';
-import ServerConnections from '../ServerConnections';
import './backdrop.scss';
diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js
index 1f7da1655..b44b6e104 100644
--- a/src/components/cardbuilder/cardBuilder.js
+++ b/src/components/cardbuilder/cardBuilder.js
@@ -12,6 +12,7 @@ import browser from 'scripts/browser';
import datetime from 'scripts/datetime';
import dom from 'scripts/dom';
import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import { getBackdropShape, getPortraitShape, getSquareShape } from 'utils/card';
import { getItemTypeIcon, getLibraryIcon } from 'utils/image';
@@ -22,7 +23,6 @@ import itemHelper from '../itemHelper';
import layoutManager from '../layoutManager';
import { playbackManager } from '../playback/playbackmanager';
import { appRouter } from '../router/appRouter';
-import ServerConnections from '../ServerConnections';
import itemShortcuts from '../shortcuts';
import 'elements/emby-button/paper-icon-button-light';
diff --git a/src/components/cardbuilder/chaptercardbuilder.js b/src/components/cardbuilder/chaptercardbuilder.js
index 21535adc8..f8fa94e6a 100644
--- a/src/components/cardbuilder/chaptercardbuilder.js
+++ b/src/components/cardbuilder/chaptercardbuilder.js
@@ -5,11 +5,12 @@
*/
import escapeHtml from 'escape-html';
+
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import datetime from '../../scripts/datetime';
import imageLoader from '../images/imageLoader';
import layoutManager from '../layoutManager';
import browser from '../../scripts/browser';
-import ServerConnections from '../ServerConnections';
const enableFocusTransform = !browser.slow && !browser.edge;
diff --git a/src/components/channelMapper/channelMapper.js b/src/components/channelMapper/channelMapper.js
index 762ca0b75..91caf81c9 100644
--- a/src/components/channelMapper/channelMapper.js
+++ b/src/components/channelMapper/channelMapper.js
@@ -3,6 +3,7 @@ import dom from '../../scripts/dom';
import dialogHelper from '../dialogHelper/dialogHelper';
import loading from '../loading/loading';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import actionsheet from '../actionSheet/actionSheet';
import '../../elements/emby-input/emby-input';
import '../../elements/emby-button/paper-icon-button-light';
@@ -10,7 +11,6 @@ import '../../elements/emby-button/emby-button';
import '../listview/listview.scss';
import 'material-design-icons-iconfont';
import '../formdialog.scss';
-import ServerConnections from '../ServerConnections';
export default class ChannelMapper {
constructor(options) {
diff --git a/src/components/collectionEditor/collectionEditor.js b/src/components/collectionEditor/collectionEditor.js
index 703c60df7..a8981f2b3 100644
--- a/src/components/collectionEditor/collectionEditor.js
+++ b/src/components/collectionEditor/collectionEditor.js
@@ -5,6 +5,7 @@ import loading from '../loading/loading';
import layoutManager from '../layoutManager';
import { appRouter } from '../router/appRouter';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import '../../elements/emby-button/emby-button';
import '../../elements/emby-button/paper-icon-button-light';
import '../../elements/emby-checkbox/emby-checkbox';
@@ -13,7 +14,6 @@ import '../../elements/emby-select/emby-select';
import 'material-design-icons-iconfont';
import '../formdialog.scss';
import '../../styles/flexstyles.scss';
-import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
let currentServerId;
diff --git a/src/components/displaySettings/displaySettings.js b/src/components/displaySettings/displaySettings.js
index 354ef54d0..21eef9f28 100644
--- a/src/components/displaySettings/displaySettings.js
+++ b/src/components/displaySettings/displaySettings.js
@@ -6,6 +6,7 @@ import { appHost } from '../apphost';
import focusManager from '../focusManager';
import datetime from '../../scripts/datetime';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import loading from '../loading/loading';
import skinManager from '../../scripts/themeManager';
import { PluginType } from '../../types/plugin.ts';
@@ -14,7 +15,6 @@ import '../../elements/emby-select/emby-select';
import '../../elements/emby-checkbox/emby-checkbox';
import '../../elements/emby-button/emby-button';
import '../../elements/emby-textarea/emby-textarea';
-import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
import template from './displaySettings.template.html';
diff --git a/src/components/filterdialog/filterdialog.js b/src/components/filterdialog/filterdialog.js
index e52dcebc9..1e192372f 100644
--- a/src/components/filterdialog/filterdialog.js
+++ b/src/components/filterdialog/filterdialog.js
@@ -1,12 +1,12 @@
import dom from '../../scripts/dom';
import dialogHelper from '../dialogHelper/dialogHelper';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import union from 'lodash-es/union';
import Events from '../../utils/events.ts';
import '../../elements/emby-checkbox/emby-checkbox';
import '../../elements/emby-collapse/emby-collapse';
import './style.scss';
-import ServerConnections from '../ServerConnections';
import template from './filterdialog.template.html';
import { stopMultiSelect } from '../../components/multiSelect/multiSelect';
diff --git a/src/components/filtermenu/filtermenu.js b/src/components/filtermenu/filtermenu.js
index 5d7609f9f..a77e7d110 100644
--- a/src/components/filtermenu/filtermenu.js
+++ b/src/components/filtermenu/filtermenu.js
@@ -5,6 +5,7 @@ import dialogHelper from '../dialogHelper/dialogHelper';
import inputManager from '../../scripts/inputManager';
import layoutManager from '../layoutManager';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import * as userSettings from '../../scripts/settings/userSettings';
import '../../elements/emby-checkbox/emby-checkbox';
import '../../elements/emby-input/emby-input';
@@ -14,7 +15,6 @@ import '../../elements/emby-select/emby-select';
import 'material-design-icons-iconfont';
import '../formdialog.scss';
import '../../styles/flexstyles.scss';
-import ServerConnections from '../ServerConnections';
import template from './filtermenu.template.html';
function onSubmit(e) {
diff --git a/src/components/groupedcards.js b/src/components/groupedcards.js
index 9167dc3ec..1756280ed 100644
--- a/src/components/groupedcards.js
+++ b/src/components/groupedcards.js
@@ -1,7 +1,7 @@
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import dom from '../scripts/dom';
import { appRouter } from './router/appRouter';
import Dashboard from '../utils/dashboard';
-import ServerConnections from './ServerConnections';
function onGroupedCardClick(e, card) {
const itemId = card.getAttribute('data-id');
diff --git a/src/components/guide/guide.js b/src/components/guide/guide.js
index 0b72fc023..20795ea2d 100644
--- a/src/components/guide/guide.js
+++ b/src/components/guide/guide.js
@@ -2,6 +2,7 @@ import escapeHtml from 'escape-html';
import inputManager from '../../scripts/inputManager';
import browser from '../../scripts/browser';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import Events from '../../utils/events.ts';
import scrollHelper from '../../scripts/scrollHelper';
import serverNotifications from '../../scripts/serverNotifications';
@@ -25,7 +26,6 @@ import '../../elements/emby-tabs/emby-tabs';
import '../../elements/emby-scroller/emby-scroller';
import '../../styles/flexstyles.scss';
import 'webcomponents.js/webcomponents-lite';
-import ServerConnections from '../ServerConnections';
import template from './tvguide.template.html';
function showViewSettings(instance) {
diff --git a/src/components/homeScreenSettings/homeScreenSettings.js b/src/components/homeScreenSettings/homeScreenSettings.js
index 74b5c82ce..d330c8f22 100644
--- a/src/components/homeScreenSettings/homeScreenSettings.js
+++ b/src/components/homeScreenSettings/homeScreenSettings.js
@@ -2,6 +2,7 @@
import escapeHtml from 'escape-html';
import { getUserViewsQuery } from 'hooks/useUserViews';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import { toApi } from 'utils/jellyfin-apiclient/compat';
import { queryClient } from 'utils/query/queryClient';
@@ -15,7 +16,6 @@ import dom from '../../scripts/dom';
import '../listview/listview.scss';
import '../../elements/emby-select/emby-select';
import '../../elements/emby-checkbox/emby-checkbox';
-import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
import template from './homeScreenSettings.template.html';
import { LibraryTab } from '../../types/libraryTab.ts';
diff --git a/src/components/homesections/sections/activeRecordings.ts b/src/components/homesections/sections/activeRecordings.ts
index 45989f298..72ab3dfe6 100644
--- a/src/components/homesections/sections/activeRecordings.ts
+++ b/src/components/homesections/sections/activeRecordings.ts
@@ -1,9 +1,9 @@
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto';
import type { ApiClient } from 'jellyfin-apiclient';
-import ServerConnections from 'components/ServerConnections';
import cardBuilder from 'components/cardbuilder/cardBuilder';
import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import type { SectionContainerElement, SectionOptions } from './section';
diff --git a/src/components/homesections/sections/liveTv.ts b/src/components/homesections/sections/liveTv.ts
index e09f812fd..4b8b17cbd 100644
--- a/src/components/homesections/sections/liveTv.ts
+++ b/src/components/homesections/sections/liveTv.ts
@@ -5,8 +5,8 @@ import type { ApiClient } from 'jellyfin-apiclient';
import { appRouter } from 'components/router/appRouter';
import cardBuilder from 'components/cardbuilder/cardBuilder';
import layoutManager from 'components/layoutManager';
-import ServerConnections from 'components/ServerConnections';
import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import { getBackdropShape } from 'utils/card';
import type { SectionContainerElement, SectionOptions } from './section';
diff --git a/src/components/homesections/sections/nextUp.ts b/src/components/homesections/sections/nextUp.ts
index fbf1443e2..539472bbf 100644
--- a/src/components/homesections/sections/nextUp.ts
+++ b/src/components/homesections/sections/nextUp.ts
@@ -4,8 +4,8 @@ import type { ApiClient } from 'jellyfin-apiclient';
import cardBuilder from 'components/cardbuilder/cardBuilder';
import layoutManager from 'components/layoutManager';
import { appRouter } from 'components/router/appRouter';
-import ServerConnections from 'components/ServerConnections';
import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import type { UserSettings } from 'scripts/settings/userSettings';
import { getBackdropShape } from 'utils/card';
diff --git a/src/components/homesections/sections/recentlyAdded.ts b/src/components/homesections/sections/recentlyAdded.ts
index 27d16d161..5f35ba303 100644
--- a/src/components/homesections/sections/recentlyAdded.ts
+++ b/src/components/homesections/sections/recentlyAdded.ts
@@ -5,11 +5,11 @@ import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collec
import escapeHtml from 'escape-html';
import type { ApiClient } from 'jellyfin-apiclient';
+import cardBuilder from 'components/cardbuilder/cardBuilder';
import layoutManager from 'components/layoutManager';
import { appRouter } from 'components/router/appRouter';
import globalize from 'lib/globalize';
-import ServerConnections from 'components/ServerConnections';
-import cardBuilder from 'components/cardbuilder/cardBuilder';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import { getBackdropShape, getPortraitShape, getSquareShape } from 'utils/card';
import type { SectionContainerElement, SectionOptions } from './section';
diff --git a/src/components/homesections/sections/resume.ts b/src/components/homesections/sections/resume.ts
index 249cad2d0..bb9215b2b 100644
--- a/src/components/homesections/sections/resume.ts
+++ b/src/components/homesections/sections/resume.ts
@@ -2,9 +2,9 @@ import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base
import type { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
import type { ApiClient } from 'jellyfin-apiclient';
-import ServerConnections from 'components/ServerConnections';
import cardBuilder from 'components/cardbuilder/cardBuilder';
import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import type { UserSettings } from 'scripts/settings/userSettings';
import { getBackdropShape, getPortraitShape } from 'utils/card';
diff --git a/src/components/imageDownloader/imageDownloader.js b/src/components/imageDownloader/imageDownloader.js
index cd05ec57e..b4ddaf85d 100644
--- a/src/components/imageDownloader/imageDownloader.js
+++ b/src/components/imageDownloader/imageDownloader.js
@@ -7,12 +7,12 @@ import browser from '../../scripts/browser';
import layoutManager from '../layoutManager';
import scrollHelper from '../../scripts/scrollHelper';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import '../../elements/emby-checkbox/emby-checkbox';
import '../../elements/emby-button/paper-icon-button-light';
import '../../elements/emby-button/emby-button';
import '../formdialog.scss';
import '../cardbuilder/card.scss';
-import ServerConnections from '../ServerConnections';
import template from './imageDownloader.template.html';
const enableFocusTransform = !browser.slow && !browser.edge;
diff --git a/src/components/imageUploader/imageUploader.js b/src/components/imageUploader/imageUploader.js
index cc211fde9..1f26dbff3 100644
--- a/src/components/imageUploader/imageUploader.js
+++ b/src/components/imageUploader/imageUploader.js
@@ -10,11 +10,12 @@ import loading from '../loading/loading';
import scrollHelper from '../../scripts/scrollHelper';
import layoutManager from '../layoutManager';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
+
import '../../elements/emby-button/emby-button';
import '../../elements/emby-select/emby-select';
import '../formdialog.scss';
import './style.scss';
-import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
import template from './imageUploader.template.html';
diff --git a/src/components/imageeditor/imageeditor.js b/src/components/imageeditor/imageeditor.js
index c975cdd7d..ab5f7832d 100644
--- a/src/components/imageeditor/imageeditor.js
+++ b/src/components/imageeditor/imageeditor.js
@@ -4,6 +4,7 @@ import dom from '../../scripts/dom';
import layoutManager from '../layoutManager';
import focusManager from '../focusManager';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import scrollHelper from '../../scripts/scrollHelper';
import imageLoader from '../images/imageLoader';
import browser from '../../scripts/browser';
@@ -13,7 +14,6 @@ import '../formdialog.scss';
import '../../elements/emby-button/emby-button';
import '../../elements/emby-button/paper-icon-button-light';
import './imageeditor.scss';
-import ServerConnections from '../ServerConnections';
import alert from '../alert';
import confirm from '../confirm/confirm';
import template from './imageeditor.template.html';
diff --git a/src/components/itemContextMenu.js b/src/components/itemContextMenu.js
index adba9ab0e..959b391d8 100644
--- a/src/components/itemContextMenu.js
+++ b/src/components/itemContextMenu.js
@@ -2,12 +2,12 @@ import browser from '../scripts/browser';
import { copy } from '../scripts/clipboard';
import dom from '../scripts/dom';
import globalize from '../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import actionsheet from './actionSheet/actionSheet';
import { appHost } from './apphost';
import { appRouter } from './router/appRouter';
import itemHelper, { canEditPlaylist } from './itemHelper';
import { playbackManager } from './playback/playbackmanager';
-import ServerConnections from './ServerConnections';
import toast from './toast/toast';
import * as userSettings from '../scripts/settings/userSettings';
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
diff --git a/src/components/itemHelper.js b/src/components/itemHelper.js
index 2a93c36e7..fa8aadbdd 100644
--- a/src/components/itemHelper.js
+++ b/src/components/itemHelper.js
@@ -7,7 +7,7 @@ import { getPlaylistsApi } from '@jellyfin/sdk/lib/utils/api/playlists-api';
import { appHost } from './apphost';
import globalize from 'lib/globalize';
-import ServerConnections from './ServerConnections';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import { toApi } from 'utils/jellyfin-apiclient/compat';
export function getDisplayName(item, options = {}) {
diff --git a/src/components/itemMediaInfo/itemMediaInfo.js b/src/components/itemMediaInfo/itemMediaInfo.js
index f77395abf..70c35e848 100644
--- a/src/components/itemMediaInfo/itemMediaInfo.js
+++ b/src/components/itemMediaInfo/itemMediaInfo.js
@@ -11,6 +11,7 @@ import toast from '../toast/toast';
import { copy } from '../../scripts/clipboard';
import dom from '../../scripts/dom';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import itemHelper from '../../components/itemHelper';
import loading from '../loading/loading';
import '../../elements/emby-select/emby-select';
@@ -20,7 +21,6 @@ import '../../elements/emby-button/paper-icon-button-light';
import '../formdialog.scss';
import 'material-design-icons-iconfont';
import '../../styles/flexstyles.scss';
-import ServerConnections from '../ServerConnections';
import template from './itemMediaInfo.template.html';
// Do not add extra spaces between tags - they will be copied into the result
diff --git a/src/components/itemidentifier/itemidentifier.js b/src/components/itemidentifier/itemidentifier.js
index 506c1c284..b374e47da 100644
--- a/src/components/itemidentifier/itemidentifier.js
+++ b/src/components/itemidentifier/itemidentifier.js
@@ -8,6 +8,7 @@ import escapeHtml from 'escape-html';
import dialogHelper from '../dialogHelper/dialogHelper';
import loading from '../loading/loading';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import scrollHelper from '../../scripts/scrollHelper';
import layoutManager from '../layoutManager';
import focusManager from '../focusManager';
@@ -18,7 +19,6 @@ import '../../elements/emby-button/paper-icon-button-light';
import '../formdialog.scss';
import 'material-design-icons-iconfont';
import '../cardbuilder/card.scss';
-import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
import template from './itemidentifier.template.html';
import datetime from '../../scripts/datetime';
diff --git a/src/components/listview/listview.js b/src/components/listview/listview.js
index 916b9a44a..5b6c54c2b 100644
--- a/src/components/listview/listview.js
+++ b/src/components/listview/listview.js
@@ -10,12 +10,12 @@ import mediaInfo from '../mediainfo/mediainfo';
import indicators from '../indicators/indicators';
import layoutManager from '../layoutManager';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import datetime from '../../scripts/datetime';
import cardBuilder from '../cardbuilder/cardBuilder';
import './listview.scss';
import '../../elements/emby-ratingbutton/emby-ratingbutton';
import '../../elements/emby-playstatebutton/emby-playstatebutton';
-import ServerConnections from '../ServerConnections';
import { getDefaultBackgroundClass } from '../cardbuilder/cardBuilderUtils';
import markdownIt from 'markdown-it';
import DOMPurify from 'dompurify';
diff --git a/src/components/lyricseditor/lyricseditor.js b/src/components/lyricseditor/lyricseditor.js
index f2e37175f..bb393ea48 100644
--- a/src/components/lyricseditor/lyricseditor.js
+++ b/src/components/lyricseditor/lyricseditor.js
@@ -5,6 +5,7 @@ import { toApi } from 'utils/jellyfin-apiclient/compat';
import dialogHelper from '../dialogHelper/dialogHelper';
import layoutManager from '../layoutManager';
import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import loading from '../loading/loading';
import focusManager from '../focusManager';
import dom from '../../scripts/dom';
@@ -16,7 +17,6 @@ import 'material-design-icons-iconfont';
import './lyricseditor.scss';
import '../../elements/emby-button/emby-button';
import '../../styles/flexstyles.scss';
-import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
import template from './lyricseditor.template.html';
import templatePreview from './lyricspreview.template.html';
diff --git a/src/components/lyricsuploader/lyricsuploader.js b/src/components/lyricsuploader/lyricsuploader.js
index 6d6030a7b..6755e4fc0 100644
--- a/src/components/lyricsuploader/lyricsuploader.js
+++ b/src/components/lyricsuploader/lyricsuploader.js
@@ -3,12 +3,12 @@ import escapeHtml from 'escape-html';
import { getLyricsApi } from '@jellyfin/sdk/lib/utils/api/lyrics-api';
import { toApi } from 'utils/jellyfin-apiclient/compat';
import dialogHelper from '../../components/dialogHelper/dialogHelper';
-import ServerConnections from '../ServerConnections';
import dom from '../../scripts/dom';
import loading from '../../components/loading/loading';
import scrollHelper from '../../scripts/scrollHelper';
import layoutManager from '../layoutManager';
import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import template from './lyricsuploader.template.html';
import toast from '../toast/toast';
import '../../elements/emby-button/emby-button';
diff --git a/src/components/metadataEditor/metadataEditor.js b/src/components/metadataEditor/metadataEditor.js
index 4dea1b00d..6dc155a84 100644
--- a/src/components/metadataEditor/metadataEditor.js
+++ b/src/components/metadataEditor/metadataEditor.js
@@ -6,6 +6,8 @@ import datetime from '../../scripts/datetime';
import loading from '../loading/loading';
import focusManager from '../focusManager';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
+
import '../../elements/emby-checkbox/emby-checkbox';
import '../../elements/emby-input/emby-input';
import '../../elements/emby-select/emby-select';
@@ -17,7 +19,6 @@ import '../formdialog.scss';
import '../../styles/clearbutton.scss';
import '../../styles/flexstyles.scss';
import './style.scss';
-import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
import { appRouter } from '../router/appRouter';
import template from './metadataEditor.template.html';
diff --git a/src/components/multiSelect/multiSelect.js b/src/components/multiSelect/multiSelect.js
index efcb17265..95e59d9ca 100644
--- a/src/components/multiSelect/multiSelect.js
+++ b/src/components/multiSelect/multiSelect.js
@@ -2,9 +2,9 @@ import browser from '../../scripts/browser';
import { appHost } from '../apphost';
import loading from '../loading/loading';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import dom from '../../scripts/dom';
import './multiSelect.scss';
-import ServerConnections from '../ServerConnections';
import alert from '../alert';
import confirm from '../confirm/confirm';
import itemHelper from '../itemHelper';
diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js
index 9c8982b51..9edd0ec18 100644
--- a/src/components/notifications/notifications.js
+++ b/src/components/notifications/notifications.js
@@ -2,8 +2,8 @@ import serverNotifications from '../../scripts/serverNotifications';
import { playbackManager } from '../playback/playbackmanager';
import Events from '../../utils/events.ts';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts';
-import ServerConnections from '../../components/ServerConnections';
import NotificationIcon from './notificationicon.png';
diff --git a/src/components/nowPlayingBar/nowPlayingBar.js b/src/components/nowPlayingBar/nowPlayingBar.js
index c16b167d0..0184cec0b 100644
--- a/src/components/nowPlayingBar/nowPlayingBar.js
+++ b/src/components/nowPlayingBar/nowPlayingBar.js
@@ -1,6 +1,7 @@
import { getImageUrl } from 'apps/stable/features/playback/utils/image';
import { getItemTextLines } from 'apps/stable/features/playback/utils/itemText';
import { appRouter, isLyricsPage } from 'components/router/appRouter';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import datetime from '../../scripts/datetime';
import Events from '../../utils/events.ts';
@@ -14,7 +15,6 @@ import globalize from 'lib/globalize';
import itemContextMenu from '../itemContextMenu';
import '../../elements/emby-button/paper-icon-button-light';
import '../../elements/emby-ratingbutton/emby-ratingbutton';
-import ServerConnections from '../ServerConnections';
import appFooter from '../appFooter/appFooter';
import itemShortcuts from '../shortcuts';
import './nowPlayingBar.scss';
diff --git a/src/components/playback/displayMirrorManager.ts b/src/components/playback/displayMirrorManager.ts
index 00856421f..63d7059f8 100644
--- a/src/components/playback/displayMirrorManager.ts
+++ b/src/components/playback/displayMirrorManager.ts
@@ -1,5 +1,5 @@
-import ServerConnections from 'components/ServerConnections';
import { getItemQuery } from 'hooks/useItem';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import { toApi } from 'utils/jellyfin-apiclient/compat';
import { queryClient } from 'utils/query/queryClient';
diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js
index f4e8ec02c..b3c519111 100644
--- a/src/components/playback/playbackmanager.js
+++ b/src/components/playback/playbackmanager.js
@@ -1,3 +1,4 @@
+import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind.js';
import { PlaybackErrorCode } from '@jellyfin/sdk/lib/generated-client/models/playback-error-code.js';
import { getMediaInfoApi } from '@jellyfin/sdk/lib/utils/api/media-info-api';
import { MediaType } from '@jellyfin/sdk/lib/generated-client/models/media-type';
@@ -14,7 +15,6 @@ import * as userSettings from '../../scripts/settings/userSettings';
import globalize from '../../lib/globalize';
import loading from '../loading/loading';
import { appHost } from '../apphost';
-import ServerConnections from '../ServerConnections';
import alert from '../alert';
import { PluginType } from '../../types/plugin.ts';
import { includesAny } from '../../utils/container.ts';
@@ -24,10 +24,10 @@ import { getItemBackdropImageUrl } from '../../utils/jellyfin-apiclient/backdrop
import { PlayerEvent } from 'apps/stable/features/playback/constants/playerEvent';
import { bindMediaSegmentManager } from 'apps/stable/features/playback/utils/mediaSegmentManager';
import { bindMediaSessionSubscriber } from 'apps/stable/features/playback/utils/mediaSessionSubscriber';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import { MediaError } from 'types/mediaError';
import { getMediaError } from 'utils/mediaError';
import { toApi } from 'utils/jellyfin-apiclient/compat';
-import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind.js';
import { bindSkipSegment } from './skipsegment.ts';
const UNLIMITED_ITEMS = -1;
diff --git a/src/components/playback/playersettingsmenu.js b/src/components/playback/playersettingsmenu.js
index dc92de54d..5b0aca301 100644
--- a/src/components/playback/playersettingsmenu.js
+++ b/src/components/playback/playersettingsmenu.js
@@ -1,8 +1,8 @@
import actionsheet from '../actionSheet/actionSheet';
import { playbackManager } from '../playback/playbackmanager';
-import globalize from '../../lib/globalize';
+import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import qualityoptions from '../qualityOptions';
-import ServerConnections from '../ServerConnections';
function showQualityMenu(player, btn) {
const videoStream = playbackManager.currentMediaSource(player).MediaStreams.filter(function (stream) {
diff --git a/src/components/playbackSettings/playbackSettings.js b/src/components/playbackSettings/playbackSettings.js
index f15627a3e..0050e0c66 100644
--- a/src/components/playbackSettings/playbackSettings.js
+++ b/src/components/playbackSettings/playbackSettings.js
@@ -3,6 +3,7 @@ import escapeHTML from 'escape-html';
import { MediaSegmentAction } from 'apps/stable/features/playback/constants/mediaSegmentAction';
import { getId, getMediaSegmentAction } from 'apps/stable/features/playback/utils/mediaSegmentSettings';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import appSettings from '../../scripts/settings/appSettings';
import { appHost } from '../apphost';
@@ -12,7 +13,6 @@ import qualityoptions from '../qualityOptions';
import globalize from '../../lib/globalize';
import loading from '../loading/loading';
import Events from '../../utils/events.ts';
-import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
import template from './playbackSettings.template.html';
diff --git a/src/components/playerstats/playerstats.js b/src/components/playerstats/playerstats.js
index 663c1932e..97d4a7afd 100644
--- a/src/components/playerstats/playerstats.js
+++ b/src/components/playerstats/playerstats.js
@@ -1,13 +1,14 @@
import '../../elements/emby-button/paper-icon-button-light';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import Events from '../../utils/events.ts';
import layoutManager from '../layoutManager';
import { playbackManager } from '../playback/playbackmanager';
import playMethodHelper from '../playback/playmethodhelper';
import { pluginManager } from '../pluginManager';
import { PluginType } from '../../types/plugin.ts';
+
import './playerstats.scss';
-import ServerConnections from '../ServerConnections';
function init(instance) {
const parent = document.createElement('div');
diff --git a/src/components/playlisteditor/playlisteditor.ts b/src/components/playlisteditor/playlisteditor.ts
index 629790695..c9d9ac5af 100644
--- a/src/components/playlisteditor/playlisteditor.ts
+++ b/src/components/playlisteditor/playlisteditor.ts
@@ -8,6 +8,7 @@ import escapeHtml from 'escape-html';
import toast from 'components/toast/toast';
import dom from 'scripts/dom';
import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import { currentSettings as userSettings } from 'scripts/settings/userSettings';
import { PluginType } from 'types/plugin';
import { toApi } from 'utils/jellyfin-apiclient/compat';
@@ -19,7 +20,6 @@ import layoutManager from '../layoutManager';
import { playbackManager } from '../playback/playbackmanager';
import { pluginManager } from '../pluginManager';
import { appRouter } from '../router/appRouter';
-import ServerConnections from '../ServerConnections';
import 'elements/emby-button/emby-button';
import 'elements/emby-input/emby-input';
diff --git a/src/components/pluginManager.js b/src/components/pluginManager.js
index ce42c806d..7f9d57dd1 100644
--- a/src/components/pluginManager.js
+++ b/src/components/pluginManager.js
@@ -8,8 +8,8 @@ import { appRouter } from './router/appRouter';
import * as inputManager from '../scripts/inputManager';
import toast from '../components/toast/toast';
import confirm from '../components/confirm/confirm';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import * as dashboard from '../utils/dashboard';
-import ServerConnections from '../components/ServerConnections';
// TODO: replace with each plugin version
const cacheParam = new Date().getTime();
diff --git a/src/components/recordingcreator/recordingbutton.js b/src/components/recordingcreator/recordingbutton.js
index f2d5376ea..90e266f7e 100644
--- a/src/components/recordingcreator/recordingbutton.js
+++ b/src/components/recordingcreator/recordingbutton.js
@@ -1,9 +1,10 @@
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import dom from '../../scripts/dom';
import recordingHelper from './recordinghelper';
+
import '../../elements/emby-button/paper-icon-button-light';
import '../../elements/emby-button/emby-button';
import './recordingfields.scss';
-import ServerConnections from '../ServerConnections';
function onRecordingButtonClick() {
const item = this.item;
diff --git a/src/components/recordingcreator/recordingcreator.js b/src/components/recordingcreator/recordingcreator.js
index d69043f60..14a4c6d0d 100644
--- a/src/components/recordingcreator/recordingcreator.js
+++ b/src/components/recordingcreator/recordingcreator.js
@@ -1,5 +1,6 @@
import dialogHelper from '../dialogHelper/dialogHelper';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import layoutManager from '../layoutManager';
import mediaInfo from '../mediainfo/mediainfo';
import loading from '../loading/loading';
@@ -8,6 +9,7 @@ import datetime from '../../scripts/datetime';
import imageLoader from '../images/imageLoader';
import RecordingFields from './recordingfields';
import Events from '../../utils/events.ts';
+
import '../../elements/emby-button/emby-button';
import '../../elements/emby-button/paper-icon-button-light';
import '../../elements/emby-checkbox/emby-checkbox';
@@ -16,7 +18,6 @@ import '../../elements/emby-input/emby-input';
import '../formdialog.scss';
import './recordingcreator.scss';
import 'material-design-icons-iconfont';
-import ServerConnections from '../ServerConnections';
import { playbackManager } from '../playback/playbackmanager';
import template from './recordingcreator.template.html';
diff --git a/src/components/recordingcreator/recordingeditor.js b/src/components/recordingcreator/recordingeditor.js
index 442fe8663..e35d98024 100644
--- a/src/components/recordingcreator/recordingeditor.js
+++ b/src/components/recordingcreator/recordingeditor.js
@@ -1,9 +1,11 @@
import dialogHelper from '../dialogHelper/dialogHelper';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import layoutManager from '../layoutManager';
import loading from '../loading/loading';
import scrollHelper from '../../scripts/scrollHelper';
+
import '../../styles/scrollstyles.scss';
import '../../elements/emby-button/emby-button';
import '../../elements/emby-collapse/emby-collapse';
@@ -13,7 +15,6 @@ import '../formdialog.scss';
import './recordingcreator.scss';
import 'material-design-icons-iconfont';
import '../../styles/flexstyles.scss';
-import ServerConnections from '../ServerConnections';
import template from './recordingeditor.template.html';
let currentDialog;
diff --git a/src/components/recordingcreator/recordingfields.js b/src/components/recordingcreator/recordingfields.js
index 19249218b..a1d3103c8 100644
--- a/src/components/recordingcreator/recordingfields.js
+++ b/src/components/recordingcreator/recordingfields.js
@@ -1,14 +1,15 @@
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import Events from '../../utils/events.ts';
import serverNotifications from '../../scripts/serverNotifications';
import loading from '../loading/loading';
import dom from '../../scripts/dom';
import recordingHelper from './recordinghelper';
+
import '../../elements/emby-button/emby-button';
import '../../elements/emby-button/paper-icon-button-light';
import './recordingfields.scss';
import '../../styles/flexstyles.scss';
-import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
import template from './recordingfields.template.html';
diff --git a/src/components/recordingcreator/recordinghelper.js b/src/components/recordingcreator/recordinghelper.js
index 3c593a18a..a251590a3 100644
--- a/src/components/recordingcreator/recordinghelper.js
+++ b/src/components/recordingcreator/recordinghelper.js
@@ -1,6 +1,6 @@
-import globalize from '../../lib/globalize';
+import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import loading from '../loading/loading';
-import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
import confirm from '../confirm/confirm';
import dialog from '../dialog/dialog';
diff --git a/src/components/recordingcreator/seriesrecordingeditor.js b/src/components/recordingcreator/seriesrecordingeditor.js
index 30c8e3936..6580b03af 100644
--- a/src/components/recordingcreator/seriesrecordingeditor.js
+++ b/src/components/recordingcreator/seriesrecordingeditor.js
@@ -1,9 +1,11 @@
import dialogHelper from '../dialogHelper/dialogHelper';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import layoutManager from '../layoutManager';
import loading from '../loading/loading';
import scrollHelper from '../../scripts/scrollHelper';
import datetime from '../../scripts/datetime';
+
import '../../styles/scrollstyles.scss';
import '../../elements/emby-button/emby-button';
import '../../elements/emby-checkbox/emby-checkbox';
@@ -14,7 +16,6 @@ import '../formdialog.scss';
import './recordingcreator.scss';
import 'material-design-icons-iconfont';
import '../../styles/flexstyles.scss';
-import ServerConnections from '../ServerConnections';
import template from './seriesrecordingeditor.template.html';
let currentDialog;
diff --git a/src/components/refreshdialog/refreshdialog.js b/src/components/refreshdialog/refreshdialog.js
index 65997a31a..721ba36e6 100644
--- a/src/components/refreshdialog/refreshdialog.js
+++ b/src/components/refreshdialog/refreshdialog.js
@@ -3,6 +3,8 @@ import dialogHelper from '../dialogHelper/dialogHelper';
import loading from '../loading/loading';
import layoutManager from '../layoutManager';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
+
import '../../elements/emby-input/emby-input';
import '../../elements/emby-button/emby-button';
import '../../elements/emby-button/paper-icon-button-light';
@@ -10,7 +12,6 @@ import '../../elements/emby-checkbox/emby-checkbox';
import '../../elements/emby-select/emby-select';
import 'material-design-icons-iconfont';
import '../formdialog.scss';
-import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
function getEditorHtml() {
diff --git a/src/components/remotecontrol/remotecontrol.js b/src/components/remotecontrol/remotecontrol.js
index b4344b5f1..a2d32ed24 100644
--- a/src/components/remotecontrol/remotecontrol.js
+++ b/src/components/remotecontrol/remotecontrol.js
@@ -11,9 +11,11 @@ import { playbackManager } from '../playback/playbackmanager';
import Events from '../../utils/events.ts';
import { appHost } from '../apphost';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import layoutManager from '../layoutManager';
import * as userSettings from '../../scripts/settings/userSettings';
import itemContextMenu from '../itemContextMenu';
+
import '../cardbuilder/card.scss';
import '../../elements/emby-button/emby-button';
import '../../elements/emby-button/paper-icon-button-light';
@@ -21,7 +23,6 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
import './remotecontrol.scss';
import '../../elements/emby-ratingbutton/emby-ratingbutton';
import '../../elements/emby-slider/emby-slider';
-import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
import { appRouter } from '../router/appRouter';
import { getDefaultBackgroundClass } from '../cardbuilder/cardBuilderUtils';
diff --git a/src/components/router/appRouter.js b/src/components/router/appRouter.js
index 29f3cf4f4..032ddfaf1 100644
--- a/src/components/router/appRouter.js
+++ b/src/components/router/appRouter.js
@@ -4,12 +4,12 @@ import { setBackdropTransparency } from '../backdrop/backdrop';
import globalize from '../../lib/globalize';
import itemHelper from '../itemHelper';
import loading from '../loading/loading';
-import ServerConnections from '../ServerConnections';
import alert from '../alert';
-import { queryClient } from 'utils/query/queryClient';
import { getItemQuery } from 'hooks/useItem';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import { toApi } from 'utils/jellyfin-apiclient/compat';
+import { queryClient } from 'utils/query/queryClient';
import { history } from 'RootAppRouter';
/** Pages of "no return" (when "Go back" should behave differently, probably quitting the application). */
diff --git a/src/components/shortcuts.js b/src/components/shortcuts.js
index d76fa0c81..1ff18ef23 100644
--- a/src/components/shortcuts.js
+++ b/src/components/shortcuts.js
@@ -8,9 +8,9 @@ import { playbackManager } from './playback/playbackmanager';
import inputManager from '../scripts/inputManager';
import { appRouter } from './router/appRouter';
import globalize from '../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import dom from '../scripts/dom';
import recordingHelper from './recordingcreator/recordinghelper';
-import ServerConnections from './ServerConnections';
import toast from './toast/toast';
import * as userSettings from '../scripts/settings/userSettings';
import { toApi } from 'utils/jellyfin-apiclient/compat';
diff --git a/src/components/slideshow/slideshow.js b/src/components/slideshow/slideshow.js
index 522a6f738..49bf8dc1e 100644
--- a/src/components/slideshow/slideshow.js
+++ b/src/components/slideshow/slideshow.js
@@ -3,16 +3,17 @@
* @module components/slideshow/slideshow
*/
import dialogHelper from '../dialogHelper/dialogHelper';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import inputManager from '../../scripts/inputManager';
import layoutManager from '../layoutManager';
import focusManager from '../focusManager';
import browser from '../../scripts/browser';
import { appHost } from '../apphost';
import dom from '../../scripts/dom';
+
import './style.scss';
import 'material-design-icons-iconfont';
import '../../elements/emby-button/paper-icon-button-light';
-import ServerConnections from '../ServerConnections';
import screenfull from 'screenfull';
import { randomInt } from '../../utils/number.ts';
diff --git a/src/components/subtitleeditor/subtitleeditor.js b/src/components/subtitleeditor/subtitleeditor.js
index 106ef0b56..fa4d71e84 100644
--- a/src/components/subtitleeditor/subtitleeditor.js
+++ b/src/components/subtitleeditor/subtitleeditor.js
@@ -3,10 +3,12 @@ import { appHost } from '../apphost';
import dialogHelper from '../dialogHelper/dialogHelper';
import layoutManager from '../layoutManager';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import * as userSettings from '../../scripts/settings/userSettings';
import loading from '../loading/loading';
import focusManager from '../focusManager';
import dom from '../../scripts/dom';
+
import '../../elements/emby-select/emby-select';
import '../listview/listview.scss';
import '../../elements/emby-button/paper-icon-button-light';
@@ -15,7 +17,6 @@ import 'material-design-icons-iconfont';
import './subtitleeditor.scss';
import '../../elements/emby-button/emby-button';
import '../../styles/flexstyles.scss';
-import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
import confirm from '../confirm/confirm';
import template from './subtitleeditor.template.html';
diff --git a/src/components/subtitlesettings/subtitlesettings.js b/src/components/subtitlesettings/subtitlesettings.js
index 0ae3d6e9d..ee8b2c3ed 100644
--- a/src/components/subtitlesettings/subtitlesettings.js
+++ b/src/components/subtitlesettings/subtitlesettings.js
@@ -1,4 +1,5 @@
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import { appHost } from '../apphost';
import appSettings from '../../scripts/settings/appSettings';
import focusManager from '../focusManager';
@@ -8,6 +9,7 @@ import subtitleAppearanceHelper from './subtitleappearancehelper';
import settingsHelper from '../settingshelper';
import dom from '../../scripts/dom';
import Events from '../../utils/events.ts';
+
import '../listview/listview.scss';
import '../../elements/emby-select/emby-select';
import '../../elements/emby-slider/emby-slider';
@@ -15,7 +17,6 @@ import '../../elements/emby-input/emby-input';
import '../../elements/emby-checkbox/emby-checkbox';
import '../../styles/flexstyles.scss';
import './subtitlesettings.scss';
-import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
import template from './subtitlesettings.template.html';
diff --git a/src/components/subtitleuploader/subtitleuploader.js b/src/components/subtitleuploader/subtitleuploader.js
index dc13ada07..f70726577 100644
--- a/src/components/subtitleuploader/subtitleuploader.js
+++ b/src/components/subtitleuploader/subtitleuploader.js
@@ -3,14 +3,15 @@ import escapeHtml from 'escape-html';
import { getSubtitleApi } from '@jellyfin/sdk/lib/utils/api/subtitle-api';
import { toApi } from 'utils/jellyfin-apiclient/compat';
import dialogHelper from '../../components/dialogHelper/dialogHelper';
-import ServerConnections from '../ServerConnections';
import dom from '../../scripts/dom';
import loading from '../../components/loading/loading';
import scrollHelper from '../../scripts/scrollHelper';
import layoutManager from '../layoutManager';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import template from './subtitleuploader.template.html';
import toast from '../toast/toast';
+
import '../../elements/emby-button/emby-button';
import '../../elements/emby-select/emby-select';
import '../formdialog.scss';
diff --git a/src/components/themeMediaPlayer.js b/src/components/themeMediaPlayer.js
index fe25704ce..c8c267532 100644
--- a/src/components/themeMediaPlayer.js
+++ b/src/components/themeMediaPlayer.js
@@ -3,6 +3,7 @@ import { MediaType } from '@jellyfin/sdk/lib/generated-client/models/media-type'
import { getLibraryApi } from '@jellyfin/sdk/lib/utils/api/library-api';
import { getItemQuery } from 'hooks/useItem';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import { currentSettings as userSettings } from 'scripts/settings/userSettings';
import { ItemKind } from 'types/base/models/item-kind';
import Events from 'utils/events.ts';
@@ -10,7 +11,6 @@ import { toApi } from 'utils/jellyfin-apiclient/compat';
import { queryClient } from 'utils/query/queryClient';
import { playbackManager } from './playback/playbackmanager';
-import ServerConnections from './ServerConnections';
let currentOwnerId;
let currentThemeIds = [];
diff --git a/src/components/upnextdialog/upnextdialog.js b/src/components/upnextdialog/upnextdialog.js
index 74a0fc5ba..7bb40943d 100644
--- a/src/components/upnextdialog/upnextdialog.js
+++ b/src/components/upnextdialog/upnextdialog.js
@@ -5,8 +5,9 @@ import mediaInfo from '../mediainfo/mediainfo';
import layoutManager from '../layoutManager';
import focusManager from '../focusManager';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import itemHelper from '../itemHelper';
-import ServerConnections from '../ServerConnections';
+
import './upnextdialog.scss';
import '../../elements/emby-button/emby-button';
import '../../styles/flexstyles.scss';
diff --git a/src/components/userdatabuttons/userdatabuttons.js b/src/components/userdatabuttons/userdatabuttons.js
index 50b8b69e2..453664e39 100644
--- a/src/components/userdatabuttons/userdatabuttons.js
+++ b/src/components/userdatabuttons/userdatabuttons.js
@@ -1,11 +1,12 @@
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import dom from '../../scripts/dom';
import itemHelper from '../itemHelper';
+
import '../../elements/emby-button/paper-icon-button-light';
import 'material-design-icons-iconfont';
import '../../elements/emby-button/emby-button';
import './userdatabuttons.scss';
-import ServerConnections from '../ServerConnections';
const userDataMethods = {
markPlayed: markPlayed,
diff --git a/src/controllers/favorites.js b/src/controllers/favorites.js
index ccc689fc2..b926859f0 100644
--- a/src/controllers/favorites.js
+++ b/src/controllers/favorites.js
@@ -2,9 +2,9 @@ import cardBuilder from 'components/cardbuilder/cardBuilder';
import focusManager from 'components/focusManager';
import layoutManager from 'components/layoutManager';
import { appRouter } from 'components/router/appRouter';
-import ServerConnections from 'components/ServerConnections';
import dom from 'scripts/dom';
import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import { getBackdropShape, getPortraitShape, getSquareShape } from 'utils/card';
import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by';
diff --git a/src/controllers/hometab.js b/src/controllers/hometab.js
index 9b5c08707..15aefabb8 100644
--- a/src/controllers/hometab.js
+++ b/src/controllers/hometab.js
@@ -2,8 +2,9 @@ import * as userSettings from '../scripts/settings/userSettings';
import loading from '../components/loading/loading';
import focusManager from '../components/focusManager';
import homeSections from '../components/homesections/homesections';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
+
import '../elements/emby-itemscontainer/emby-itemscontainer';
-import ServerConnections from '../components/ServerConnections';
class HomeTab {
constructor(view, params) {
diff --git a/src/controllers/itemDetails/index.js b/src/controllers/itemDetails/index.js
index c3f37a155..122b6a7c3 100644
--- a/src/controllers/itemDetails/index.js
+++ b/src/controllers/itemDetails/index.js
@@ -21,12 +21,12 @@ import loading from 'components/loading/loading';
import { playbackManager } from 'components/playback/playbackmanager';
import { appRouter } from 'components/router/appRouter';
import itemShortcuts from 'components/shortcuts';
-import ServerConnections from 'components/ServerConnections';
+import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import browser from 'scripts/browser';
import datetime from 'scripts/datetime';
import dom from 'scripts/dom';
import { download } from 'scripts/fileDownloader';
-import globalize from 'lib/globalize';
import libraryMenu from 'scripts/libraryMenu';
import * as userSettings from 'scripts/settings/userSettings';
import { getPortraitShape, getSquareShape } from 'utils/card';
@@ -1379,7 +1379,6 @@ function renderChildren(page, item) {
if (item.Type == 'MusicAlbum') {
let showArtist = false;
for (const track of result.Items) {
- // eslint-disable-next-line sonarjs/no-alphabetical-sort
if (!isEqual(track.ArtistItems.map(x => x.Id).sort(), track.AlbumArtists.map(x => x.Id).sort())) {
showArtist = true;
break;
diff --git a/src/controllers/list.js b/src/controllers/list.js
index 479c647c2..2c3d09019 100644
--- a/src/controllers/list.js
+++ b/src/controllers/list.js
@@ -8,9 +8,9 @@ import AlphaNumericShortcuts from '../scripts/alphanumericshortcuts';
import libraryBrowser from '../scripts/libraryBrowser';
import { playbackManager } from '../components/playback/playbackmanager';
import AlphaPicker from '../components/alphaPicker/alphaPicker';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import '../elements/emby-itemscontainer/emby-itemscontainer';
import '../elements/emby-scroller/emby-scroller';
-import ServerConnections from '../components/ServerConnections';
import LibraryMenu from '../scripts/libraryMenu';
import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type';
import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by';
diff --git a/src/controllers/lyrics.js b/src/controllers/lyrics.js
index 6272f7fcc..36ae408bc 100644
--- a/src/controllers/lyrics.js
+++ b/src/controllers/lyrics.js
@@ -4,12 +4,12 @@ import autoFocuser from 'components/autoFocuser';
import { appRouter } from '../components/router/appRouter';
import layoutManager from 'components/layoutManager';
import { playbackManager } from '../components/playback/playbackmanager';
-import ServerConnections from '../components/ServerConnections';
import scrollManager from 'components/scrollManager';
import focusManager from 'components/focusManager';
import keyboardNavigation from 'scripts/keyboardNavigation';
import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import LibraryMenu from 'scripts/libraryMenu';
import Events from 'utils/events';
diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js
index b143df9d4..e0d51005f 100644
--- a/src/controllers/playback/video/index.js
+++ b/src/controllers/playback/video/index.js
@@ -19,10 +19,10 @@ import '../../../elements/emby-slider/emby-slider';
import '../../../elements/emby-button/paper-icon-button-light';
import '../../../elements/emby-ratingbutton/emby-ratingbutton';
import '../../../styles/videoosd.scss';
-import ServerConnections from '../../../components/ServerConnections';
import shell from '../../../scripts/shell';
import SubtitleSync from '../../../components/subtitlesync/subtitlesync';
import { appRouter } from '../../../components/router/appRouter';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import LibraryMenu from '../../../scripts/libraryMenu';
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components/backdrop/backdrop';
import { pluginManager } from '../../../components/pluginManager';
diff --git a/src/controllers/session/addServer/index.js b/src/controllers/session/addServer/index.js
index 3e10dd629..68b14e890 100644
--- a/src/controllers/session/addServer/index.js
+++ b/src/controllers/session/addServer/index.js
@@ -1,10 +1,10 @@
-import appSettings from '../../../scripts/settings/appSettings';
-import loading from '../../../components/loading/loading';
-import globalize from '../../../lib/globalize';
-import '../../../elements/emby-button/emby-button';
-import Dashboard from '../../../utils/dashboard';
-import ServerConnections from '../../../components/ServerConnections';
-import { ConnectionState } from '../../../utils/jellyfin-apiclient/ConnectionState.ts';
+import loading from 'components/loading/loading';
+import globalize from 'lib/globalize';
+import { ConnectionState, ServerConnections } from 'lib/jellyfin-apiclient';
+import appSettings from 'scripts/settings/appSettings';
+import Dashboard from 'utils/dashboard';
+
+import 'elements/emby-button/emby-button';
function handleConnectionResult(page, result) {
loading.hide();
diff --git a/src/controllers/session/login/index.js b/src/controllers/session/login/index.js
index 4c241389a..e99d14da4 100644
--- a/src/controllers/session/login/index.js
+++ b/src/controllers/session/login/index.js
@@ -11,12 +11,13 @@ import globalize from '../../../lib/globalize';
import '../../../components/cardbuilder/card.scss';
import '../../../elements/emby-checkbox/emby-checkbox';
import Dashboard from '../../../utils/dashboard';
-import ServerConnections from '../../../components/ServerConnections';
import toast from '../../../components/toast/toast';
import dialogHelper from '../../../components/dialogHelper/dialogHelper';
import baseAlert from '../../../components/alert';
-import './login.scss';
import { getDefaultBackgroundClass } from '../../../components/cardbuilder/cardBuilderUtils';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
+
+import './login.scss';
const enableFocusTransform = !browser.slow && !browser.edge;
diff --git a/src/controllers/session/selectServer/index.js b/src/controllers/session/selectServer/index.js
index 577c897ba..3ed198677 100644
--- a/src/controllers/session/selectServer/index.js
+++ b/src/controllers/session/selectServer/index.js
@@ -17,10 +17,9 @@ import '../../../elements/emby-itemscontainer/emby-itemscontainer';
import '../../../components/cardbuilder/card.scss';
import '../../../elements/emby-button/emby-button';
import Dashboard from '../../../utils/dashboard';
-import ServerConnections from '../../../components/ServerConnections';
import alert from '../../../components/alert';
-import { ConnectionState } from '../../../utils/jellyfin-apiclient/ConnectionState.ts';
import { getDefaultBackgroundClass } from '../../../components/cardbuilder/cardBuilderUtils';
+import { ConnectionState, ServerConnections } from 'lib/jellyfin-apiclient';
const enableFocusTransform = !browser.slow && !browser.edge;
diff --git a/src/elements/emby-itemscontainer/emby-itemscontainer.js b/src/elements/emby-itemscontainer/emby-itemscontainer.js
index dd40a8053..e1657d095 100644
--- a/src/elements/emby-itemscontainer/emby-itemscontainer.js
+++ b/src/elements/emby-itemscontainer/emby-itemscontainer.js
@@ -1,3 +1,6 @@
+import 'webcomponents.js/webcomponents-lite';
+import Sortable from 'sortablejs';
+
import itemShortcuts from '../../components/shortcuts';
import inputManager from '../../scripts/inputManager';
import { playbackManager } from '../../components/playback/playbackmanager';
@@ -7,11 +10,9 @@ import browser from '../../scripts/browser';
import dom from '../../scripts/dom';
import loading from '../../components/loading/loading';
import focusManager from '../../components/focusManager';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import serverNotifications from '../../scripts/serverNotifications';
import Events from '../../utils/events.ts';
-import 'webcomponents.js/webcomponents-lite';
-import ServerConnections from '../../components/ServerConnections';
-import Sortable from 'sortablejs';
const ItemsContainerPrototype = Object.create(HTMLDivElement.prototype);
diff --git a/src/elements/emby-playstatebutton/emby-playstatebutton.js b/src/elements/emby-playstatebutton/emby-playstatebutton.js
index 1e83bb055..f41a14352 100644
--- a/src/elements/emby-playstatebutton/emby-playstatebutton.js
+++ b/src/elements/emby-playstatebutton/emby-playstatebutton.js
@@ -1,8 +1,8 @@
import serverNotifications from '../../scripts/serverNotifications';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import Events from '../../utils/events.ts';
import EmbyButtonPrototype from '../../elements/emby-button/emby-button';
-import ServerConnections from '../../components/ServerConnections';
function addNotificationEvent(instance, name, handler) {
const localHandler = handler.bind(instance);
diff --git a/src/elements/emby-ratingbutton/emby-ratingbutton.js b/src/elements/emby-ratingbutton/emby-ratingbutton.js
index a0621afea..40b581c7b 100644
--- a/src/elements/emby-ratingbutton/emby-ratingbutton.js
+++ b/src/elements/emby-ratingbutton/emby-ratingbutton.js
@@ -1,8 +1,8 @@
import serverNotifications from '../../scripts/serverNotifications';
import globalize from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import Events from '../../utils/events.ts';
import EmbyButtonPrototype from '../emby-button/emby-button';
-import ServerConnections from '../../components/ServerConnections';
function addNotificationEvent(instance, name, handler) {
const localHandler = handler.bind(instance);
diff --git a/src/hooks/useApi.tsx b/src/hooks/useApi.tsx
index 20614091b..b8bd88b42 100644
--- a/src/hooks/useApi.tsx
+++ b/src/hooks/useApi.tsx
@@ -3,9 +3,9 @@ import type { UserDto } from '@jellyfin/sdk/lib/generated-client';
import type { ApiClient, Event } from 'jellyfin-apiclient';
import React, { type FC, type PropsWithChildren, createContext, useContext, useEffect, useMemo, useState } from 'react';
-import ServerConnections from '../components/ServerConnections';
-import events from '../utils/events';
-import { toApi } from '../utils/jellyfin-apiclient/compat';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
+import events from 'utils/events';
+import { toApi } from 'utils/jellyfin-apiclient/compat';
export interface JellyfinApiContext {
__legacyApiClient__?: ApiClient
diff --git a/src/index.jsx b/src/index.jsx
index 66df6e33e..8d57c445f 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -5,7 +5,7 @@ import React from 'react';
import { createRoot } from 'react-dom/client';
// NOTE: We need to import this first to initialize the connection
-import ServerConnections from './components/ServerConnections';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import { appHost } from './components/apphost';
import autoFocuser from './components/autoFocuser';
@@ -23,7 +23,6 @@ import { pageClassOn, serverAddress } from './utils/dashboard';
import Events from './utils/events';
import RootApp from './RootApp';
-import { history } from 'RootAppRouter';
// Import the button webcomponent for use throughout the site
// NOTE: This is a bit of a hack, files should ensure the component is imported before use
@@ -206,7 +205,7 @@ async function renderApp() {
const root = createRoot(container);
root.render(
-
+
);
}
diff --git a/src/components/ServerConnections.js b/src/lib/jellyfin-apiclient/ServerConnections.js
similarity index 89%
rename from src/components/ServerConnections.js
rename to src/lib/jellyfin-apiclient/ServerConnections.js
index 38692998f..bae007970 100644
--- a/src/components/ServerConnections.js
+++ b/src/lib/jellyfin-apiclient/ServerConnections.js
@@ -1,16 +1,17 @@
// NOTE: This is used for jsdoc return type
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { Api } from '@jellyfin/sdk';
-import { MINIMUM_VERSION } from '@jellyfin/sdk/lib/versions';
-import { ConnectionManager, Credentials, ApiClient } from 'jellyfin-apiclient';
+import { Credentials, ApiClient } from 'jellyfin-apiclient';
-import { appHost } from './apphost';
-import Dashboard from '../utils/dashboard';
-import Events from '../utils/events.ts';
-import { setUserInfo } from '../scripts/settings/userSettings';
-import appSettings from '../scripts/settings/appSettings';
+import { appHost } from 'components/apphost';
+import appSettings from 'scripts/settings/appSettings';
+import { setUserInfo } from 'scripts/settings/userSettings';
+import Dashboard from 'utils/dashboard';
+import Events from 'utils/events.ts';
import { toApi } from 'utils/jellyfin-apiclient/compat';
+import ConnectionManager from './connectionManager';
+
const normalizeImageOptions = options => {
if (!options.quality && (options.maxWidth || options.width || options.maxHeight || options.height || options.fillWidth || options.fillHeight)) {
options.quality = 90;
@@ -37,9 +38,6 @@ class ServerConnections extends ConnectionManager {
this.localApiClient = null;
this.firstConnection = null;
- // Set the apiclient minimum version to match the SDK
- this._minServerVersion = MINIMUM_VERSION;
-
Events.on(this, 'localusersignedout', (_e, logoutInfo) => {
setUserInfo(null, null);
// Ensure the updated credentials are persisted to storage
diff --git a/src/lib/jellyfin-apiclient/connectionManager.js b/src/lib/jellyfin-apiclient/connectionManager.js
new file mode 100644
index 000000000..15d6c8591
--- /dev/null
+++ b/src/lib/jellyfin-apiclient/connectionManager.js
@@ -0,0 +1,772 @@
+import { AUTHORIZATION_HEADER } from '@jellyfin/sdk/lib/api';
+import { getAuthorizationHeader } from '@jellyfin/sdk/lib/utils';
+import { MINIMUM_VERSION } from '@jellyfin/sdk/lib/versions';
+import { ApiClient } from 'jellyfin-apiclient';
+
+import events from 'utils/events';
+import { ajax } from 'utils/fetch';
+import { equalsIgnoreCase } from 'utils/string';
+
+import { ConnectionMode } from './connectionMode';
+import { ConnectionState } from './connectionState';
+import { compareVersions } from './utils/compareVersions';
+
+const DEFAULT_CONNECTION_TIMEOUT = 20000;
+
+function getServerAddress(server, mode) {
+ switch (mode) {
+ case ConnectionMode.Local:
+ return server.LocalAddress;
+ case ConnectionMode.Manual:
+ return server.ManualAddress;
+ case ConnectionMode.Remote:
+ return server.RemoteAddress;
+ default:
+ return server.ManualAddress || server.LocalAddress || server.RemoteAddress;
+ }
+}
+
+function updateServerInfo(server, systemInfo) {
+ server.Name = systemInfo.ServerName;
+
+ if (systemInfo.Id) {
+ server.Id = systemInfo.Id;
+ }
+
+ if (systemInfo.LocalAddress) {
+ server.LocalAddress = systemInfo.LocalAddress;
+ }
+}
+
+function normalizeAddress(address) {
+ // Attempt to correct bad input
+ address = address.trim();
+
+ // Seeing failures in iOS when protocol isn't lowercase
+ address = address.replace('Http:', 'http:');
+ address = address.replace('Https:', 'https:');
+
+ return address;
+}
+
+export default class ConnectionManager {
+ constructor(credentialProvider, appName, appVersion, deviceName, deviceId, capabilities) {
+ console.log('Begin ConnectionManager constructor');
+
+ const self = this;
+ this._apiClients = [];
+
+ // Set the minimum version to match the SDK
+ self._minServerVersion = MINIMUM_VERSION;
+
+ self.appVersion = () => appVersion;
+
+ self.appName = () => appName;
+
+ self.capabilities = () => capabilities;
+
+ self.deviceId = () => deviceId;
+
+ self.credentialProvider = () => credentialProvider;
+
+ self.getServerInfo = (id) => {
+ const servers = credentialProvider.credentials().Servers;
+
+ return servers.filter((s) => s.Id === id)[0];
+ };
+
+ self.getLastUsedServer = () => {
+ const servers = credentialProvider.credentials().Servers;
+
+ servers.sort((a, b) => (b.DateLastAccessed || 0) - (a.DateLastAccessed || 0));
+
+ if (!servers.length) {
+ return null;
+ }
+
+ return servers[0];
+ };
+
+ self.addApiClient = (apiClient) => {
+ self._apiClients.push(apiClient);
+
+ const existingServers = credentialProvider
+ .credentials()
+ .Servers.filter(
+ (s) =>
+ equalsIgnoreCase(s.ManualAddress, apiClient.serverAddress())
+ || equalsIgnoreCase(s.LocalAddress, apiClient.serverAddress())
+ || equalsIgnoreCase(s.RemoteAddress, apiClient.serverAddress())
+ );
+
+ const existingServer = existingServers.length ? existingServers[0] : apiClient.serverInfo();
+ existingServer.DateLastAccessed = new Date().getTime();
+ existingServer.LastConnectionMode = ConnectionMode.Manual;
+ existingServer.ManualAddress = apiClient.serverAddress();
+
+ if (apiClient.manualAddressOnly) {
+ existingServer.manualAddressOnly = true;
+ }
+
+ apiClient.serverInfo(existingServer);
+
+ apiClient.onAuthenticated = (instance, result) => onAuthenticated(instance, result, {}, true);
+
+ if (!existingServers.length) {
+ const credentials = credentialProvider.credentials();
+ credentials.Servers = [existingServer];
+ credentialProvider.credentials(credentials);
+ }
+
+ events.trigger(self, 'apiclientcreated', [apiClient]);
+ };
+
+ self.clearData = () => {
+ console.log('connection manager clearing data');
+
+ const credentials = credentialProvider.credentials();
+ credentials.Servers = [];
+ credentialProvider.credentials(credentials);
+ };
+
+ self._getOrAddApiClient = (server, serverUrl) => {
+ let apiClient = self.getApiClient(server.Id);
+
+ if (!apiClient) {
+ apiClient = new ApiClient(serverUrl, appName, appVersion, deviceName, deviceId);
+
+ self._apiClients.push(apiClient);
+
+ apiClient.serverInfo(server);
+
+ apiClient.onAuthenticated = (instance, result) => {
+ return onAuthenticated(instance, result, {}, true);
+ };
+
+ events.trigger(self, 'apiclientcreated', [apiClient]);
+ }
+
+ console.log('returning instance from getOrAddApiClient');
+ return apiClient;
+ };
+
+ self.getOrCreateApiClient = (serverId) => {
+ const credentials = credentialProvider.credentials();
+ const servers = credentials.Servers.filter((s) => equalsIgnoreCase(s.Id, serverId));
+
+ if (!servers.length) {
+ throw new Error(`Server not found: ${serverId}`);
+ }
+
+ const server = servers[0];
+
+ return self._getOrAddApiClient(server, getServerAddress(server, server.LastConnectionMode));
+ };
+
+ function onAuthenticated(apiClient, result, options, saveCredentials) {
+ const credentials = credentialProvider.credentials();
+ const servers = credentials.Servers.filter((s) => s.Id === result.ServerId);
+
+ const server = servers.length ? servers[0] : apiClient.serverInfo();
+
+ if (options.updateDateLastAccessed !== false) {
+ server.DateLastAccessed = new Date().getTime();
+ }
+ server.Id = result.ServerId;
+
+ if (saveCredentials) {
+ server.UserId = result.User.Id;
+ server.AccessToken = result.AccessToken;
+ } else {
+ server.UserId = null;
+ server.AccessToken = null;
+ }
+
+ credentialProvider.addOrUpdateServer(credentials.Servers, server);
+ credentialProvider.credentials(credentials);
+
+ // set this now before updating server info, otherwise it won't be set in time
+ apiClient.enableAutomaticBitrateDetection = options.enableAutomaticBitrateDetection;
+
+ apiClient.serverInfo(server);
+ apiClient.setAuthenticationInfo(result.AccessToken, result.User.Id);
+ afterConnected(apiClient, options);
+
+ return onLocalUserSignIn(server, apiClient.serverAddress(), result.User);
+ }
+
+ function afterConnected(apiClient, options = {}) {
+ if (options.reportCapabilities !== false) {
+ apiClient.reportCapabilities(capabilities);
+ }
+ apiClient.enableAutomaticBitrateDetection = options.enableAutomaticBitrateDetection;
+
+ if (options.enableWebSocket !== false) {
+ console.log('calling apiClient.ensureWebSocket');
+
+ apiClient.ensureWebSocket();
+ }
+ }
+
+ function onLocalUserSignIn(server, serverUrl, user) {
+ // Ensure this is created so that listeners of the event can get the apiClient instance
+ self._getOrAddApiClient(server, serverUrl);
+
+ // This allows the app to have a single hook that fires before any other
+ const promise = self.onLocalUserSignedIn ? self.onLocalUserSignedIn.call(self, user) : Promise.resolve();
+
+ return promise.then(() => {
+ events.trigger(self, 'localusersignedin', [user]);
+ });
+ }
+
+ function validateAuthentication(server, serverUrl) {
+ return ajax({
+ type: 'GET',
+ url: `${serverUrl}/System/Info`,
+ dataType: 'json',
+ headers: {
+ [AUTHORIZATION_HEADER]: getAuthorizationHeader(
+ {
+ name: appName,
+ version: appVersion
+ },
+ {
+ id: deviceId,
+ name: deviceName
+ },
+ server.AccessToken
+ )
+ }
+ }).then(
+ (systemInfo) => {
+ updateServerInfo(server, systemInfo);
+ return Promise.resolve();
+ },
+ () => {
+ server.UserId = null;
+ server.AccessToken = null;
+ return Promise.resolve();
+ }
+ );
+ }
+
+ function getImageUrl(localUser) {
+ if (localUser && localUser.PrimaryImageTag) {
+ const apiClient = self.getApiClient(localUser);
+
+ const url = apiClient.getUserImageUrl(localUser.Id, {
+ tag: localUser.PrimaryImageTag,
+ type: 'Primary'
+ });
+
+ return {
+ url,
+ supportsParams: true
+ };
+ }
+
+ return {
+ url: null,
+ supportsParams: false
+ };
+ }
+
+ self.user = (apiClient) =>
+ new Promise((resolve) => {
+ let localUser;
+
+ function onLocalUserDone() {
+ if (apiClient && apiClient.getCurrentUserId()) {
+ apiClient.getCurrentUser().then((u) => {
+ localUser = u;
+ const image = getImageUrl(localUser);
+
+ resolve({
+ localUser,
+ name: localUser ? localUser.Name : null,
+ imageUrl: image.url,
+ supportsImageParams: image.supportsParams
+ });
+ });
+ }
+ }
+
+ if (apiClient && apiClient.getCurrentUserId()) {
+ onLocalUserDone();
+ }
+ });
+
+ self.logout = () => {
+ const promises = [];
+
+ for (let i = 0, length = self._apiClients.length; i < length; i++) {
+ const apiClient = self._apiClients[i];
+
+ if (apiClient.accessToken()) {
+ promises.push(logoutOfServer(apiClient));
+ }
+ }
+
+ return Promise.all(promises).then(() => {
+ const credentials = credentialProvider.credentials();
+
+ const servers = credentials.Servers.filter((u) => u.UserLinkType !== 'Guest');
+
+ for (let j = 0, numServers = servers.length; j < numServers; j++) {
+ const server = servers[j];
+
+ server.UserId = null;
+ server.AccessToken = null;
+ server.ExchangeToken = null;
+ }
+ });
+ };
+
+ function logoutOfServer(apiClient) {
+ const serverInfo = apiClient.serverInfo() || {};
+
+ const logoutInfo = {
+ serverId: serverInfo.Id
+ };
+
+ return apiClient.logout().then(
+ () => {
+ events.trigger(self, 'localusersignedout', [logoutInfo]);
+ },
+ () => {
+ events.trigger(self, 'localusersignedout', [logoutInfo]);
+ }
+ );
+ }
+
+ self.getSavedServers = () => {
+ const credentials = credentialProvider.credentials();
+
+ const servers = credentials.Servers.slice(0);
+
+ servers.sort((a, b) => (b.DateLastAccessed || 0) - (a.DateLastAccessed || 0));
+
+ return servers;
+ };
+
+ self.getAvailableServers = () => {
+ console.log('Begin getAvailableServers');
+
+ // Clone the array
+ const credentials = credentialProvider.credentials();
+
+ return Promise.all([findServers()]).then((responses) => {
+ const foundServers = responses[0];
+ const servers = credentials.Servers.slice(0);
+ foundServers.forEach(server => {
+ credentialProvider.addOrUpdateServer(servers, server);
+ });
+
+ servers.sort((a, b) => (b.DateLastAccessed || 0) - (a.DateLastAccessed || 0));
+ credentials.Servers = servers;
+ credentialProvider.credentials(credentials);
+
+ return servers;
+ });
+ };
+
+ function findServers() {
+ return new Promise((resolve) => {
+ const onFinish = function (foundServers) {
+ const servers = foundServers.map((foundServer) => {
+ const info = {
+ Id: foundServer.Id,
+ LocalAddress: convertEndpointAddressToManualAddress(foundServer) || foundServer.Address,
+ Name: foundServer.Name
+ };
+ info.LastConnectionMode = info.ManualAddress ? ConnectionMode.Manual : ConnectionMode.Local;
+ return info;
+ });
+ resolve(servers);
+ };
+
+ if (window && window.NativeShell && typeof window.NativeShell.findServers === 'function') {
+ window.NativeShell.findServers(1e3).then(onFinish, function () {
+ onFinish([]);
+ });
+ } else {
+ resolve([]);
+ }
+ });
+ }
+
+ function convertEndpointAddressToManualAddress(info) {
+ if (info.Address && info.EndpointAddress) {
+ let address = info.EndpointAddress.split(':')[0];
+
+ // Determine the port, if any
+ const parts = info.Address.split(':');
+ if (parts.length > 1) {
+ const portString = parts[parts.length - 1];
+
+ if (!isNaN(parseInt(portString, 10))) {
+ address += `:${portString}`;
+ }
+ }
+
+ return normalizeAddress(address);
+ }
+
+ return null;
+ }
+
+ self.connectToServers = (servers, options) => {
+ console.log(`Begin connectToServers, with ${servers.length} servers`);
+
+ const firstServer = servers.length ? servers[0] : null;
+ // See if we have any saved credentials and can auto sign in
+ if (firstServer) {
+ return self.connectToServer(firstServer, options).then((result) => {
+ if (result.State === ConnectionState.Unavailable) {
+ result.State = ConnectionState.ServerSelection;
+ }
+
+ console.log('resolving connectToServers with result.State: ' + result.State);
+ return result;
+ });
+ }
+
+ return Promise.resolve({
+ Servers: servers,
+ State: ConnectionState.ServerSelection
+ });
+ };
+
+ function getTryConnectPromise(url, connectionMode, state, resolve, reject) {
+ console.log('getTryConnectPromise ' + url);
+
+ ajax({
+ url: `${url}/System/Info/Public`,
+ timeout: DEFAULT_CONNECTION_TIMEOUT,
+ type: 'GET',
+ dataType: 'json'
+ }).then(
+ (result) => {
+ if (!state.resolved) {
+ state.resolved = true;
+
+ console.log('Reconnect succeeded to ' + url);
+ resolve({
+ url: url,
+ connectionMode: connectionMode,
+ data: result
+ });
+ }
+ },
+ () => {
+ console.log('Reconnect failed to ' + url);
+
+ if (!state.resolved) {
+ state.rejects++;
+ if (state.rejects >= state.numAddresses) {
+ reject();
+ }
+ }
+ }
+ );
+ }
+
+ function tryReconnect(serverInfo) {
+ const addresses = [];
+ const addressesStrings = [];
+
+ // the timeouts are a small hack to try and ensure the remote address doesn't resolve first
+
+ // manualAddressOnly is used for the local web app that always connects to a fixed address
+ if (
+ !serverInfo.manualAddressOnly
+ && serverInfo.LocalAddress
+ && addressesStrings.indexOf(serverInfo.LocalAddress) === -1
+ ) {
+ addresses.push({
+ url: serverInfo.LocalAddress,
+ mode: ConnectionMode.Local,
+ timeout: 0
+ });
+ addressesStrings.push(addresses[addresses.length - 1].url);
+ }
+ if (serverInfo.ManualAddress && addressesStrings.indexOf(serverInfo.ManualAddress) === -1) {
+ addresses.push({
+ url: serverInfo.ManualAddress,
+ mode: ConnectionMode.Manual,
+ timeout: 100
+ });
+ addressesStrings.push(addresses[addresses.length - 1].url);
+ }
+ if (
+ !serverInfo.manualAddressOnly
+ && serverInfo.RemoteAddress
+ && addressesStrings.indexOf(serverInfo.RemoteAddress) === -1
+ ) {
+ addresses.push({
+ url: serverInfo.RemoteAddress,
+ mode: ConnectionMode.Remote,
+ timeout: 200
+ });
+ addressesStrings.push(addresses[addresses.length - 1].url);
+ }
+
+ console.log('tryReconnect: ' + addressesStrings.join('|'));
+
+ return new Promise((resolve, reject) => {
+ const state = {};
+ state.numAddresses = addresses.length;
+ state.rejects = 0;
+
+ addresses.forEach((url) => {
+ setTimeout(() => {
+ if (!state.resolved) {
+ getTryConnectPromise(url.url, url.mode, state, resolve, reject);
+ }
+ }, url.timeout);
+ });
+ });
+ }
+
+ self.connectToServer = (server, options) => {
+ console.log('begin connectToServer');
+
+ return new Promise((resolve) => {
+ options = options || {};
+
+ tryReconnect(server).then(
+ (result) => {
+ const serverUrl = result.url;
+ const connectionMode = result.connectionMode;
+ result = result.data;
+
+ if (compareVersions(self.minServerVersion(), result.Version) === 1) {
+ console.log('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'
+ );
+ resolve({
+ State: ConnectionState.Unavailable
+ });
+ } else {
+ onSuccessfulConnection(server, result, connectionMode, serverUrl, true, resolve, options);
+ }
+ },
+ () => {
+ resolve({
+ State: ConnectionState.Unavailable
+ });
+ }
+ );
+ });
+ };
+
+ function onSuccessfulConnection(server, systemInfo, connectionMode, serverUrl, verifyLocalAuthentication, resolve, options = {}) {
+ const credentials = credentialProvider.credentials();
+
+ if (options.enableAutoLogin === false) {
+ server.UserId = null;
+ server.AccessToken = null;
+ } else if (server.AccessToken && verifyLocalAuthentication) {
+ void validateAuthentication(server, serverUrl).then(function () {
+ onSuccessfulConnection(server, systemInfo, connectionMode, serverUrl, false, resolve, options);
+ });
+ return;
+ }
+
+ updateServerInfo(server, systemInfo);
+
+ server.LastConnectionMode = connectionMode;
+
+ if (options.updateDateLastAccessed !== false) {
+ server.DateLastAccessed = new Date().getTime();
+ }
+ credentialProvider.addOrUpdateServer(credentials.Servers, server);
+ credentialProvider.credentials(credentials);
+
+ const result = {
+ Servers: []
+ };
+
+ result.ApiClient = self._getOrAddApiClient(server, serverUrl);
+
+ result.ApiClient.setSystemInfo(systemInfo);
+ result.SystemInfo = systemInfo;
+
+ result.State = server.AccessToken && options.enableAutoLogin !== false ? ConnectionState.SignedIn : ConnectionState.ServerSignIn;
+
+ result.Servers.push(server);
+
+ // set this now before updating server info, otherwise it won't be set in time
+ result.ApiClient.enableAutomaticBitrateDetection = options.enableAutomaticBitrateDetection;
+
+ result.ApiClient.updateServerInfo(server, serverUrl);
+ result.ApiClient.setAuthenticationInfo(server.AccessToken, server.UserId);
+
+ const resolveActions = function () {
+ resolve(result);
+
+ events.trigger(self, 'connected', [result]);
+ };
+
+ if (result.State === ConnectionState.SignedIn) {
+ afterConnected(result.ApiClient, options);
+
+ result.ApiClient.getCurrentUser().then((user) => {
+ onLocalUserSignIn(server, serverUrl, user).then(resolveActions, resolveActions);
+ }, resolveActions);
+ } else {
+ resolveActions();
+ }
+ }
+
+ function tryConnectToAddress(address, options) {
+ const server = {
+ ManualAddress: address,
+ LastConnectionMode: ConnectionMode.Manual
+ };
+
+ return self.connectToServer(server, options).then((result) => {
+ // connectToServer never rejects, but resolves with State=ConnectionState.Unavailable
+ if (result.State === ConnectionState.Unavailable) {
+ return Promise.reject();
+ }
+ return result;
+ });
+ }
+
+ self.connectToAddress = function (address, options) {
+ if (!address) {
+ return Promise.reject();
+ }
+
+ address = normalizeAddress(address);
+
+ const urls = [];
+
+ if (/^[^:]+:\/\//.test(address)) {
+ // Protocol specified - connect as is
+ urls.push(address);
+ } else {
+ urls.push(`https://${address}`);
+ urls.push(`http://${address}`);
+ }
+
+ let i = 0;
+
+ function onFail() {
+ console.log(`connectToAddress ${urls[i]} failed`);
+
+ if (++i < urls.length) {
+ return tryConnectToAddress(urls[i], options).catch(onFail);
+ }
+
+ return Promise.resolve({
+ State: ConnectionState.Unavailable
+ });
+ }
+
+ return tryConnectToAddress(urls[i], options).catch(onFail);
+ };
+
+ self.deleteServer = (serverId) => {
+ if (!serverId) {
+ throw new Error('null serverId');
+ }
+
+ let server = credentialProvider.credentials().Servers.filter((s) => s.Id === serverId);
+ server = server.length ? server[0] : null;
+
+ return new Promise((resolve) => {
+ function onDone() {
+ const credentials = credentialProvider.credentials();
+
+ credentials.Servers = credentials.Servers.filter((s) => s.Id !== serverId);
+
+ credentialProvider.credentials(credentials);
+ resolve();
+ }
+
+ if (!server.ConnectServerId) {
+ onDone();
+ }
+ });
+ };
+ }
+
+ connect(options) {
+ console.log('Begin connect');
+
+ return this.getAvailableServers().then((servers) => {
+ return this.connectToServers(servers, options);
+ });
+ }
+
+ handleMessageReceived(msg) {
+ const serverId = msg.ServerId;
+ if (serverId) {
+ const apiClient = this.getApiClient(serverId);
+ if (apiClient) {
+ if (typeof msg.Data === 'string') {
+ try {
+ msg.Data = JSON.parse(msg.Data);
+ } catch (err) {
+ console.log('unable to parse json content: ' + err);
+ }
+ }
+
+ apiClient.handleMessageReceived(msg);
+ }
+ }
+ }
+
+ getApiClients() {
+ const servers = this.getSavedServers();
+
+ for (let i = 0, length = servers.length; i < length; i++) {
+ const server = servers[i];
+ if (server.Id) {
+ this._getOrAddApiClient(server, getServerAddress(server, server.LastConnectionMode));
+ }
+ }
+
+ return this._apiClients;
+ }
+
+ /**
+ * Gets the ApiClient for a given BaseItem or ServerId.
+ * @param {import('@jellyfin/sdk/lib/generated-client').BaseItemDto | string | undefined} item
+ * @returns {ApiClient}
+ */
+ getApiClient(item) {
+ if (!item) {
+ throw new Error('item or serverId cannot be null');
+ }
+
+ // Accept string + object
+ if (item.ServerId) {
+ item = item.ServerId;
+ }
+
+ return this._apiClients.filter((a) => {
+ const serverInfo = a.serverInfo();
+
+ // We have to keep this hack in here because of the addApiClient method
+ return !serverInfo || serverInfo.Id === item;
+ })[0];
+ }
+
+ minServerVersion(val) {
+ if (val) {
+ this._minServerVersion = val;
+ }
+
+ return this._minServerVersion;
+ }
+}
diff --git a/src/lib/jellyfin-apiclient/connectionMode.ts b/src/lib/jellyfin-apiclient/connectionMode.ts
new file mode 100644
index 000000000..d915ed5d4
--- /dev/null
+++ b/src/lib/jellyfin-apiclient/connectionMode.ts
@@ -0,0 +1,6 @@
+/** The mode used to connect to a server. */
+export const enum ConnectionMode {
+ Local = 0,
+ Remote = 1,
+ Manual = 2
+}
diff --git a/src/utils/jellyfin-apiclient/ConnectionState.ts b/src/lib/jellyfin-apiclient/connectionState.ts
similarity index 88%
rename from src/utils/jellyfin-apiclient/ConnectionState.ts
rename to src/lib/jellyfin-apiclient/connectionState.ts
index 134a7ffce..03ff95dc5 100644
--- a/src/utils/jellyfin-apiclient/ConnectionState.ts
+++ b/src/lib/jellyfin-apiclient/connectionState.ts
@@ -1,7 +1,7 @@
/**
* Server state values for a connected server used by jellyfin-apiclient.
*/
-export enum ConnectionState {
+export const enum ConnectionState {
SignedIn = 'SignedIn',
ServerSignIn = 'ServerSignIn',
ServerSelection = 'ServerSelection',
diff --git a/src/lib/jellyfin-apiclient/index.ts b/src/lib/jellyfin-apiclient/index.ts
new file mode 100644
index 000000000..ce919d85f
--- /dev/null
+++ b/src/lib/jellyfin-apiclient/index.ts
@@ -0,0 +1,3 @@
+export { ConnectionMode } from './connectionMode';
+export { ConnectionState } from './connectionState';
+export { default as ServerConnections } from './ServerConnections';
diff --git a/src/lib/jellyfin-apiclient/utils/compareVersions.ts b/src/lib/jellyfin-apiclient/utils/compareVersions.ts
new file mode 100644
index 000000000..82b8ee00f
--- /dev/null
+++ b/src/lib/jellyfin-apiclient/utils/compareVersions.ts
@@ -0,0 +1,22 @@
+export function compareVersions(a: string = '', b: string = '') {
+ // -1 a is smaller
+ // 1 a is larger
+ // 0 equal
+ const aParts = a.split('.');
+ const bParts = b.split('.');
+
+ for (let i = 0, length = Math.max(aParts.length, bParts.length); i < length; i++) {
+ const aVal = parseInt(aParts[i] || '0', 10);
+ const bVal = parseInt(bParts[i] || '0', 10);
+
+ if (aVal < bVal) {
+ return -1;
+ }
+
+ if (aVal > bVal) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
diff --git a/src/plugins/backdropScreensaver/plugin.js b/src/plugins/backdropScreensaver/plugin.js
index 1058be5be..fc22fbb4b 100644
--- a/src/plugins/backdropScreensaver/plugin.js
+++ b/src/plugins/backdropScreensaver/plugin.js
@@ -1,7 +1,7 @@
-import ServerConnections from '../../components/ServerConnections';
-import { PluginType } from '../../types/plugin.ts';
-import * as userSettings from '../../scripts/settings/userSettings';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
+import * as userSettings from 'scripts/settings/userSettings';
+import { PluginType } from 'types/plugin.ts';
class BackdropScreensaver {
constructor() {
diff --git a/src/plugins/bookPlayer/plugin.js b/src/plugins/bookPlayer/plugin.js
index 733519cb3..1e0ad2a00 100644
--- a/src/plugins/bookPlayer/plugin.js
+++ b/src/plugins/bookPlayer/plugin.js
@@ -3,10 +3,10 @@ import 'material-design-icons-iconfont';
import loading from '../../components/loading/loading';
import keyboardnavigation from '../../scripts/keyboardNavigation';
import dialogHelper from '../../components/dialogHelper/dialogHelper';
-import ServerConnections from '../../components/ServerConnections';
import Screenfull from 'screenfull';
import TableOfContents from './tableOfContents';
import { translateHtml } from '../../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import browser from 'scripts/browser';
import * as userSettings from '../../scripts/settings/userSettings';
import TouchHelper from 'scripts/touchHelper';
diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js
index 868f02691..a7c149ac4 100644
--- a/src/plugins/chromecastPlayer/plugin.js
+++ b/src/plugins/chromecastPlayer/plugin.js
@@ -3,8 +3,8 @@ import * as userSettings from '../../scripts/settings/userSettings';
import { playbackManager } from '../../components/playback/playbackmanager';
import globalize from '../../lib/globalize';
import CastSenderApi from './castSenderApi';
-import ServerConnections from '../../components/ServerConnections';
import alert from '../../components/alert';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts';
import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts';
diff --git a/src/plugins/comicsPlayer/plugin.js b/src/plugins/comicsPlayer/plugin.js
index 0e8f599a1..24d12f0c0 100644
--- a/src/plugins/comicsPlayer/plugin.js
+++ b/src/plugins/comicsPlayer/plugin.js
@@ -3,7 +3,7 @@ import loading from '../../components/loading/loading';
import dialogHelper from '../../components/dialogHelper/dialogHelper';
import keyboardnavigation from '../../scripts/keyboardNavigation';
import { appRouter } from '../../components/router/appRouter';
-import ServerConnections from '../../components/ServerConnections';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import * as userSettings from '../../scripts/settings/userSettings';
import { PluginType } from '../../types/plugin.ts';
diff --git a/src/plugins/htmlAudioPlayer/plugin.js b/src/plugins/htmlAudioPlayer/plugin.js
index 029cdeca8..b3900aad3 100644
--- a/src/plugins/htmlAudioPlayer/plugin.js
+++ b/src/plugins/htmlAudioPlayer/plugin.js
@@ -69,7 +69,7 @@ function enableHlsPlayer(url, item, mediaSource, mediaType) {
// issue head request to get content type
return new Promise(function (resolve, reject) {
- import('../../components/fetchhelper').then((fetchHelper) => {
+ import('../../utils/fetch').then((fetchHelper) => {
fetchHelper.ajax({
url: url,
type: 'HEAD'
diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js
index 4b6e0c9a7..fcaff8f32 100644
--- a/src/plugins/htmlVideoPlayer/plugin.js
+++ b/src/plugins/htmlVideoPlayer/plugin.js
@@ -29,7 +29,7 @@ import {
import itemHelper from '../../components/itemHelper';
import Screenfull from 'screenfull';
import globalize from '../../lib/globalize';
-import ServerConnections from '../../components/ServerConnections';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import profileBuilder, { canPlaySecondaryAudio } from '../../scripts/browserDeviceProfile';
import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings';
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../components/backdrop/backdrop';
diff --git a/src/plugins/pdfPlayer/plugin.js b/src/plugins/pdfPlayer/plugin.js
index c42869865..f77c01364 100644
--- a/src/plugins/pdfPlayer/plugin.js
+++ b/src/plugins/pdfPlayer/plugin.js
@@ -1,9 +1,9 @@
-import ServerConnections from '../../components/ServerConnections';
import loading from '../../components/loading/loading';
import keyboardnavigation from '../../scripts/keyboardNavigation';
import dialogHelper from '../../components/dialogHelper/dialogHelper';
import dom from '../../scripts/dom';
import { appRouter } from '../../components/router/appRouter';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts';
diff --git a/src/plugins/photoPlayer/plugin.js b/src/plugins/photoPlayer/plugin.js
index 44aa79b82..7fcec8294 100644
--- a/src/plugins/photoPlayer/plugin.js
+++ b/src/plugins/photoPlayer/plugin.js
@@ -1,5 +1,5 @@
-import ServerConnections from '../../components/ServerConnections';
-import { PluginType } from '../../types/plugin.ts';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
+import { PluginType } from 'types/plugin.ts';
export default class PhotoPlayer {
constructor() {
diff --git a/src/plugins/playAccessValidation/plugin.js b/src/plugins/playAccessValidation/plugin.js
index 0a3ab4a85..cae96f8c3 100644
--- a/src/plugins/playAccessValidation/plugin.js
+++ b/src/plugins/playAccessValidation/plugin.js
@@ -1,7 +1,7 @@
-import globalize from '../../lib/globalize';
-import ServerConnections from '../../components/ServerConnections';
-import alert from '../../components/alert';
-import { PluginType } from '../../types/plugin.ts';
+import alert from 'components/alert';
+import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
+import { PluginType } from 'types/plugin.ts';
function showErrorMessage() {
return alert(globalize.translate('MessagePlayAccessRestricted'));
diff --git a/src/plugins/sessionPlayer/plugin.js b/src/plugins/sessionPlayer/plugin.js
index 32c24ce7e..40681ae81 100644
--- a/src/plugins/sessionPlayer/plugin.js
+++ b/src/plugins/sessionPlayer/plugin.js
@@ -1,6 +1,6 @@
import { playbackManager } from '../../components/playback/playbackmanager';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import serverNotifications from '../../scripts/serverNotifications';
-import ServerConnections from '../../components/ServerConnections';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts';
import isEqual from 'lodash-es/isEqual';
diff --git a/src/plugins/syncPlay/plugin.ts b/src/plugins/syncPlay/plugin.ts
index 33d99aa75..41f5715ab 100644
--- a/src/plugins/syncPlay/plugin.ts
+++ b/src/plugins/syncPlay/plugin.ts
@@ -1,5 +1,5 @@
import { playbackManager } from '../../components/playback/playbackmanager';
-import ServerConnections from '../../components/ServerConnections';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import Events from '../../utils/events';
import SyncPlay from './core';
import SyncPlayNoActivePlayer from './ui/players/NoActivePlayer';
diff --git a/src/plugins/syncPlay/ui/groupSelectionMenu.js b/src/plugins/syncPlay/ui/groupSelectionMenu.js
index ec224c64b..1b6a3d963 100644
--- a/src/plugins/syncPlay/ui/groupSelectionMenu.js
+++ b/src/plugins/syncPlay/ui/groupSelectionMenu.js
@@ -5,7 +5,7 @@ import actionsheet from '../../../components/actionSheet/actionSheet';
import globalize from '../../../lib/globalize';
import playbackPermissionManager from './playbackPermissionManager';
import { pluginManager } from '../../../components/pluginManager';
-import ServerConnections from '../../../components/ServerConnections';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import { PluginType } from '../../../types/plugin.ts';
import Events from '../../../utils/events.ts';
diff --git a/src/scripts/autoBackdrops.js b/src/scripts/autoBackdrops.js
index 0ba53e242..8c0283740 100644
--- a/src/scripts/autoBackdrops.js
+++ b/src/scripts/autoBackdrops.js
@@ -4,8 +4,8 @@ import libraryMenu from './libraryMenu';
import { pageClassOn } from '../utils/dashboard';
import { queryClient } from 'utils/query/queryClient';
import { getBrandingOptionsQuery } from 'apps/dashboard/features/branding/api/useBrandingOptions';
-import ServerConnections from 'components/ServerConnections';
import { SPLASHSCREEN_URL } from 'constants/branding';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
const cache = {};
diff --git a/src/scripts/autoThemes.js b/src/scripts/autoThemes.js
index b7c2fdf0d..3a842261a 100644
--- a/src/scripts/autoThemes.js
+++ b/src/scripts/autoThemes.js
@@ -1,8 +1,8 @@
import * as userSettings from './settings/userSettings';
import skinManager from './themeManager';
-import ServerConnections from '../components/ServerConnections';
-import { pageClassOn } from '../utils/dashboard';
-import Events from '../utils/events.ts';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
+import { pageClassOn } from 'utils/dashboard';
+import Events from 'utils/events.ts';
// Set the default theme when loading
skinManager.setTheme(userSettings.theme())
diff --git a/src/scripts/autocast.js b/src/scripts/autocast.js
index 65d7b19de..ac86286e2 100644
--- a/src/scripts/autocast.js
+++ b/src/scripts/autocast.js
@@ -1,6 +1,6 @@
-import ServerConnections from 'components/ServerConnections';
-import { playbackManager } from '../components/playback/playbackmanager';
-import Events from '../utils/events.ts';
+import { playbackManager } from 'components/playback/playbackmanager';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
+import Events from 'utils/events.ts';
export function enable(enabled) {
console.debug('[autocast] %s cast player', enabled ? 'enabling' : 'disabling');
diff --git a/src/scripts/deleteHelper.js b/src/scripts/deleteHelper.js
index e482737c5..3f9fc8979 100644
--- a/src/scripts/deleteHelper.js
+++ b/src/scripts/deleteHelper.js
@@ -1,11 +1,12 @@
-import globalize from 'lib/globalize';
-import alert from '../components/alert';
-import confirm from '../components/confirm/confirm';
-import { appRouter } from '../components/router/appRouter';
-import ServerConnections from '../components/ServerConnections';
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
+import alert from 'components/alert';
+import confirm from 'components/confirm/confirm';
+import { appRouter } from 'components/router/appRouter';
+import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
+
function alertText(options) {
return alert(options);
}
diff --git a/src/scripts/itemsByName.js b/src/scripts/itemsByName.js
index 936c7b877..e60d468d0 100644
--- a/src/scripts/itemsByName.js
+++ b/src/scripts/itemsByName.js
@@ -1,10 +1,11 @@
-import listView from '../components/listview/listview';
-import cardBuilder from '../components/cardbuilder/cardBuilder';
-import imageLoader from '../components/images/imageLoader';
+import listView from 'components/listview/listview';
+import cardBuilder from 'components/cardbuilder/cardBuilder';
+import imageLoader from 'components/images/imageLoader';
import globalize from 'lib/globalize';
-import '../elements/emby-itemscontainer/emby-itemscontainer';
-import '../elements/emby-button/emby-button';
-import ServerConnections from '../components/ServerConnections';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
+
+import 'elements/emby-itemscontainer/emby-itemscontainer';
+import 'elements/emby-button/emby-button';
function renderItems(page, item) {
const sections = [];
diff --git a/src/scripts/libraryMenu.js b/src/scripts/libraryMenu.js
index fe93f8bd7..c4c6c6d4d 100644
--- a/src/scripts/libraryMenu.js
+++ b/src/scripts/libraryMenu.js
@@ -20,10 +20,10 @@ import { pluginManager } from '../components/pluginManager';
import groupSelectionMenu from '../plugins/syncPlay/ui/groupSelectionMenu';
import browser from './browser';
import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import imageHelper from '../utils/image';
import { getMenuLinks } from '../scripts/settings/webSettings';
import Dashboard, { pageClassOn } from '../utils/dashboard';
-import ServerConnections from '../components/ServerConnections';
import { PluginType } from '../types/plugin.ts';
import Events from '../utils/events.ts';
import { getParameterByName } from '../utils/url.ts';
diff --git a/src/scripts/playlistViewer.js b/src/scripts/playlistViewer.js
index 524086312..2bb15930d 100644
--- a/src/scripts/playlistViewer.js
+++ b/src/scripts/playlistViewer.js
@@ -1,7 +1,7 @@
import { getPlaylistsApi } from '@jellyfin/sdk/lib/utils/api/playlists-api';
-import ServerConnections from 'components/ServerConnections';
import listView from 'components/listview/listview';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import { toApi } from 'utils/jellyfin-apiclient/compat';
function getFetchPlaylistItemsFn(apiClient, itemId) {
diff --git a/src/scripts/screensavermanager.js b/src/scripts/screensavermanager.js
index fccc2751a..d0b5ef1b9 100644
--- a/src/scripts/screensavermanager.js
+++ b/src/scripts/screensavermanager.js
@@ -1,10 +1,11 @@
-import { playbackManager } from '../components/playback/playbackmanager';
-import { pluginManager } from '../components/pluginManager';
+import { playbackManager } from 'components/playback/playbackmanager';
+import { pluginManager } from 'components/pluginManager';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
+import { PluginType } from 'types/plugin.ts';
+import Events from 'utils/events.ts';
+
import inputManager from './inputManager';
import * as userSettings from './settings/userSettings';
-import ServerConnections from '../components/ServerConnections';
-import { PluginType } from '../types/plugin.ts';
-import Events from '../utils/events.ts';
import './screensavermanager.scss';
diff --git a/src/scripts/serverNotifications.js b/src/scripts/serverNotifications.js
index 00088ef29..0f9353dcf 100644
--- a/src/scripts/serverNotifications.js
+++ b/src/scripts/serverNotifications.js
@@ -1,13 +1,13 @@
-import { playbackManager } from '../components/playback/playbackmanager';
-import { pluginManager } from '../components/pluginManager';
-import inputManager from '../scripts/inputManager';
-import focusManager from '../components/focusManager';
-import { appRouter } from '../components/router/appRouter';
-import ServerConnections from '../components/ServerConnections';
-import toast from '../components/toast/toast';
-import alert from '../components/alert';
-import Events from '../utils/events.ts';
-import { PluginType } from '../types/plugin.ts';
+import alert from 'components/alert';
+import focusManager from 'components/focusManager';
+import { playbackManager } from 'components/playback/playbackmanager';
+import { pluginManager } from 'components/pluginManager';
+import { appRouter } from 'components/router/appRouter';
+import toast from 'components/toast/toast';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
+import inputManager from 'scripts/inputManager';
+import Events from 'utils/events.ts';
+import { PluginType } from 'types/plugin.ts';
const serverNotifications = {};
diff --git a/src/scripts/taskbutton.js b/src/scripts/taskbutton.js
index 0bc9c0ab8..8a4979b60 100644
--- a/src/scripts/taskbutton.js
+++ b/src/scripts/taskbutton.js
@@ -1,10 +1,10 @@
-import serverNotifications from '../scripts/serverNotifications';
-import globalize from '../lib/globalize';
-import ServerConnections from '../components/ServerConnections';
-import Events from '../utils/events.ts';
+import globalize from 'lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
+import serverNotifications from 'scripts/serverNotifications';
+import Events from 'utils/events.ts';
-import '../elements/emby-button/emby-button';
+import 'elements/emby-button/emby-button';
function taskbutton(options) {
function pollTasks() {
diff --git a/src/utils/dashboard.js b/src/utils/dashboard.js
index 9b468730d..8c5d849eb 100644
--- a/src/utils/dashboard.js
+++ b/src/utils/dashboard.js
@@ -1,10 +1,10 @@
-import ServerConnections from '../components/ServerConnections';
import toast from '../components/toast/toast';
import loading from '../components/loading/loading';
import { appRouter } from '../components/router/appRouter';
import baseAlert from '../components/alert';
import baseConfirm from '../components/confirm/confirm';
import globalize from '../lib/globalize';
+import { ServerConnections } from 'lib/jellyfin-apiclient';
import * as webSettings from '../scripts/settings/webSettings';
import datetime from '../scripts/datetime';
import { setBackdropTransparency } from '../components/backdrop/backdrop';
diff --git a/src/components/fetchhelper.js b/src/utils/fetch.js
similarity index 100%
rename from src/components/fetchhelper.js
rename to src/utils/fetch.js
diff --git a/src/utils/string.ts b/src/utils/string.ts
index b3a858e6f..1f7c5aec5 100644
--- a/src/utils/string.ts
+++ b/src/utils/string.ts
@@ -1,3 +1,11 @@
+/**
+ * Checks if two string are equal ignoring case.
+ * @param str1 The first string.
+ * @param str2 The second string.
+ * @returns True if the strings are equal ignoring case.
+ */
+export const equalsIgnoreCase = (str1 = '', str2 = '') => str1.toLowerCase() === str2.toLowerCase();
+
/**
* Checks if a string is empty or contains only whitespace.
* @param {string} value The string to test.