1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Merge pull request #1918 from jellyfin/standalone

Fix startup wizard redirect and standalone mode
This commit is contained in:
Joshua M. Boniface 2020-11-21 22:25:50 -05:00 committed by GitHub
commit 3961657c70
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 104 additions and 134 deletions

View file

@ -44,7 +44,7 @@ Jellyfin Web is the frontend used for most of the clients available for end user
### Dependencies ### 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) - [Yarn 1.22.4](https://classic.yarnpkg.com/en/docs/install)
- Gulp-cli - Gulp-cli
@ -69,14 +69,8 @@ Jellyfin Web is the frontend used for most of the clients available for end user
yarn serve yarn serve
``` ```
4. Build the client with sourcemaps. 4. Build the client with sourcemaps available.
```sh ```sh
yarn build:development yarn build:development
``` ```
You can build a nginx compatible version as well.
```sh
yarn build:standalone
```

View file

@ -2,7 +2,6 @@ const { src, dest, series, parallel, watch } = require('gulp');
const browserSync = require('browser-sync').create(); const browserSync = require('browser-sync').create();
const del = require('del'); const del = require('del');
const babel = require('gulp-babel'); const babel = require('gulp-babel');
const concat = require('gulp-concat');
const terser = require('gulp-terser'); const terser = require('gulp-terser');
const htmlmin = require('gulp-htmlmin'); const htmlmin = require('gulp-htmlmin');
const imagemin = require('gulp-imagemin'); const imagemin = require('gulp-imagemin');
@ -16,7 +15,6 @@ const stream = require('webpack-stream');
const inject = require('gulp-inject'); const inject = require('gulp-inject');
const postcss = require('gulp-postcss'); const postcss = require('gulp-postcss');
const sass = require('gulp-sass'); const sass = require('gulp-sass');
const gulpif = require('gulp-if');
const lazypipe = require('lazypipe'); const lazypipe = require('lazypipe');
sass.compiler = require('node-sass'); sass.compiler = require('node-sass');
@ -30,10 +28,7 @@ if (mode.production()) {
const options = { const options = {
javascript: { javascript: {
query: ['src/**/*.js', '!src/bundle.js', '!src/standalone.js', '!src/scripts/apploader.js'] query: ['src/**/*.js', '!src/bundle.js']
},
apploader: {
query: ['src/standalone.js', 'src/scripts/apploader.js']
}, },
css: { css: {
query: ['src/**/*.css', 'src/**/*.scss'] query: ['src/**/*.css', 'src/**/*.scss']
@ -68,8 +63,6 @@ function serve() {
} }
}); });
watch(options.apploader.query, apploader(true));
watch('src/bundle.js', webpack); watch('src/bundle.js', webpack);
watch(options.css.query).on('all', function (event, path) { watch(options.css.query).on('all', function (event, path) {
@ -131,20 +124,6 @@ function javascript(query) {
.pipe(browserSync.stream()); .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() { function webpack() {
return stream(config) return stream(config)
.pipe(dest('dist/')) .pipe(dest('dist/'))
@ -195,10 +174,5 @@ function injectBundle() {
.pipe(browserSync.stream()); .pipe(browserSync.stream());
} }
function build(standalone) { exports.default = series(clean, parallel(javascript, webpack, css, html, images, copy), injectBundle);
return series(clean, parallel(javascript, apploader(standalone), webpack, css, html, images, copy)); exports.serve = series(exports.default, serve);
}
exports.default = series(build(false), injectBundle);
exports.standalone = series(build(true), injectBundle);
exports.serve = series(exports.standalone, serve);

View file

@ -1,4 +0,0 @@
export default {
isNativeApp: false
};

View file

@ -1,7 +1,6 @@
import { ConnectionManager, Credentials, ApiClient, Events } from 'jellyfin-apiclient'; import { ConnectionManager, Credentials, ApiClient, Events } from 'jellyfin-apiclient';
import { appHost } from './apphost'; import { appHost } from './apphost';
import Dashboard from '../scripts/clientUtils'; import Dashboard from '../scripts/clientUtils';
import AppInfo from './AppInfo';
import { setUserInfo } from '../scripts/settings/userSettings'; import { setUserInfo } from '../scripts/settings/userSettings';
class ServerConnections extends ConnectionManager { class ServerConnections extends ConnectionManager {
@ -14,12 +13,11 @@ class ServerConnections extends ConnectionManager {
}); });
} }
initApiClient() { initApiClient(server) {
if (!AppInfo.isNativeApp) {
console.debug('creating ApiClient singleton'); console.debug('creating ApiClient singleton');
const apiClient = new ApiClient( const apiClient = new ApiClient(
Dashboard.serverAddress(), server,
appHost.appName(), appHost.appName(),
appHost.appVersion(), appHost.appVersion(),
appHost.deviceName(), appHost.deviceName(),
@ -35,7 +33,6 @@ class ServerConnections extends ConnectionManager {
console.debug('loaded ApiClient singleton'); console.debug('loaded ApiClient singleton');
} }
}
setLocalApiClient(apiClient) { setLocalApiClient(apiClient) {
if (apiClient) { if (apiClient) {

View file

@ -8,7 +8,6 @@ import itemHelper from './itemHelper';
import loading from './loading/loading'; import loading from './loading/loading';
import page from 'page'; import page from 'page';
import viewManager from './viewManager/viewManager'; import viewManager from './viewManager/viewManager';
import AppInfo from './AppInfo';
import Dashboard from '../scripts/clientUtils'; import Dashboard from '../scripts/clientUtils';
import ServerConnections from './ServerConnections'; import ServerConnections from './ServerConnections';
import alert from './alert'; import alert from './alert';
@ -80,11 +79,7 @@ class AppRouter {
} }
showSelectServer() { showSelectServer() {
Dashboard.navigate(AppInfo.isNativeApp ? 'selectserver.html' : 'login.html'); Dashboard.navigate('selectserver.html');
}
showWelcome() {
Dashboard.navigate(AppInfo.isNativeApp ? 'selectserver.html' : 'login.html');
} }
showSettings() { showSettings() {
@ -286,9 +281,6 @@ class AppRouter {
case 'ServerSelection': case 'ServerSelection':
this.showSelectServer(); this.showSelectServer();
break; break;
case 'ConnectSignIn':
this.showWelcome();
break;
case 'ServerUpdateNeeded': case 'ServerUpdateNeeded':
alert({ alert({
text: globalize.translate('ServerUpdateNeeded', 'https://github.com/jellyfin/jellyfin'), text: globalize.translate('ServerUpdateNeeded', 'https://github.com/jellyfin/jellyfin'),
@ -508,25 +500,35 @@ class AppRouter {
authenticate(ctx, route, callback) { authenticate(ctx, route, callback) {
const firstResult = this.firstConnectionResult; const firstResult = this.firstConnectionResult;
if (firstResult) {
this.firstConnectionResult = null;
if (firstResult.State !== 'SignedIn' && !route.anonymous) { 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); this.handleConnectionResult(firstResult);
return;
} }
}).catch(error => {
console.error(error);
});
return;
} }
const apiClient = ServerConnections.currentApiClient(); const apiClient = ServerConnections.currentApiClient();
const pathname = ctx.pathname.toLowerCase(); 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 isCurrentRouteStartup = this.currentRouteInfo ? this.currentRouteInfo.route.startup : true;
const shouldExitApp = ctx.isBack && route.isDefaultRoute && isCurrentRouteStartup; const shouldExitApp = ctx.isBack && route.isDefaultRoute && isCurrentRouteStartup;
if (!shouldExitApp && (!apiClient || !apiClient.isLoggedIn()) && !route.anonymous) { 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(); this.beginConnectionWizard();
return; return;
} }
@ -534,16 +536,16 @@ class AppRouter {
if (shouldExitApp) { if (shouldExitApp) {
if (appHost.supports('exit')) { if (appHost.supports('exit')) {
appHost.exit(); appHost.exit();
return;
} }
return; return;
} }
if (apiClient && apiClient.isLoggedIn()) { if (apiClient && apiClient.isLoggedIn()) {
console.debug('appRouter - user is authenticated'); console.debug('user is authenticated');
if (route.isDefaultRoute) { if (route.isDefaultRoute) {
console.debug('appRouter - loading skin home page'); console.debug('loading home page');
this.goHome(); this.goHome();
return; return;
} else if (route.roles) { } else if (route.roles) {
@ -554,7 +556,7 @@ class AppRouter {
} }
} }
console.debug('appRouter - proceeding to ' + pathname); console.debug('proceeding to page: ' + pathname);
callback(); callback();
} }

View file

@ -22,6 +22,7 @@
"id": "wmc" "id": "wmc"
} }
], ],
"servers": [],
"plugins": [ "plugins": [
"playAccessValidation/plugin", "playAccessValidation/plugin",
"experimentalWarnings/plugin", "experimentalWarnings/plugin",

View file

@ -6,7 +6,6 @@ import '../../elements/emby-textarea/emby-textarea';
import '../../elements/emby-input/emby-input'; import '../../elements/emby-input/emby-input';
import '../../elements/emby-select/emby-select'; import '../../elements/emby-select/emby-select';
import '../../elements/emby-button/emby-button'; import '../../elements/emby-button/emby-button';
import AppInfo from '../../components/AppInfo';
import Dashboard from '../../scripts/clientUtils'; import Dashboard from '../../scripts/clientUtils';
import alert from '../../components/alert'; import alert from '../../components/alert';
@ -47,10 +46,6 @@ import alert from '../../components/alert';
ApiClient.updateNamedConfiguration(brandingConfigKey, brandingConfig).then(function () { ApiClient.updateNamedConfiguration(brandingConfigKey, brandingConfig).then(function () {
Dashboard.processServerConfigurationUpdateResult(); Dashboard.processServerConfigurationUpdateResult();
if (requiresReload && !AppInfo.isNativeApp) {
window.location.reload(true);
}
}); });
}); });
}, function () { }, function () {

View file

@ -1,4 +1,3 @@
import AppInfo from '../components/AppInfo';
import ServerConnections from '../components/ServerConnections'; import ServerConnections from '../components/ServerConnections';
import toast from '../components/toast/toast'; import toast from '../components/toast/toast';
import loading from '../components/loading/loading'; import loading from '../components/loading/loading';
@ -6,38 +5,48 @@ import { appRouter } from '../components/appRouter';
import baseAlert from '../components/alert'; import baseAlert from '../components/alert';
import baseConfirm from '../components/confirm/confirm'; import baseConfirm from '../components/confirm/confirm';
import globalize from '../scripts/globalize'; import globalize from '../scripts/globalize';
import * as webSettings from './settings/webSettings';
export function getCurrentUser() { export function getCurrentUser() {
return window.ApiClient.getCurrentUser(false); return window.ApiClient.getCurrentUser(false);
} }
//TODO: investigate url prefix support for serverAddress function // TODO: investigate url prefix support for serverAddress function
export function serverAddress() { export async function serverAddress() {
if (AppInfo.isNativeApp) {
const apiClient = window.ApiClient; const apiClient = window.ApiClient;
if (apiClient) { if (apiClient) {
return apiClient.serverAddress(); return Promise.resolve(apiClient.serverAddress());
} }
return null; const current = await ServerConnections.getAvailableServers().then(servers => {
if (servers.length !== 0) {
return Promise.resolve(servers[0].ManualAddress);
} }
});
const urlLower = window.location.href.toLowerCase(); // TODO this makes things faster but it also blocks the wizard in some scenarios
const index = urlLower.lastIndexOf('/web'); // if (current) return Promise.resolve(current);
if (index != -1) { const urls = [];
return urlLower.substring(0, index); urls.push(window.location.origin);
} urls.push(`https://${window.location.hostname}:8920`);
urls.push(`http://${window.location.hostname}:8096`);
urls.push(...await webSettings.getServers());
const loc = window.location; const promises = urls.map(url => {
let address = loc.protocol + '//' + loc.hostname; return fetch(`${url}/System/Info/Public`).then(resp => url).catch(error => {
return Promise.resolve();
});
});
if (loc.port) { return Promise.all(promises).then(responses => {
address += ':' + loc.port; responses = responses.filter(response => response);
} return responses[0];
}).catch(error => {
return address; console.log(error);
return Promise.resolve();
});
} }
export function getCurrentUserId() { export function getCurrentUserId() {
@ -56,16 +65,9 @@ export function onServerChanged(userId, accessToken, apiClient) {
export function logout() { export function logout() {
ServerConnections.logout().then(function () { ServerConnections.logout().then(function () {
let loginPage; webSettings.getMultiServer().then(multi => {
multi ? navigate('selectserver.html') : navigate('login.html');
if (AppInfo.isNativeApp) { });
loginPage = 'selectserver.html';
window.ApiClient = null;
} else {
loginPage = 'login.html';
}
navigate(loginPage);
}); });
} }

View file

@ -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 = { const baseDefaultTheme = {
'name': 'Dark', 'name': 'Dark',
'id': 'dark', 'id': 'dark',

View file

@ -7,7 +7,6 @@ import 'classlist.js';
import 'whatwg-fetch'; import 'whatwg-fetch';
import 'resize-observer-polyfill'; import 'resize-observer-polyfill';
import '../assets/css/site.scss'; import '../assets/css/site.scss';
import AppInfo from '../components/AppInfo';
import { Events } from 'jellyfin-apiclient'; import { Events } from 'jellyfin-apiclient';
import ServerConnections from '../components/ServerConnections'; import ServerConnections from '../components/ServerConnections';
import globalize from './globalize'; import globalize from './globalize';
@ -26,7 +25,7 @@ import './libraryMenu';
import './routes'; import './routes';
import '../components/themeMediaPlayer'; import '../components/themeMediaPlayer';
import './autoBackdrops'; import './autoBackdrops';
import { pageClassOn } from './clientUtils'; import { navigate, pageClassOn, serverAddress } from './clientUtils';
import '../libraries/screensavermanager'; import '../libraries/screensavermanager';
import './serverNotifications'; import './serverNotifications';
import '../components/playback/playerSelectionMenu'; import '../components/playback/playerSelectionMenu';
@ -60,12 +59,6 @@ window.getParameterByName = function(name, url) {
return decodeURIComponent(results[1].replace(/\+/g, ' ')); return decodeURIComponent(results[1].replace(/\+/g, ' '));
}; };
if (window.appMode === 'cordova' || window.appMode === 'android' || window.appMode === 'standalone') {
AppInfo.isNativeApp = true;
}
Object.freeze(AppInfo);
function loadCoreDictionary() { 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 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) { const translations = languages.map(function (language) {
@ -82,8 +75,14 @@ function loadCoreDictionary() {
} }
function init() { function init() {
ServerConnections.initApiClient(); serverAddress().then(server => {
if (!server) {
navigate('selectserver.html');
return;
}
ServerConnections.initApiClient(server);
}).then(() => {
console.debug('initAfterDependencies promises resolved'); console.debug('initAfterDependencies promises resolved');
loadCoreDictionary().then(function () { loadCoreDictionary().then(function () {
@ -94,6 +93,7 @@ function init() {
autoFocuser.enable(); autoFocuser.enable();
Events.on(ServerConnections, 'localusersignedin', globalize.updateCurrentCulture); Events.on(ServerConnections, 'localusersignedin', globalize.updateCurrentCulture);
});
} }
function onGlobalizeInit() { function onGlobalizeInit() {