diff --git a/package.json b/package.json index a38ff8a3..d3365320 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,9 @@ "version": "1.6.0", "description": "API client for Jellyfin", "main": "dist/jellyfin-apiclient.js", - "dependencies": {}, + "dependencies": { + "js-cookie": "^2.2.1" + }, "devDependencies": { "@babel/core": "^7.9.6", "@babel/preset-env": "^7.9.6", diff --git a/src/apiClient.js b/src/apiClient.js index c3aee544..05c2631b 100644 --- a/src/apiClient.js +++ b/src/apiClient.js @@ -497,8 +497,9 @@ class ApiClient { * Authenticates a user * @param {String} name * @param {String} password + * @param {String} rememberMe */ - authenticateUserByName(name, password) { + authenticateUserByName(name, password, rememberMe) { if (!name) { return Promise.reject(); } @@ -525,6 +526,7 @@ class ApiClient { }; if (this.onAuthenticated) { + result.enableAutoLogin = rememberMe; this.onAuthenticated(this, result).then(afterOnAuthenticated); } else { afterOnAuthenticated(); @@ -537,8 +539,9 @@ class ApiClient { /** * Authenticates a user using quick connect * @param {String} token + * @param {String} rememberMe */ - quickConnect(token) { + quickConnect(token, rememberMe) { if (!token) { return Promise.reject(); } @@ -564,6 +567,7 @@ class ApiClient { }; if (this.onAuthenticated) { + result.enableAutoLogin = rememberMe; this.onAuthenticated(this, result).then(afterOnAuthenticated); } else { afterOnAuthenticated(); diff --git a/src/connectionManager.js b/src/connectionManager.js index 907a931e..002e12a0 100644 --- a/src/connectionManager.js +++ b/src/connectionManager.js @@ -261,7 +261,7 @@ export default class ConnectionManager { apiClient.serverInfo(existingServer); - apiClient.onAuthenticated = (instance, result) => onAuthenticated(instance, result, {}, true); + apiClient.onAuthenticated = (instance, result) => onAuthenticated(instance, result, {enableAutoLogin: result.enableAutoLogin}, true); if (!existingServers.length) { const credentials = credentialProvider.credentials(); @@ -291,7 +291,7 @@ export default class ConnectionManager { apiClient.serverInfo(server); apiClient.onAuthenticated = (instance, result) => { - return onAuthenticated(instance, result, {}, true); + return onAuthenticated(instance, result, {enableAutoLogin: result.enableAutoLogin}, true); }; events.trigger(self, 'apiclientcreated', [apiClient]); @@ -333,6 +333,9 @@ export default class ConnectionManager { server.AccessToken = null; } + // set to true to store authentication data + server.EnableAutoLogin = !!options.enableAutoLogin; + credentialProvider.addOrUpdateServer(credentials.Servers, server); credentialProvider.credentials(credentials); @@ -460,6 +463,8 @@ export default class ConnectionManager { server.AccessToken = null; server.ExchangeToken = null; } + + credentialProvider.credentials(credentials); }); }; diff --git a/src/credentials.js b/src/credentials.js index 3d1b81da..bbaf87d6 100644 --- a/src/credentials.js +++ b/src/credentials.js @@ -1,5 +1,37 @@ import events from './events'; import appStorage from './appStorage'; +import cookies from 'js-cookie'; + +function readSession(key, server) { + const value = cookies.get(`${key}-${server.Id}`); + + if (value) { + try { + const { UserId, AccessToken } = JSON.parse(value); + server.UserId = UserId || server.UserId; + server.AccessToken = AccessToken || server.AccessToken; + } catch (e) { + console.error(e); + } + } +} + +function saveSession(key, server) { + cookies.set(`${key}-${server.Id}`, JSON.stringify({ + UserId: server.UserId, + AccessToken: server.AccessToken + })); +} + +function removeSession(key) { + const prefix = `${key}-`; + + for (const cookie in cookies.get()) { + if (cookie.startsWith(prefix)) { + cookies.remove(cookie); + } + } +} function initialize(appStorage, key) { const json = appStorage.getItem(key) || '{}'; @@ -7,13 +39,38 @@ function initialize(appStorage, key) { console.log(`Stored JSON credentials: ${json}`); let credentials = JSON.parse(json); credentials.Servers = credentials.Servers || []; + + for (const server of credentials.Servers) { + readSession(key, server); + } + return credentials; } function set(instance, data) { if (data) { instance._credentials = data; - instance.appStorage.setItem(instance.key, JSON.stringify(data)); + + const dataCopy = JSON.parse(JSON.stringify(data)); + + // Remove session data so we don't leave removed servers + removeSession(instance.key); + + for (const server of dataCopy.Servers || []) { + if (server.UserId && server.AccessToken) { + saveSession(instance.key, server); + } else { + delete server.EnableAutoLogin; + } + + if (!server.EnableAutoLogin) { + delete server.UserId; + delete server.AccessToken; + delete server.EnableAutoLogin; + } + } + + instance.appStorage.setItem(instance.key, JSON.stringify(dataCopy)); } else { instance.clear(); } @@ -29,6 +86,7 @@ export default class Credentials { } clear() { + removeSession(this.key); this._credentials = null; this.appStorage.removeItem(this.key); } @@ -57,6 +115,7 @@ export default class Credentials { if (server.AccessToken) { existing.AccessToken = server.AccessToken; existing.UserId = server.UserId; + existing.EnableAutoLogin = server.EnableAutoLogin; } if (server.ExchangeToken) { existing.ExchangeToken = server.ExchangeToken; diff --git a/yarn.lock b/yarn.lock index a1690bb9..0c86f6f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3975,6 +3975,11 @@ jest@^26.0.1: import-local "^3.0.2" jest-cli "^26.6.3" +js-cookie@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" + integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ== + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"