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

Merge pull request #1096 from ConfusedPolarBear/quickconnect

Add quick connect (login without typing password)
This commit is contained in:
dkanada 2020-09-15 15:03:26 +09:00 committed by GitHub
commit 1dad3e7eeb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 319 additions and 3 deletions

View file

@ -34,6 +34,7 @@
- [Ryan Hartzell](https://github.com/ryan-hartzell)
- [Thibault Nocchi](https://github.com/ThibaultNocchi)
- [MrTimscampi](https://github.com/MrTimscampi)
- [ConfusedPolarBear](https://github.com/ConfusedPolarBear)
- [Sarab Singh](https://github.com/sarab97)
- [GuilhermeHideki](https://github.com/GuilhermeHideki)
- [Andrei Oanca](https://github.com/OancaAndrei)

View file

@ -166,6 +166,8 @@
"src/components/playmenu.js",
"src/components/pluginManager.js",
"src/components/prompt/prompt.js",
"src/components/qualityOptions.js",
"src/components/quickConnectSettings/quickConnectSettings.js",
"src/components/recordingcreator/recordingbutton.js",
"src/components/recordingcreator/recordingcreator.js",
"src/components/recordingcreator/seriesrecordingeditor.js",
@ -173,7 +175,6 @@
"src/components/refreshdialog/refreshdialog.js",
"src/components/recordingcreator/recordingeditor.js",
"src/components/recordingcreator/recordingfields.js",
"src/components/qualityOptions.js",
"src/components/remotecontrol/remotecontrol.js",
"src/components/sanatizefilename.js",
"src/components/scrollManager.js",
@ -244,6 +245,7 @@
"src/controllers/dashboard/plugins/installed/index.js",
"src/controllers/dashboard/plugins/available/index.js",
"src/controllers/dashboard/plugins/repositories/index.js",
"src/controllers/dashboard/quickconnect.js",
"src/controllers/dashboard/scheduledtasks/scheduledtask.js",
"src/controllers/dashboard/scheduledtasks/scheduledtasks.js",
"src/controllers/dashboard/serveractivity.js",
@ -292,6 +294,7 @@
"src/controllers/user/menu/index.js",
"src/controllers/user/playback/index.js",
"src/controllers/user/profile/index.js",
"src/controllers/user/quickConnect/index.js",
"src/controllers/user/subtitles/index.js",
"src/controllers/wizard/finish/index.js",
"src/controllers/wizard/remote/index.js",

View file

@ -0,0 +1,41 @@
import globalize from 'globalize';
import toast from 'toast';
export class QuickConnectSettings {
constructor() { }
authorize(code) {
let url = ApiClient.getUrl('/QuickConnect/Authorize?Code=' + code);
ApiClient.ajax({
type: 'POST',
url: url
}, true).then(() => {
toast(globalize.translate('QuickConnectAuthorizeSuccess'));
}).catch(() => {
toast(globalize.translate('QuickConnectAuthorizeFail'));
});
// prevent bubbling
return false;
}
activate() {
let url = ApiClient.getUrl('/QuickConnect/Activate');
return ApiClient.ajax({
type: 'POST',
url: url
}).then(() => {
toast(globalize.translate('QuickConnectActivationSuccessful'));
return true;
}).catch((e) => {
console.error('Error activating quick connect. Error:', e);
Dashboard.alert({
title: globalize.translate('HeaderError'),
message: globalize.translate('DefaultErrorMessage')
});
throw e;
});
}
}
export default QuickConnectSettings;

View file

@ -0,0 +1,58 @@
import loading from 'loading';
import toast from 'toast';
import globalize from 'globalize';
const unavailable = 'Unavailable';
const available = 'Available';
const active = 'Active';
let page;
export default function(view) {
view.addEventListener('viewshow', function () {
page = this;
loading.show();
page.querySelector('#btnQuickConnectSubmit').onclick = onSubmit;
updatePage();
});
}
function loadPage(status) {
let check = status === available || status === active;
page.querySelector('#quickConnectStatus').textContent = status.toLocaleLowerCase();
page.querySelector('#chkQuickConnectAvailable').checked = check;
loading.hide();
}
function onSubmit() {
loading.show();
let newStatus = page.querySelector('#chkQuickConnectAvailable').checked ? available : unavailable;
let url = ApiClient.getUrl('/QuickConnect/Available?Status=' + newStatus);
ApiClient.ajax({
type: 'POST',
url: url
}, true).then(() => {
toast(globalize.translate('SettingsSaved'));
setTimeout(updatePage, 500);
return true;
}).catch((e) => {
console.error('Unable to set quick connect status. error:', e);
});
loading.hide();
return false;
}
function updatePage() {
ApiClient.getQuickConnect('Status').then((response) => {
loadPage(response);
return true;
}).catch((e) => {
console.error('Unable to get quick connect status. error:', e);
});
}

View file

@ -43,6 +43,10 @@
<span>${ButtonManualLogin}</span>
</button>
<button is="emby-button" type="button" class="raised cancel block btnQuick">
<span>${ButtonUseQuickConnect}</span>
</button>
<button is="emby-button" type="button" class="raised cancel block btnForgotPassword">
<span>${ButtonForgotPassword}</span>
</button>

View file

@ -19,8 +19,7 @@ import 'emby-checkbox';
var user = result.User;
loading.hide();
Dashboard.onServerChanged(user.Id, result.AccessToken, apiClient);
Dashboard.navigate('home.html');
onLoginSuccessful(user.Id, result.AccessToken, apiClient);
}, function (response) {
page.querySelector('#txtManualName').value = '';
page.querySelector('#txtManualPassword').value = '';
@ -41,6 +40,60 @@ import 'emby-checkbox';
});
}
function authenticateQuickConnect(apiClient) {
let url = apiClient.getUrl('/QuickConnect/Initiate');
apiClient.getJSON(url).then(function (json) {
if (!json.Secret || !json.Code) {
console.error('Malformed quick connect response', json);
return false;
}
Dashboard.alert({
message: globalize.translate('QuickConnectAuthorizeCode', json.Code),
title: globalize.translate('QuickConnect')
});
let connectUrl = apiClient.getUrl('/QuickConnect/Connect?Secret=' + json.Secret);
let interval = setInterval(function() {
apiClient.getJSON(connectUrl).then(async function(data) {
if (!data.Authenticated) {
return;
}
clearInterval(interval);
let result = await apiClient.quickConnect(data.Authentication);
onLoginSuccessful(result.User.Id, result.AccessToken, apiClient);
}, function (e) {
clearInterval(interval);
Dashboard.alert({
message: globalize.translate('QuickConnectDeactivated'),
title: globalize.translate('HeaderError')
});
console.error('Unable to login with quick connect', e);
});
}, 5000, connectUrl);
return true;
}, function(e) {
Dashboard.alert({
message: globalize.translate('QuickConnectNotActive'),
title: globalize.translate('HeaderError')
});
console.error('Quick connect error: ', e);
return false;
});
}
function onLoginSuccessful(id, accessToken, apiClient) {
Dashboard.onServerChanged(id, accessToken, apiClient);
Dashboard.navigate('home.html');
}
function showManualForm(context, showCancel, focusPassword) {
context.querySelector('.chkRememberLogin').checked = appSettings.enableAutoLogin();
context.querySelector('.manualLoginForm').classList.remove('hide');
@ -187,6 +240,11 @@ import 'emby-checkbox';
Dashboard.navigate('forgotpassword.html');
});
view.querySelector('.btnCancel').addEventListener('click', showVisualForm);
view.querySelector('.btnQuick').addEventListener('click', function () {
const apiClient = getApiClient();
authenticateQuickConnect(apiClient);
return false;
});
view.querySelector('.btnManual').addEventListener('click', function () {
view.querySelector('#txtManualName').value = '';
showManualForm(view, true);
@ -194,6 +252,7 @@ import 'emby-checkbox';
view.querySelector('.btnSelectServer').addEventListener('click', function () {
Dashboard.selectServer();
});
view.addEventListener('viewshow', function (e) {
loading.show();
libraryMenu.setTransparentMenu(true);

View file

@ -48,6 +48,16 @@
</div>
</a>
<a is="emby-linkbutton" data-ripple="false" href="#" style="display:block;padding:0;margin:0;" class="lnkQuickConnectPreferences listItem-border">
<div class="listItem">
<em class="material-icons listItemIcon listItemIcon-transparent">tap_and_play</em>
<div class="listItemBody">
<div class="listItemBodyText">${QuickConnect}</div>
</div>
</div>
</a>
<a is="emby-linkbutton" data-ripple="false" href="#" style="display:block;padding:0;margin:0;" class="clientSettings listItem-border">
<div class="listItem">
<span class="material-icons listItemIcon listItemIcon-transparent devices_other"></span>

View file

@ -26,6 +26,7 @@ export default function (view, params) {
page.querySelector('.lnkHomePreferences').setAttribute('href', 'mypreferenceshome.html?userId=' + userId);
page.querySelector('.lnkPlaybackPreferences').setAttribute('href', 'mypreferencesplayback.html?userId=' + userId);
page.querySelector('.lnkSubtitlePreferences').setAttribute('href', 'mypreferencessubtitles.html?userId=' + userId);
page.querySelector('.lnkQuickConnectPreferences').setAttribute('href', 'mypreferencesquickconnect.html');
if (window.NativeShell && window.NativeShell.AppHost.supports('clientsettings')) {
page.querySelector('.clientSettings').classList.remove('hide');

View file

@ -0,0 +1,17 @@
<div id="quickConnectPreferencesPage" data-role="page" class="page libraryPage userPreferencesPage noSecondaryNavPage" data-title="${QuickConnect}" data-backbutton="true" style="margin: 0 auto; max-width: 54em">
<button is="emby-button" id="btnQuickConnectActivate" type="button" class="raised button-submit block">
<span>${ButtonActivate}</span>
</button>
<form class="quickConnectSettingsContainer">
<div style="margin-bottom: 1em">
${QuickConnectDescription}
</div>
<div class="inputContainer">
<input is="emby-input" type="number" min="0" max="999999" required id="txtQuickConnectCode" label="${LabelQuickConnectCode}" autocomplete="off" />
</div>
<button id="btnQuickConnectAuthorize" is="emby-button" type="submit" class="raised button-submit block">
<span>${Authorize}</span>
</button>
</form>
</div>

View file

@ -0,0 +1,61 @@
import QuickConnectSettings from 'quickConnectSettings';
import globalize from 'globalize';
import toast from 'toast';
export default function (view) {
let quickConnectSettingsInstance = null;
view.addEventListener('viewshow', function () {
let codeElement = view.querySelector('#txtQuickConnectCode');
quickConnectSettingsInstance = new QuickConnectSettings();
view.querySelector('#btnQuickConnectActivate').addEventListener('click', () => {
quickConnectSettingsInstance.activate(quickConnectSettingsInstance).then(() => {
renderPage();
});
});
view.querySelector('#btnQuickConnectAuthorize').addEventListener('click', () => {
if (!codeElement.validity.valid) {
toast(globalize.translate('QuickConnectInvalidCode'));
return;
}
let code = codeElement.value;
quickConnectSettingsInstance.authorize(code);
});
view.querySelector('.quickConnectSettingsContainer').addEventListener('submit', (e) => {
e.preventDefault();
});
renderPage();
});
function renderPage(forceActive = false) {
ApiClient.getQuickConnect('Status').then((status) => {
let btn = view.querySelector('#btnQuickConnectActivate');
let container = view.querySelector('.quickConnectSettingsContainer');
// The activation button should only be visible when quick connect is unavailable (with the text replaced with an error) or when it is available (so it can be activated)
// The authorization container is only usable when quick connect is active, so it should be hidden otherwise
container.style.display = 'none';
if (status === 'Unavailable') {
btn.textContent = globalize.translate('QuickConnectNotAvailable');
btn.disabled = true;
btn.classList.remove('button-submit');
btn.classList.add('button');
} else if (status === 'Active' || forceActive) {
container.style.display = '';
btn.style.display = 'none';
}
return true;
}).catch((e) => {
throw e;
});
}
}

24
src/quickconnect.html Normal file
View file

@ -0,0 +1,24 @@
<div id="quickConnectPage" data-role="page" class="page type-interior advancedConfigurationPage">
<div class="content-primary">
<form class="quickConnectSettings">
<div class="verticalSection">
<div class="sectionTitleContainer flex align-items-center">
<h2 class="sectionTitle">${QuickConnect}</h2>
</div>
</div>
<div>${LabelCurrentStatus}<span id="quickConnectStatus" style="padding:0 0.4em;"></span></div>
<div class="checkboxList paperList" style="padding:.5em 1em;">
<label>
<input type="checkbox" is="emby-checkbox" id="chkQuickConnectAvailable" />
<span>${EnableQuickConnect}</span>
</label>
</div>
<button is="emby-button" id="btnQuickConnectSubmit" type="submit" class="raised button-submit block">
<span>${Save}</span>
</button>
</form>
</div>
</div>

View file

@ -404,6 +404,12 @@ import 'flexStyles';
pageIds: ['devicesPage', 'devicePage'],
icon: 'devices'
});
links.push({
name: globalize.translate('QuickConnect'),
href: 'quickConnect.html',
pageIds: ['quickConnectPage'],
icon: 'tap_and_play'
});
links.push({
name: globalize.translate('HeaderActivity'),
href: 'serveractivity.html',

View file

@ -97,6 +97,13 @@ import 'detailtablecss';
controller: 'user/home/index'
});
defineRoute({
alias: '/mypreferencesquickconnect.html',
path: '/controllers/user/quickConnect/index.html',
autoFocus: false,
transition: 'fade',
controller: 'user/quickConnect/index'
});
defineRoute({
alias: '/mypreferencesplayback.html',
path: '/controllers/user/playback/index.html',
@ -151,6 +158,13 @@ import 'detailtablecss';
controller: 'dashboard/devices/device'
});
defineRoute({
path: '/quickconnect.html',
autoFocus: false,
roles: 'admin',
controller: 'dashboard/quickconnect'
});
defineRoute({
alias: '/dlnaprofile.html',
path: '/controllers/dashboard/dlna/profile.html',

View file

@ -625,6 +625,7 @@ function initClient() {
define('displaySettings', [componentsPath + '/displaySettings/displaySettings'], returnFirstDependency);
define('playbackSettings', [componentsPath + '/playbackSettings/playbackSettings'], returnFirstDependency);
define('homescreenSettings', [componentsPath + '/homeScreenSettings/homeScreenSettings'], returnFirstDependency);
define('quickConnectSettings', [componentsPath + '/quickConnectSettings/quickConnectSettings'], returnFirstDependency);
define('playbackManager', [componentsPath + '/playback/playbackmanager'], getPlaybackManager);
define('timeSyncManager', [componentsPath + '/syncPlay/timeSyncManager'], returnDefault);
define('groupSelectionMenu', [componentsPath + '/syncPlay/groupSelectionMenu'], returnFirstDependency);

View file

@ -45,6 +45,7 @@
"Audio": "Audio",
"AuthProviderHelp": "Select an authentication provider to be used to authenticate this user's password.",
"Auto": "Auto",
"Authorize": "Authorize",
"Backdrop": "Backdrop",
"Backdrops": "Backdrops",
"Banner": "Banner",
@ -60,6 +61,7 @@
"Browse": "Browse",
"MessageBrowsePluginCatalog": "Browse our plugin catalog to view available plugins.",
"BurnSubtitlesHelp": "Determines if the server should burn in subtitles when transcoding videos. Avoiding this will greatly improve performance. Select Auto to burn image based formats (VOBSUB, PGS, SUB, IDX, …) and certain ASS or SSA subtitles.",
"ButtonActivate": "Activate",
"ButtonAddImage": "Add Image",
"ButtonAddMediaLibrary": "Add Media Library",
"ButtonAddScheduledTaskTrigger": "Add Trigger",
@ -107,6 +109,7 @@
"ButtonTogglePlaylist": "Playlist",
"ButtonTrailer": "Trailer",
"ButtonUninstall": "Uninstall",
"ButtonUseQuickConnect": "Use Quick Connect",
"ButtonWebsite": "Website",
"CancelRecording": "Cancel recording",
"CancelSeries": "Cancel series",
@ -196,6 +199,7 @@
"EnableNextVideoInfoOverlayHelp": "At the end of a video, display info about the next video coming up in the current playlist.",
"EnablePhotos": "Display photos",
"EnablePhotosHelp": "Images will be detected and displayed alongside other media files.",
"EnableQuickConnect": "Enable quick connect on this server",
"EnableStreamLooping": "Auto-loop live streams",
"EnableStreamLoopingHelp": "Enable this if live streams only contain a few seconds of data and need to be continuously requested. Enabling this when not needed may cause problems.",
"EnableThemeSongsHelp": "Play theme songs in the background while browsing the library.",
@ -507,6 +511,7 @@
"LabelCountry": "Country:",
"LabelCriticRating": "Critic rating:",
"LabelCurrentPassword": "Current password:",
"LabelCurrentStatus": "Current status:",
"LabelCustomCertificatePath": "Custom SSL certificate path:",
"LabelCustomCertificatePathHelp": "Path to a PKCS #12 file containing a certificate and private key to enable TLS support on a custom domain.",
"LabelCustomCss": "Custom CSS:",
@ -713,6 +718,7 @@
"LabelPublicHttpPortHelp": "The public port number that should be mapped to the local HTTP port.",
"LabelPublicHttpsPort": "Public HTTPS port number:",
"LabelPublicHttpsPortHelp": "The public port number that should be mapped to the local HTTPS port.",
"LabelQuickConnectCode": "Quick connect code:",
"LabelReasonForTranscoding": "Reason for transcoding:",
"LabelRecord": "Record:",
"LabelRecordingPath": "Default recording path:",
@ -1147,6 +1153,16 @@
"Profile": "Profile",
"Programs": "Programs",
"Quality": "Quality",
"QuickConnect": "Quick Connect",
"QuickConnectActivationSuccessful": "Successfully activated",
"QuickConnectAuthorizeCode": "Enter code {0} to login",
"QuickConnectAuthorizeSuccess": "Request authorized",
"QuickConnectAuthorizeFail": "Unknown quick connect code",
"QuickConnectDeactivated": "Quick connect was deactivated before the login request could be approved",
"QuickConnectDescription": "To sign in with quick connect, select the Quick Connect button on the device you are logging in from and enter the displayed code below.",
"QuickConnectInvalidCode": "Invalid quick connect code",
"QuickConnectNotAvailable": "Ask your server administrator to enable quick connect",
"QuickConnectNotActive": "Quick connect is not active on this server",
"Raised": "Raised",
"Rate": "Rate",
"RecentlyWatched": "Recently watched",