2022-04-13 16:38:19 -04:00
|
|
|
import { Action, createHashHistory } from 'history';
|
|
|
|
|
2023-05-01 10:04:13 -04:00
|
|
|
import { appHost } from '../apphost';
|
|
|
|
import { clearBackdrop, setBackdropTransparency } from '../backdrop/backdrop';
|
|
|
|
import globalize from '../../scripts/globalize';
|
|
|
|
import Events from '../../utils/events.ts';
|
|
|
|
import itemHelper from '../itemHelper';
|
|
|
|
import loading from '../loading/loading';
|
|
|
|
import viewManager from '../viewManager/viewManager';
|
|
|
|
import ServerConnections from '../ServerConnections';
|
|
|
|
import alert from '../alert';
|
|
|
|
import { ConnectionState } from '../../utils/jellyfin-apiclient/ConnectionState.ts';
|
2020-07-30 20:34:21 +02:00
|
|
|
|
2022-04-27 15:14:35 -04:00
|
|
|
export const history = createHashHistory();
|
2022-04-13 16:38:19 -04:00
|
|
|
|
2022-04-20 17:40:36 -04:00
|
|
|
/**
|
|
|
|
* Page types of "no return" (when "Go back" should behave differently, probably quitting the application).
|
|
|
|
*/
|
|
|
|
const START_PAGE_TYPES = ['home', 'login', 'selectserver'];
|
2023-05-10 09:50:30 -04:00
|
|
|
const START_PAGE_PATHS = ['/home.html', '/login.html', '/selectserver.html'];
|
2022-04-20 17:40:36 -04:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
class AppRouter {
|
2022-04-13 16:38:19 -04:00
|
|
|
allRoutes = new Map();
|
2022-04-27 15:14:35 -04:00
|
|
|
currentRouteInfo = { route: {} };
|
2020-07-30 20:34:21 +02:00
|
|
|
currentViewLoadRequest;
|
|
|
|
firstConnectionResult;
|
|
|
|
forcedLogoutMsg;
|
|
|
|
msgTimeout;
|
2021-10-03 21:51:06 +03:00
|
|
|
promiseShow;
|
2020-07-30 20:34:21 +02:00
|
|
|
resolveOnNextShow;
|
|
|
|
|
|
|
|
constructor() {
|
2021-09-21 22:26:09 +03:00
|
|
|
document.addEventListener('viewshow', () => this.onViewShow());
|
2020-07-30 20:34:21 +02:00
|
|
|
|
2022-04-13 16:38:19 -04:00
|
|
|
// TODO: Can this baseRoute logic be simplified?
|
2022-04-20 17:40:36 -04:00
|
|
|
this.baseRoute = window.location.href.split('?')[0].replace(this.#getRequestFile(), '');
|
2020-07-30 20:34:21 +02:00
|
|
|
// support hashbang
|
|
|
|
this.baseRoute = this.baseRoute.split('#')[0];
|
|
|
|
if (this.baseRoute.endsWith('/') && !this.baseRoute.endsWith('://')) {
|
|
|
|
this.baseRoute = this.baseRoute.substring(0, this.baseRoute.length - 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-13 16:38:19 -04:00
|
|
|
addRoute(path, route) {
|
|
|
|
this.allRoutes.set(path, {
|
|
|
|
route,
|
|
|
|
handler: this.#getHandler(route)
|
|
|
|
});
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
|
|
|
|
2022-04-20 17:40:36 -04:00
|
|
|
#beginConnectionWizard() {
|
2022-04-12 16:58:56 -04:00
|
|
|
clearBackdrop();
|
2020-07-31 21:35:01 +01:00
|
|
|
loading.show();
|
2022-04-21 13:43:12 -04:00
|
|
|
ServerConnections.connect().then(result => {
|
2022-04-20 17:40:36 -04:00
|
|
|
this.#handleConnectionResult(result);
|
2019-01-10 15:39:37 +03:00
|
|
|
});
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2021-10-03 21:51:06 +03:00
|
|
|
ready() {
|
|
|
|
return this.promiseShow || Promise.resolve();
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
|
|
|
|
2021-10-03 21:51:06 +03:00
|
|
|
async back() {
|
|
|
|
if (this.promiseShow) await this.promiseShow;
|
|
|
|
|
|
|
|
this.promiseShow = new Promise((resolve) => {
|
2022-11-03 21:29:21 +03:00
|
|
|
const unlisten = history.listen(() => {
|
|
|
|
unlisten();
|
|
|
|
this.promiseShow = null;
|
|
|
|
resolve();
|
|
|
|
});
|
2022-04-13 16:38:19 -04:00
|
|
|
history.back();
|
2021-10-03 21:51:06 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
return this.promiseShow;
|
|
|
|
}
|
|
|
|
|
|
|
|
async show(path, options) {
|
|
|
|
if (this.promiseShow) await this.promiseShow;
|
|
|
|
|
2022-06-09 14:54:39 -04:00
|
|
|
// ensure the path does not start with '#' since the router adds this
|
|
|
|
if (path.startsWith('#')) {
|
|
|
|
path = path.substring(1);
|
|
|
|
}
|
|
|
|
// Support legacy '#!' routes since people may have old bookmarks, etc.
|
|
|
|
if (path.startsWith('!')) {
|
|
|
|
path = path.substring(1);
|
2020-12-03 00:19:31 -05:00
|
|
|
}
|
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (path.indexOf('/') !== 0 && path.indexOf('://') === -1) {
|
|
|
|
path = '/' + path;
|
|
|
|
}
|
|
|
|
|
|
|
|
path = path.replace(this.baseUrl(), '');
|
|
|
|
|
2022-10-03 14:22:02 -04:00
|
|
|
// can't use this with home right now due to the back menu
|
|
|
|
if (this.currentRouteInfo?.path === path && this.currentRouteInfo.route.type !== 'home') {
|
|
|
|
loading.hide();
|
|
|
|
return Promise.resolve();
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
|
|
|
|
2021-10-03 21:51:06 +03:00
|
|
|
this.promiseShow = new Promise((resolve) => {
|
2020-07-30 20:34:21 +02:00
|
|
|
this.resolveOnNextShow = resolve;
|
2021-10-03 21:51:06 +03:00
|
|
|
// Schedule a call to return the promise
|
2022-04-13 16:38:19 -04:00
|
|
|
setTimeout(() => history.push(path, options), 0);
|
2020-07-30 20:34:21 +02:00
|
|
|
});
|
2021-10-03 21:51:06 +03:00
|
|
|
|
|
|
|
return this.promiseShow;
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
|
|
|
|
2022-04-13 16:38:19 -04:00
|
|
|
#goToRoute({ location, action }) {
|
|
|
|
// Strip the leading "!" if present
|
|
|
|
const normalizedPath = location.pathname.replace(/^!/, '');
|
|
|
|
|
2022-04-19 23:33:51 -04:00
|
|
|
const route = this.allRoutes.get(normalizedPath);
|
|
|
|
if (route) {
|
|
|
|
console.debug('[appRouter] "%s" route found', normalizedPath, location, route);
|
|
|
|
route.handler({
|
|
|
|
// Recreate the default context used by page.js: https://github.com/visionmedia/page.js#context
|
|
|
|
path: normalizedPath + location.search,
|
|
|
|
pathname: normalizedPath,
|
|
|
|
querystring: location.search.replace(/^\?/, ''),
|
|
|
|
state: location.state,
|
|
|
|
// Custom context variables
|
|
|
|
isBack: action === Action.Pop
|
|
|
|
});
|
2022-04-13 16:38:19 -04:00
|
|
|
} else {
|
2023-03-01 09:35:21 -05:00
|
|
|
// The route is not registered here, so it should be handled by react-router
|
2022-04-25 12:53:33 -04:00
|
|
|
this.currentRouteInfo = {
|
|
|
|
route: {},
|
|
|
|
path: normalizedPath + location.search
|
|
|
|
};
|
2022-04-13 16:38:19 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
start() {
|
2020-07-30 20:34:21 +02:00
|
|
|
loading.show();
|
|
|
|
|
2022-04-21 13:43:12 -04:00
|
|
|
ServerConnections.getApiClients().forEach(apiClient => {
|
|
|
|
Events.off(apiClient, 'requestfail', this.onRequestFail);
|
|
|
|
Events.on(apiClient, 'requestfail', this.onRequestFail);
|
|
|
|
});
|
|
|
|
|
|
|
|
Events.on(ServerConnections, 'apiclientcreated', (_e, apiClient) => {
|
|
|
|
Events.off(apiClient, 'requestfail', this.onRequestFail);
|
|
|
|
Events.on(apiClient, 'requestfail', this.onRequestFail);
|
|
|
|
});
|
2020-07-30 20:34:21 +02:00
|
|
|
|
2022-04-14 01:19:27 -04:00
|
|
|
return ServerConnections.connect().then(result => {
|
2020-07-30 20:34:21 +02:00
|
|
|
this.firstConnectionResult = result;
|
2022-04-13 16:38:19 -04:00
|
|
|
|
|
|
|
// Handle the initial route
|
|
|
|
this.#goToRoute({ location: history.location });
|
|
|
|
|
|
|
|
// Handle route changes
|
|
|
|
history.listen(params => {
|
|
|
|
this.#goToRoute(params);
|
2020-07-30 20:34:21 +02:00
|
|
|
});
|
|
|
|
}).catch().then(() => {
|
|
|
|
loading.hide();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
baseUrl() {
|
|
|
|
return this.baseRoute;
|
|
|
|
}
|
|
|
|
|
|
|
|
canGoBack() {
|
2023-05-10 09:50:30 -04:00
|
|
|
const { path, route } = this.currentRouteInfo;
|
2023-07-06 17:09:54 -04:00
|
|
|
const pathOnly = path?.split('?')[0] ?? '';
|
2023-05-10 09:50:30 -04:00
|
|
|
|
|
|
|
if (!route) {
|
2020-07-30 20:34:21 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-07-06 17:09:54 -04:00
|
|
|
if (!document.querySelector('.dialogContainer') && (START_PAGE_TYPES.includes(route.type) || START_PAGE_PATHS.includes(pathOnly))) {
|
2020-07-30 20:34:21 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-08-25 10:12:35 +09:00
|
|
|
return window.history.length > 1;
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
showItem(item, serverId, options) {
|
|
|
|
// TODO: Refactor this so it only gets items, not strings.
|
|
|
|
if (typeof (item) === 'string') {
|
2020-10-17 19:08:56 +01:00
|
|
|
const apiClient = serverId ? ServerConnections.getApiClient(serverId) : ServerConnections.currentApiClient();
|
2020-07-30 20:34:21 +02:00
|
|
|
apiClient.getItem(apiClient.getCurrentUserId(), item).then((itemObject) => {
|
|
|
|
this.showItem(itemObject, options);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
if (arguments.length === 2) {
|
|
|
|
options = arguments[1];
|
|
|
|
}
|
|
|
|
|
|
|
|
const url = this.getRouteUrl(item, options);
|
2022-04-21 13:43:12 -04:00
|
|
|
this.show(url, { item });
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-12 16:22:00 -04:00
|
|
|
/**
|
|
|
|
* Sets the backdrop, background, and document transparency
|
|
|
|
* @deprecated use Dashboard.setBackdropTransparency
|
|
|
|
*/
|
2020-07-30 20:34:21 +02:00
|
|
|
setTransparency(level) {
|
2022-04-12 16:22:00 -04:00
|
|
|
// TODO: Remove this after JMP is updated to not use this function
|
|
|
|
console.warn('Deprecated! Use Dashboard.setBackdropTransparency');
|
|
|
|
setBackdropTransparency(level);
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
|
|
|
|
2022-04-20 17:40:36 -04:00
|
|
|
#handleConnectionResult(result) {
|
2018-10-23 01:05:09 +03:00
|
|
|
switch (result.State) {
|
2022-10-21 15:05:50 -04:00
|
|
|
case ConnectionState.SignedIn:
|
2020-07-31 21:35:01 +01:00
|
|
|
loading.hide();
|
2020-10-18 20:00:39 +01:00
|
|
|
this.goHome();
|
2019-01-10 15:39:37 +03:00
|
|
|
break;
|
2022-10-21 15:05:50 -04:00
|
|
|
case ConnectionState.ServerSignIn:
|
2022-04-22 15:06:17 -04:00
|
|
|
this.showLocalLogin(result.ApiClient.serverId());
|
2018-10-23 01:05:09 +03:00
|
|
|
break;
|
2022-10-21 15:05:50 -04:00
|
|
|
case ConnectionState.ServerSelection:
|
2020-07-30 20:34:21 +02:00
|
|
|
this.showSelectServer();
|
2018-10-23 01:05:09 +03:00
|
|
|
break;
|
2022-10-21 15:05:50 -04:00
|
|
|
case ConnectionState.ServerUpdateNeeded:
|
2020-10-18 15:18:15 +01:00
|
|
|
alert({
|
|
|
|
text: globalize.translate('ServerUpdateNeeded', 'https://github.com/jellyfin/jellyfin'),
|
|
|
|
html: globalize.translate('ServerUpdateNeeded', '<a href="https://github.com/jellyfin/jellyfin">https://github.com/jellyfin/jellyfin</a>')
|
|
|
|
}).then(() => {
|
|
|
|
this.showSelectServer();
|
2019-01-16 18:30:15 +09:00
|
|
|
});
|
2019-01-10 15:39:37 +03:00
|
|
|
break;
|
|
|
|
default:
|
2018-10-23 01:05:09 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-21 13:57:01 -04:00
|
|
|
#loadContentUrl(ctx, _next, route, request) {
|
2020-07-30 20:34:21 +02:00
|
|
|
let url;
|
2019-01-10 15:39:37 +03:00
|
|
|
if (route.contentPath && typeof (route.contentPath) === 'function') {
|
|
|
|
url = route.contentPath(ctx.querystring);
|
|
|
|
} else {
|
|
|
|
url = route.contentPath || route.path;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ctx.querystring && route.enableContentQueryString) {
|
|
|
|
url += '?' + ctx.querystring;
|
|
|
|
}
|
|
|
|
|
2020-11-11 11:58:59 +00:00
|
|
|
let promise;
|
2020-11-08 20:54:44 +00:00
|
|
|
if (route.serverRequest) {
|
|
|
|
const apiClient = ServerConnections.currentApiClient();
|
|
|
|
url = apiClient.getUrl(`/web${url}`);
|
2020-11-11 11:58:59 +00:00
|
|
|
promise = apiClient.get(url);
|
2020-11-08 20:54:44 +00:00
|
|
|
} else {
|
2023-05-01 10:04:13 -04:00
|
|
|
promise = import(/* webpackChunkName: "[request]" */ `../../controllers/${url}`);
|
2020-11-08 20:54:44 +00:00
|
|
|
}
|
2020-11-11 11:58:59 +00:00
|
|
|
|
|
|
|
promise.then((html) => {
|
2022-04-20 17:40:36 -04:00
|
|
|
this.#loadContent(ctx, route, html, request);
|
2019-01-10 15:39:37 +03:00
|
|
|
});
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2022-04-20 17:40:36 -04:00
|
|
|
#handleRoute(ctx, next, route) {
|
|
|
|
this.#authenticate(ctx, route, () => {
|
|
|
|
this.#initRoute(ctx, next, route);
|
2019-01-10 15:39:37 +03:00
|
|
|
});
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2022-04-20 17:40:36 -04:00
|
|
|
#initRoute(ctx, next, route) {
|
2020-07-30 20:34:21 +02:00
|
|
|
const onInitComplete = (controllerFactory) => {
|
2022-04-20 17:40:36 -04:00
|
|
|
this.#sendRouteToViewManager(ctx, next, route, controllerFactory);
|
2018-10-23 01:05:09 +03:00
|
|
|
};
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2022-06-29 05:48:11 +03:00
|
|
|
if (route.controller) {
|
2023-05-01 10:04:13 -04:00
|
|
|
import(/* webpackChunkName: "[request]" */ '../../controllers/' + route.controller).then(onInitComplete);
|
2019-02-24 20:07:41 +00:00
|
|
|
} else {
|
|
|
|
onInitComplete();
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2022-04-20 17:40:36 -04:00
|
|
|
#cancelCurrentLoadRequest() {
|
2020-07-30 20:34:21 +02:00
|
|
|
const currentRequest = this.currentViewLoadRequest;
|
2019-01-10 15:39:37 +03:00
|
|
|
if (currentRequest) {
|
|
|
|
currentRequest.cancel = true;
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2022-04-20 17:40:36 -04:00
|
|
|
#sendRouteToViewManager(ctx, next, route, controllerFactory) {
|
|
|
|
this.#cancelCurrentLoadRequest();
|
2020-07-30 20:34:21 +02:00
|
|
|
const isBackNav = ctx.isBack;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
const currentRequest = {
|
|
|
|
url: this.baseUrl() + ctx.path,
|
2019-01-10 15:39:37 +03:00
|
|
|
transition: route.transition,
|
|
|
|
isBack: isBackNav,
|
|
|
|
state: ctx.state,
|
|
|
|
type: route.type,
|
|
|
|
fullscreen: route.fullscreen,
|
|
|
|
controllerFactory: controllerFactory,
|
|
|
|
options: {
|
|
|
|
supportsThemeMedia: route.supportsThemeMedia || false,
|
|
|
|
enableMediaControl: route.enableMediaControl !== false
|
|
|
|
},
|
|
|
|
autoFocus: route.autoFocus
|
|
|
|
};
|
2020-07-30 20:34:21 +02:00
|
|
|
this.currentViewLoadRequest = currentRequest;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
const onNewViewNeeded = () => {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (typeof route.path === 'string') {
|
2022-04-20 17:40:36 -04:00
|
|
|
this.#loadContentUrl(ctx, next, route, currentRequest);
|
2019-01-10 15:39:37 +03:00
|
|
|
} else {
|
|
|
|
next();
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
};
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (!isBackNav) {
|
|
|
|
onNewViewNeeded();
|
|
|
|
return;
|
|
|
|
}
|
2020-07-30 20:34:21 +02:00
|
|
|
viewManager.tryRestoreView(currentRequest, () => {
|
|
|
|
this.currentRouteInfo = {
|
2018-10-23 01:05:09 +03:00
|
|
|
route: route,
|
|
|
|
path: ctx.path
|
2019-01-10 15:39:37 +03:00
|
|
|
};
|
2020-07-30 20:34:21 +02:00
|
|
|
}).catch((result) => {
|
2023-07-06 13:39:48 -04:00
|
|
|
if (!result?.cancelled) {
|
2019-01-10 15:39:37 +03:00
|
|
|
onNewViewNeeded();
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
});
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2021-09-21 22:26:09 +03:00
|
|
|
onViewShow() {
|
|
|
|
const resolve = this.resolveOnNextShow;
|
|
|
|
if (resolve) {
|
2021-10-03 21:51:06 +03:00
|
|
|
this.promiseShow = null;
|
2021-09-21 22:26:09 +03:00
|
|
|
this.resolveOnNextShow = null;
|
|
|
|
resolve();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
onForcedLogoutMessageTimeout() {
|
|
|
|
const msg = this.forcedLogoutMsg;
|
|
|
|
this.forcedLogoutMsg = null;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (msg) {
|
2020-10-18 15:18:15 +01:00
|
|
|
alert(msg);
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
showForcedLogoutMessage(msg) {
|
|
|
|
this.forcedLogoutMsg = msg;
|
|
|
|
if (this.msgTimeout) {
|
|
|
|
clearTimeout(this.msgTimeout);
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
this.msgTimeout = setTimeout(this.onForcedLogoutMessageTimeout, 100);
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2022-04-21 13:43:12 -04:00
|
|
|
onRequestFail(_e, data) {
|
2020-07-30 20:34:21 +02:00
|
|
|
const apiClient = this;
|
2021-03-29 20:51:41 +02:00
|
|
|
|
2022-10-03 14:22:02 -04:00
|
|
|
if (data.status === 403 && data.errorCode === 'ParentalControl') {
|
|
|
|
const isCurrentAllowed = appRouter.currentRouteInfo ? (appRouter.currentRouteInfo.route.anonymous || appRouter.currentRouteInfo.route.startup) : true;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2022-10-03 14:22:02 -04:00
|
|
|
// Bounce to the login screen, but not if a password entry fails, obviously
|
|
|
|
if (!isCurrentAllowed) {
|
|
|
|
appRouter.showForcedLogoutMessage(globalize.translate('AccessRestrictedTryAgainLater'));
|
|
|
|
appRouter.showLocalLogin(apiClient.serverId());
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-20 17:40:36 -04:00
|
|
|
#authenticate(ctx, route, callback) {
|
2020-07-30 20:34:21 +02:00
|
|
|
const firstResult = this.firstConnectionResult;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-09-10 23:20:55 +09:00
|
|
|
this.firstConnectionResult = null;
|
2021-03-22 23:34:48 +03:00
|
|
|
if (firstResult) {
|
2022-10-21 15:05:50 -04:00
|
|
|
if (firstResult.State === ConnectionState.ServerSignIn) {
|
2021-03-22 23:34:48 +03:00
|
|
|
const url = firstResult.ApiClient.serverAddress() + '/System/Info/Public';
|
|
|
|
fetch(url).then(response => {
|
|
|
|
if (!response.ok) return Promise.reject('fetch failed');
|
|
|
|
return response.json();
|
|
|
|
}).then(data => {
|
|
|
|
if (data !== null && data.StartupWizardCompleted === false) {
|
|
|
|
ServerConnections.setLocalApiClient(firstResult.ApiClient);
|
2022-04-20 17:09:31 -04:00
|
|
|
this.show('wizardstart.html');
|
2021-03-22 23:34:48 +03:00
|
|
|
} else {
|
2022-04-20 17:40:36 -04:00
|
|
|
this.#handleConnectionResult(firstResult);
|
2021-03-22 23:34:48 +03:00
|
|
|
}
|
|
|
|
}).catch(error => {
|
|
|
|
console.error(error);
|
|
|
|
});
|
2020-11-07 15:16:39 +09:00
|
|
|
|
2021-03-22 23:34:48 +03:00
|
|
|
return;
|
2022-10-21 15:05:50 -04:00
|
|
|
} else if (firstResult.State !== ConnectionState.SignedIn) {
|
2022-04-20 17:40:36 -04:00
|
|
|
this.#handleConnectionResult(firstResult);
|
2021-03-22 23:34:48 +03:00
|
|
|
return;
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
|
|
|
|
2020-10-17 19:08:56 +01:00
|
|
|
const apiClient = ServerConnections.currentApiClient();
|
2020-07-30 20:34:21 +02:00
|
|
|
const pathname = ctx.pathname.toLowerCase();
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2022-04-13 16:38:19 -04:00
|
|
|
console.debug('[appRouter] processing path request: ' + pathname);
|
2020-07-30 20:34:21 +02:00
|
|
|
const isCurrentRouteStartup = this.currentRouteInfo ? this.currentRouteInfo.route.startup : true;
|
|
|
|
const shouldExitApp = ctx.isBack && route.isDefaultRoute && isCurrentRouteStartup;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2023-07-06 13:39:48 -04:00
|
|
|
if (!shouldExitApp && (!apiClient?.isLoggedIn()) && !route.anonymous) {
|
2022-04-13 16:38:19 -04:00
|
|
|
console.debug('[appRouter] route does not allow anonymous access: redirecting to login');
|
2022-04-20 17:40:36 -04:00
|
|
|
this.#beginConnectionWizard();
|
2019-01-10 15:39:37 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-10-23 01:05:09 +03:00
|
|
|
if (shouldExitApp) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (appHost.supports('exit')) {
|
|
|
|
appHost.exit();
|
|
|
|
}
|
2020-09-10 23:20:55 +09:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-07-06 13:39:48 -04:00
|
|
|
if (apiClient?.isLoggedIn()) {
|
2022-04-13 16:38:19 -04:00
|
|
|
console.debug('[appRouter] user is authenticated');
|
2019-03-24 12:15:13 +00:00
|
|
|
|
2022-10-21 17:28:17 -04:00
|
|
|
if (route.roles) {
|
2022-04-20 17:40:36 -04:00
|
|
|
this.#validateRoles(apiClient, route.roles).then(() => {
|
2019-01-10 15:39:37 +03:00
|
|
|
callback();
|
2022-04-22 14:30:05 -04:00
|
|
|
}, this.#beginConnectionWizard.bind(this));
|
2019-01-10 15:39:37 +03:00
|
|
|
return;
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2022-04-13 16:38:19 -04:00
|
|
|
console.debug('[appRouter] proceeding to page: ' + pathname);
|
2019-01-10 15:39:37 +03:00
|
|
|
callback();
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2022-04-20 17:40:36 -04:00
|
|
|
#validateRoles(apiClient, roles) {
|
2020-07-30 20:34:21 +02:00
|
|
|
return Promise.all(roles.split(',').map((role) => {
|
2022-04-20 17:40:36 -04:00
|
|
|
return this.#validateRole(apiClient, role);
|
2019-01-10 15:39:37 +03:00
|
|
|
}));
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2022-04-20 17:40:36 -04:00
|
|
|
#validateRole(apiClient, role) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (role === 'admin') {
|
2020-07-30 20:34:21 +02:00
|
|
|
return apiClient.getCurrentUser().then((user) => {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (user.Policy.IsAdministrator) {
|
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
return Promise.reject();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unknown role
|
|
|
|
return Promise.resolve();
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2022-04-20 17:40:36 -04:00
|
|
|
#loadContent(ctx, route, html, request) {
|
2020-07-18 09:21:15 +01:00
|
|
|
html = globalize.translateHtml(html, route.dictionary);
|
2019-01-10 15:39:37 +03:00
|
|
|
request.view = html;
|
|
|
|
|
|
|
|
viewManager.loadView(request);
|
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
this.currentRouteInfo = {
|
2018-10-23 01:05:09 +03:00
|
|
|
route: route,
|
|
|
|
path: ctx.path
|
2019-01-10 15:39:37 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
ctx.handled = true;
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2022-04-20 17:40:36 -04:00
|
|
|
#getRequestFile() {
|
2020-08-25 10:12:35 +09:00
|
|
|
let path = window.location.pathname || '';
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
const index = path.lastIndexOf('/');
|
2019-01-10 15:39:37 +03:00
|
|
|
if (index !== -1) {
|
|
|
|
path = path.substring(index);
|
|
|
|
} else {
|
|
|
|
path = '/' + path;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!path || path === '/') {
|
|
|
|
path = '/index.html';
|
|
|
|
}
|
|
|
|
|
|
|
|
return path;
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2022-04-13 16:38:19 -04:00
|
|
|
#getHandler(route) {
|
2020-07-30 20:34:21 +02:00
|
|
|
return (ctx, next) => {
|
2022-04-27 15:14:35 -04:00
|
|
|
const ignore = ctx.path === this.currentRouteInfo.path;
|
2021-09-21 22:26:09 +03:00
|
|
|
if (ignore) {
|
2022-04-27 15:14:35 -04:00
|
|
|
console.debug('[appRouter] path did not change, ignoring route change');
|
2021-09-21 22:26:09 +03:00
|
|
|
// Resolve 'show' promise
|
|
|
|
this.onViewShow();
|
|
|
|
return;
|
|
|
|
}
|
2021-08-25 00:11:12 +03:00
|
|
|
|
2022-04-20 17:40:36 -04:00
|
|
|
this.#handleRoute(ctx, next, route);
|
2020-07-30 20:34:21 +02:00
|
|
|
};
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
getRouteUrl(item, options) {
|
|
|
|
if (!item) {
|
|
|
|
throw new Error('item cannot be null');
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (item.url) {
|
|
|
|
return item.url;
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
const context = options ? options.context : null;
|
|
|
|
const id = item.Id || item.ItemId;
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (!options) {
|
|
|
|
options = {};
|
|
|
|
}
|
2020-01-25 01:18:07 +03:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
let url;
|
|
|
|
// TODO: options will never be false. Replace condition with lodash's isEmpty()
|
|
|
|
const itemType = item.Type || (options ? options.itemType : null);
|
|
|
|
const serverId = item.ServerId || options.serverId;
|
|
|
|
|
|
|
|
if (item === 'settings') {
|
2022-06-09 14:54:39 -04:00
|
|
|
return '#/mypreferencesmenu.html';
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (item === 'wizard') {
|
2022-06-09 14:54:39 -04:00
|
|
|
return '#/wizardstart.html';
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
2020-07-24 19:02:25 +02:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (item === 'manageserver') {
|
2022-06-09 14:54:39 -04:00
|
|
|
return '#/dashboard.html';
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
2019-01-11 13:40:36 +01:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (item === 'recordedtv') {
|
2022-06-09 14:54:39 -04:00
|
|
|
return '#/livetv.html?tab=3&serverId=' + options.serverId;
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
2019-01-11 13:40:36 +01:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (item === 'nextup') {
|
2022-06-09 14:54:39 -04:00
|
|
|
return '#/list.html?type=nextup&serverId=' + options.serverId;
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (item === 'list') {
|
2022-10-16 16:04:37 +02:00
|
|
|
let urlForList = '#/list.html?serverId=' + options.serverId + '&type=' + options.itemTypes;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (options.isFavorite) {
|
2022-10-16 16:04:37 +02:00
|
|
|
urlForList += '&IsFavorite=true';
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
2020-07-30 20:34:21 +02:00
|
|
|
|
2022-10-16 16:04:37 +02:00
|
|
|
return urlForList;
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (item === 'livetv') {
|
|
|
|
if (options.section === 'programs') {
|
2022-06-09 14:54:39 -04:00
|
|
|
return '#/livetv.html?tab=0&serverId=' + options.serverId;
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
|
|
|
if (options.section === 'guide') {
|
2022-06-09 14:54:39 -04:00
|
|
|
return '#/livetv.html?tab=1&serverId=' + options.serverId;
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (options.section === 'movies') {
|
2022-06-09 14:54:39 -04:00
|
|
|
return '#/list.html?type=Programs&IsMovie=true&serverId=' + options.serverId;
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (options.section === 'shows') {
|
2022-06-09 14:54:39 -04:00
|
|
|
return '#/list.html?type=Programs&IsSeries=true&IsMovie=false&IsNews=false&serverId=' + options.serverId;
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (options.section === 'sports') {
|
2022-06-09 14:54:39 -04:00
|
|
|
return '#/list.html?type=Programs&IsSports=true&serverId=' + options.serverId;
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (options.section === 'kids') {
|
2022-06-09 14:54:39 -04:00
|
|
|
return '#/list.html?type=Programs&IsKids=true&serverId=' + options.serverId;
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (options.section === 'news') {
|
2022-06-09 14:54:39 -04:00
|
|
|
return '#/list.html?type=Programs&IsNews=true&serverId=' + options.serverId;
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (options.section === 'onnow') {
|
2022-06-09 14:54:39 -04:00
|
|
|
return '#/list.html?type=Programs&IsAiring=true&serverId=' + options.serverId;
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
|
|
|
|
2022-01-13 10:15:24 -05:00
|
|
|
if (options.section === 'channels') {
|
2022-06-09 14:54:39 -04:00
|
|
|
return '#/livetv.html?tab=2&serverId=' + options.serverId;
|
2022-01-13 10:15:24 -05:00
|
|
|
}
|
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (options.section === 'dvrschedule') {
|
2022-06-09 14:54:39 -04:00
|
|
|
return '#/livetv.html?tab=4&serverId=' + options.serverId;
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (options.section === 'seriesrecording') {
|
2022-06-09 14:54:39 -04:00
|
|
|
return '#/livetv.html?tab=5&serverId=' + options.serverId;
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
|
|
|
|
2022-06-09 14:54:39 -04:00
|
|
|
return '#/livetv.html?serverId=' + options.serverId;
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (itemType == 'SeriesTimer') {
|
2022-06-09 14:54:39 -04:00
|
|
|
return '#/details?seriesTimerId=' + id + '&serverId=' + serverId;
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (item.CollectionType == 'livetv') {
|
2022-06-09 14:54:39 -04:00
|
|
|
return '#/livetv.html';
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (item.Type === 'Genre') {
|
2022-06-09 14:54:39 -04:00
|
|
|
url = '#/list.html?genreId=' + item.Id + '&serverId=' + serverId;
|
2020-07-30 20:34:21 +02:00
|
|
|
|
|
|
|
if (context === 'livetv') {
|
|
|
|
url += '&type=Programs';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (options.parentId) {
|
|
|
|
url += '&parentId=' + options.parentId;
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
return url;
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
2020-07-30 20:34:21 +02:00
|
|
|
|
|
|
|
if (item.Type === 'MusicGenre') {
|
2022-06-09 14:54:39 -04:00
|
|
|
url = '#/list.html?musicGenreId=' + item.Id + '&serverId=' + serverId;
|
2020-07-30 20:34:21 +02:00
|
|
|
|
|
|
|
if (options.parentId) {
|
|
|
|
url += '&parentId=' + options.parentId;
|
|
|
|
}
|
|
|
|
|
|
|
|
return url;
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (item.Type === 'Studio') {
|
2022-06-09 14:54:39 -04:00
|
|
|
url = '#/list.html?studioId=' + item.Id + '&serverId=' + serverId;
|
2020-07-30 20:34:21 +02:00
|
|
|
|
|
|
|
if (options.parentId) {
|
|
|
|
url += '&parentId=' + options.parentId;
|
|
|
|
}
|
|
|
|
|
|
|
|
return url;
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (context !== 'folders' && !itemHelper.isLocalItem(item)) {
|
|
|
|
if (item.CollectionType == 'movies') {
|
2022-06-09 14:54:39 -04:00
|
|
|
url = '#/movies.html?topParentId=' + item.Id;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (options && options.section === 'latest') {
|
|
|
|
url += '&tab=1';
|
|
|
|
}
|
|
|
|
|
|
|
|
return url;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (item.CollectionType == 'tvshows') {
|
2022-06-09 14:54:39 -04:00
|
|
|
url = '#/tv.html?topParentId=' + item.Id;
|
2020-07-30 20:34:21 +02:00
|
|
|
|
|
|
|
if (options && options.section === 'latest') {
|
2021-01-02 00:23:43 -05:00
|
|
|
url += '&tab=1';
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return url;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (item.CollectionType == 'music') {
|
2022-06-09 14:54:39 -04:00
|
|
|
url = '#/music.html?topParentId=' + item.Id;
|
2021-01-02 00:23:43 -05:00
|
|
|
|
|
|
|
if (options?.section === 'latest') {
|
|
|
|
url += '&tab=1';
|
|
|
|
}
|
|
|
|
|
|
|
|
return url;
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
const itemTypes = ['Playlist', 'TvChannel', 'Program', 'BoxSet', 'MusicAlbum', 'MusicGenre', 'Person', 'Recording', 'MusicArtist'];
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (itemTypes.indexOf(itemType) >= 0) {
|
2022-06-09 14:54:39 -04:00
|
|
|
return '#/details?id=' + id + '&serverId=' + serverId;
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
const contextSuffix = context ? '&context=' + context : '';
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (itemType == 'Series' || itemType == 'Season' || itemType == 'Episode') {
|
2022-06-09 14:54:39 -04:00
|
|
|
return '#/details?id=' + id + contextSuffix + '&serverId=' + serverId;
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-30 20:34:21 +02:00
|
|
|
if (item.IsFolder) {
|
|
|
|
if (id) {
|
2022-06-09 14:54:39 -04:00
|
|
|
return '#/list.html?parentId=' + id + '&serverId=' + serverId;
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return '#';
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
2020-07-30 20:34:21 +02:00
|
|
|
|
2022-06-09 14:54:39 -04:00
|
|
|
return '#/details?id=' + id + '&serverId=' + serverId;
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
2022-04-20 17:09:31 -04:00
|
|
|
|
|
|
|
showLocalLogin(serverId) {
|
|
|
|
return this.show('login.html?serverid=' + serverId);
|
|
|
|
}
|
|
|
|
|
|
|
|
showVideoOsd() {
|
|
|
|
return this.show('video');
|
|
|
|
}
|
|
|
|
|
|
|
|
showSelectServer() {
|
|
|
|
return this.show('selectserver.html');
|
|
|
|
}
|
|
|
|
|
|
|
|
showSettings() {
|
|
|
|
return this.show('mypreferencesmenu.html');
|
|
|
|
}
|
|
|
|
|
|
|
|
showNowPlaying() {
|
|
|
|
return this.show('queue');
|
|
|
|
}
|
|
|
|
|
|
|
|
showGuide() {
|
|
|
|
return this.show('livetv.html?tab=1');
|
|
|
|
}
|
|
|
|
|
|
|
|
goHome() {
|
|
|
|
return this.show('home.html');
|
|
|
|
}
|
|
|
|
|
|
|
|
showSearch() {
|
|
|
|
return this.show('search.html');
|
|
|
|
}
|
|
|
|
|
|
|
|
showLiveTV() {
|
|
|
|
return this.show('livetv.html');
|
|
|
|
}
|
|
|
|
|
|
|
|
showRecordedTV() {
|
|
|
|
return this.show('livetv.html?tab=3');
|
|
|
|
}
|
|
|
|
|
|
|
|
showFavorites() {
|
|
|
|
return this.show('home.html?tab=1');
|
|
|
|
}
|
2020-07-30 20:34:21 +02:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-08-16 20:24:45 +02:00
|
|
|
export const appRouter = new AppRouter();
|
2020-10-18 20:00:39 +01:00
|
|
|
|
|
|
|
window.Emby = window.Emby || {};
|
|
|
|
window.Emby.Page = appRouter;
|