diff --git a/README.md b/README.md index ca42965dd..1108ec9f2 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Jellyfin Web is the frontend used for most of the clients available for end user ### Dependencies -- [Node.js](https://nodejs.org/en/download/) +- [Node.js](https://nodejs.org/en/download) - [Yarn 1.22.4](https://classic.yarnpkg.com/en/docs/install) - Gulp-cli @@ -69,14 +69,8 @@ Jellyfin Web is the frontend used for most of the clients available for end user yarn serve ``` -4. Build the client with sourcemaps. +4. Build the client with sourcemaps available. ```sh yarn build:development ``` - - You can build a nginx compatible version as well. - - ```sh - yarn build:standalone - ``` diff --git a/gulpfile.js b/gulpfile.js index 8b407ec2a..7d1184dbd 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -2,7 +2,6 @@ const { src, dest, series, parallel, watch } = require('gulp'); const browserSync = require('browser-sync').create(); const del = require('del'); const babel = require('gulp-babel'); -const concat = require('gulp-concat'); const terser = require('gulp-terser'); const htmlmin = require('gulp-htmlmin'); const imagemin = require('gulp-imagemin'); @@ -16,7 +15,6 @@ const stream = require('webpack-stream'); const inject = require('gulp-inject'); const postcss = require('gulp-postcss'); const sass = require('gulp-sass'); -const gulpif = require('gulp-if'); const lazypipe = require('lazypipe'); sass.compiler = require('node-sass'); @@ -30,10 +28,7 @@ if (mode.production()) { const options = { javascript: { - query: ['src/**/*.js', '!src/bundle.js', '!src/standalone.js', '!src/scripts/apploader.js'] - }, - apploader: { - query: ['src/standalone.js', 'src/scripts/apploader.js'] + query: ['src/**/*.js', '!src/bundle.js'] }, css: { query: ['src/**/*.css', 'src/**/*.scss'] @@ -68,8 +63,6 @@ function serve() { } }); - watch(options.apploader.query, apploader(true)); - watch('src/bundle.js', webpack); watch(options.css.query).on('all', function (event, path) { @@ -131,20 +124,6 @@ function javascript(query) { .pipe(browserSync.stream()); } -function apploader(standalone) { - function task() { - return src(options.apploader.query, { base: './src/' }) - .pipe(gulpif(standalone, concat('scripts/apploader.js'))) - .pipe(pipelineJavascript()) - .pipe(dest('dist/')) - .pipe(browserSync.stream()); - } - - task.displayName = 'apploader'; - - return task; -} - function webpack() { return stream(config) .pipe(dest('dist/')) @@ -195,10 +174,5 @@ function injectBundle() { .pipe(browserSync.stream()); } -function build(standalone) { - return series(clean, parallel(javascript, apploader(standalone), webpack, css, html, images, copy)); -} - -exports.default = series(build(false), injectBundle); -exports.standalone = series(build(true), injectBundle); -exports.serve = series(exports.standalone, serve); +exports.default = series(clean, parallel(javascript, webpack, css, html, images, copy), injectBundle); +exports.serve = series(exports.default, serve); diff --git a/src/components/AppInfo.js b/src/components/AppInfo.js deleted file mode 100644 index a89c55d0b..000000000 --- a/src/components/AppInfo.js +++ /dev/null @@ -1,4 +0,0 @@ - -export default { - isNativeApp: false -}; diff --git a/src/components/ServerConnections.js b/src/components/ServerConnections.js index 316f42558..0242e549c 100644 --- a/src/components/ServerConnections.js +++ b/src/components/ServerConnections.js @@ -1,7 +1,6 @@ import { ConnectionManager, Credentials, ApiClient, Events } from 'jellyfin-apiclient'; import { appHost } from './apphost'; import Dashboard from '../scripts/clientUtils'; -import AppInfo from './AppInfo'; import { setUserInfo } from '../scripts/settings/userSettings'; class ServerConnections extends ConnectionManager { @@ -14,27 +13,25 @@ class ServerConnections extends ConnectionManager { }); } - initApiClient() { - if (!AppInfo.isNativeApp) { - console.debug('creating ApiClient singleton'); + initApiClient(server) { + console.debug('creating ApiClient singleton'); - const apiClient = new ApiClient( - Dashboard.serverAddress(), - appHost.appName(), - appHost.appVersion(), - appHost.deviceName(), - appHost.deviceId() - ); + const apiClient = new ApiClient( + server, + appHost.appName(), + appHost.appVersion(), + appHost.deviceName(), + appHost.deviceId() + ); - apiClient.enableAutomaticNetworking = false; - apiClient.manualAddressOnly = true; + apiClient.enableAutomaticNetworking = false; + apiClient.manualAddressOnly = true; - this.addApiClient(apiClient); + this.addApiClient(apiClient); - this.setLocalApiClient(apiClient); + this.setLocalApiClient(apiClient); - console.debug('loaded ApiClient singleton'); - } + console.debug('loaded ApiClient singleton'); } setLocalApiClient(apiClient) { diff --git a/src/components/appRouter.js b/src/components/appRouter.js index 76a76e516..03000ebaf 100644 --- a/src/components/appRouter.js +++ b/src/components/appRouter.js @@ -8,7 +8,6 @@ import itemHelper from './itemHelper'; import loading from './loading/loading'; import page from 'page'; import viewManager from './viewManager/viewManager'; -import AppInfo from './AppInfo'; import Dashboard from '../scripts/clientUtils'; import ServerConnections from './ServerConnections'; import alert from './alert'; @@ -80,11 +79,7 @@ class AppRouter { } showSelectServer() { - Dashboard.navigate(AppInfo.isNativeApp ? 'selectserver.html' : 'login.html'); - } - - showWelcome() { - Dashboard.navigate(AppInfo.isNativeApp ? 'selectserver.html' : 'login.html'); + Dashboard.navigate('selectserver.html'); } showSettings() { @@ -286,9 +281,6 @@ class AppRouter { case 'ServerSelection': this.showSelectServer(); break; - case 'ConnectSignIn': - this.showWelcome(); - break; case 'ServerUpdateNeeded': alert({ text: globalize.translate('ServerUpdateNeeded', 'https://github.com/jellyfin/jellyfin'), @@ -508,25 +500,35 @@ class AppRouter { authenticate(ctx, route, callback) { const firstResult = this.firstConnectionResult; - if (firstResult) { - this.firstConnectionResult = null; - if (firstResult.State !== 'SignedIn' && !route.anonymous) { - this.handleConnectionResult(firstResult); - return; - } + this.firstConnectionResult = null; + if (firstResult && firstResult.State === 'ServerSignIn') { + const url = 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) { + Dashboard.navigate('wizardstart.html'); + } else { + this.handleConnectionResult(firstResult); + } + }).catch(error => { + console.error(error); + }); + + return; } const apiClient = ServerConnections.currentApiClient(); const pathname = ctx.pathname.toLowerCase(); - console.debug('appRouter - processing path request ' + pathname); - + console.debug('processing path request: ' + pathname); const isCurrentRouteStartup = this.currentRouteInfo ? this.currentRouteInfo.route.startup : true; const shouldExitApp = ctx.isBack && route.isDefaultRoute && isCurrentRouteStartup; if (!shouldExitApp && (!apiClient || !apiClient.isLoggedIn()) && !route.anonymous) { - console.debug('appRouter - route does not allow anonymous access, redirecting to login'); + console.debug('route does not allow anonymous access: redirecting to login'); this.beginConnectionWizard(); return; } @@ -534,16 +536,16 @@ class AppRouter { if (shouldExitApp) { if (appHost.supports('exit')) { appHost.exit(); - return; } + return; } if (apiClient && apiClient.isLoggedIn()) { - console.debug('appRouter - user is authenticated'); + console.debug('user is authenticated'); if (route.isDefaultRoute) { - console.debug('appRouter - loading skin home page'); + console.debug('loading home page'); this.goHome(); return; } else if (route.roles) { @@ -554,7 +556,7 @@ class AppRouter { } } - console.debug('appRouter - proceeding to ' + pathname); + console.debug('proceeding to page: ' + pathname); callback(); } diff --git a/src/config.json b/src/config.json index ef7a6a99f..b896b5f06 100644 --- a/src/config.json +++ b/src/config.json @@ -22,6 +22,7 @@ "id": "wmc" } ], + "servers": [], "plugins": [ "playAccessValidation/plugin", "experimentalWarnings/plugin", diff --git a/src/controllers/dashboard/general.js b/src/controllers/dashboard/general.js index bdb99c710..a780f35e1 100644 --- a/src/controllers/dashboard/general.js +++ b/src/controllers/dashboard/general.js @@ -6,7 +6,6 @@ import '../../elements/emby-textarea/emby-textarea'; import '../../elements/emby-input/emby-input'; import '../../elements/emby-select/emby-select'; import '../../elements/emby-button/emby-button'; -import AppInfo from '../../components/AppInfo'; import Dashboard from '../../scripts/clientUtils'; import alert from '../../components/alert'; @@ -47,10 +46,6 @@ import alert from '../../components/alert'; ApiClient.updateNamedConfiguration(brandingConfigKey, brandingConfig).then(function () { Dashboard.processServerConfigurationUpdateResult(); - - if (requiresReload && !AppInfo.isNativeApp) { - window.location.reload(true); - } }); }); }, function () { diff --git a/src/scripts/clientUtils.js b/src/scripts/clientUtils.js index ec0a0e784..5b43cd469 100644 --- a/src/scripts/clientUtils.js +++ b/src/scripts/clientUtils.js @@ -1,4 +1,3 @@ -import AppInfo from '../components/AppInfo'; import ServerConnections from '../components/ServerConnections'; import toast from '../components/toast/toast'; import loading from '../components/loading/loading'; @@ -6,38 +5,48 @@ import { appRouter } from '../components/appRouter'; import baseAlert from '../components/alert'; import baseConfirm from '../components/confirm/confirm'; import globalize from '../scripts/globalize'; +import * as webSettings from './settings/webSettings'; export function getCurrentUser() { return window.ApiClient.getCurrentUser(false); } -//TODO: investigate url prefix support for serverAddress function -export function serverAddress() { - if (AppInfo.isNativeApp) { - const apiClient = window.ApiClient; +// TODO: investigate url prefix support for serverAddress function +export async function serverAddress() { + const apiClient = window.ApiClient; - if (apiClient) { - return apiClient.serverAddress(); + if (apiClient) { + return Promise.resolve(apiClient.serverAddress()); + } + + const current = await ServerConnections.getAvailableServers().then(servers => { + if (servers.length !== 0) { + return Promise.resolve(servers[0].ManualAddress); } + }); - return null; - } + // TODO this makes things faster but it also blocks the wizard in some scenarios + // if (current) return Promise.resolve(current); - const urlLower = window.location.href.toLowerCase(); - const index = urlLower.lastIndexOf('/web'); + const urls = []; + urls.push(window.location.origin); + urls.push(`https://${window.location.hostname}:8920`); + urls.push(`http://${window.location.hostname}:8096`); + urls.push(...await webSettings.getServers()); - if (index != -1) { - return urlLower.substring(0, index); - } + const promises = urls.map(url => { + return fetch(`${url}/System/Info/Public`).then(resp => url).catch(error => { + return Promise.resolve(); + }); + }); - const loc = window.location; - let address = loc.protocol + '//' + loc.hostname; - - if (loc.port) { - address += ':' + loc.port; - } - - return address; + return Promise.all(promises).then(responses => { + responses = responses.filter(response => response); + return responses[0]; + }).catch(error => { + console.log(error); + return Promise.resolve(); + }); } export function getCurrentUserId() { @@ -56,16 +65,9 @@ export function onServerChanged(userId, accessToken, apiClient) { export function logout() { ServerConnections.logout().then(function () { - let loginPage; - - if (AppInfo.isNativeApp) { - loginPage = 'selectserver.html'; - window.ApiClient = null; - } else { - loginPage = 'login.html'; - } - - navigate(loginPage); + webSettings.getMultiServer().then(multi => { + multi ? navigate('selectserver.html') : navigate('login.html'); + }); }); } diff --git a/src/scripts/settings/webSettings.js b/src/scripts/settings/webSettings.js index 189af93de..f872200fe 100644 --- a/src/scripts/settings/webSettings.js +++ b/src/scripts/settings/webSettings.js @@ -85,6 +85,15 @@ export function getMultiServer() { }); } +export function getServers() { + return getConfig().then(config => { + return config.servers || []; + }).catch(error => { + console.log('cannot get web config:', error); + return []; + }); +} + const baseDefaultTheme = { 'name': 'Dark', 'id': 'dark', diff --git a/src/scripts/site.js b/src/scripts/site.js index c8b02c606..13efd5611 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -7,7 +7,6 @@ import 'classlist.js'; import 'whatwg-fetch'; import 'resize-observer-polyfill'; import '../assets/css/site.scss'; -import AppInfo from '../components/AppInfo'; import { Events } from 'jellyfin-apiclient'; import ServerConnections from '../components/ServerConnections'; import globalize from './globalize'; @@ -26,7 +25,7 @@ import './libraryMenu'; import './routes'; import '../components/themeMediaPlayer'; import './autoBackdrops'; -import { pageClassOn } from './clientUtils'; +import { navigate, pageClassOn, serverAddress } from './clientUtils'; import '../libraries/screensavermanager'; import './serverNotifications'; import '../components/playback/playerSelectionMenu'; @@ -60,12 +59,6 @@ window.getParameterByName = function(name, url) { return decodeURIComponent(results[1].replace(/\+/g, ' ')); }; -if (window.appMode === 'cordova' || window.appMode === 'android' || window.appMode === 'standalone') { - AppInfo.isNativeApp = true; -} - -Object.freeze(AppInfo); - function loadCoreDictionary() { const languages = ['ar', 'be-by', 'bg-bg', 'ca', 'cs', 'da', 'de', 'el', 'en-gb', 'en-us', 'es', 'es-ar', 'es-mx', 'fa', 'fi', 'fr', 'fr-ca', 'gsw', 'he', 'hi-in', 'hr', 'hu', 'id', 'it', 'ja', 'kk', 'ko', 'lt-lt', 'ms', 'nb', 'nl', 'pl', 'pt-br', 'pt-pt', 'ro', 'ru', 'sk', 'sl-si', 'sv', 'tr', 'uk', 'vi', 'zh-cn', 'zh-hk', 'zh-tw']; const translations = languages.map(function (language) { @@ -82,18 +75,25 @@ function loadCoreDictionary() { } function init() { - ServerConnections.initApiClient(); + serverAddress().then(server => { + if (!server) { + navigate('selectserver.html'); + return; + } - console.debug('initAfterDependencies promises resolved'); + ServerConnections.initApiClient(server); + }).then(() => { + console.debug('initAfterDependencies promises resolved'); - loadCoreDictionary().then(function () { - onGlobalizeInit(); + loadCoreDictionary().then(function () { + onGlobalizeInit(); + }); + + keyboardNavigation.enable(); + autoFocuser.enable(); + + Events.on(ServerConnections, 'localusersignedin', globalize.updateCurrentCulture); }); - - keyboardNavigation.enable(); - autoFocuser.enable(); - - Events.on(ServerConnections, 'localusersignedin', globalize.updateCurrentCulture); } function onGlobalizeInit() {