diff --git a/src/LightDMMock.js b/src/LightDMMock.js index 1df0b84..84790f4 100644 --- a/src/LightDMMock.js +++ b/src/LightDMMock.js @@ -1,156 +1,344 @@ /** - * LightDMMock "class" + * @author Elias Schriefer + * @alias lightdm + * @classdesc + * A LightDM Mock that is written in modern JavaScript and based on + * [cytodev](https://github.com/cytodev)'s + * [LightDMMock](https://github.com/cytodev/LightDMMock), the lightdm-webkit2-greeter + * [manual](https://man.archlinux.org/man/community/lightdm-webkit2-greeter/lightdm-webkit2-greeter.1.en), + * the [LightDM API documentation](https://people.ubuntu.com/~robert-ancell/lightdm/reference/), + * and the latest version of [Antergos](https://github.com/Antergos)' + * lightdm-webkit2-greeter [source code](https://github.com/Antergos/web-greeter). + * Please note that the deprecation errors are intrusive for a reason. + * + * This is a rewrite in modern JavaScript. * - * @author Roel Walraven + * ##### Usage: + * 1. Set the type of your JavaScript to `module` in your theme that needs mocking + * ```html + * + * ``` + * ```html + * + * ``` + * + * 2. Import {@link LightDMMock} and create a new instance + * ```javascript + * import LightDMMock from "LightDMMock/src/LightDMMock.js"; * - * A LightDM Mock that is tightly based on the source C code of - * Antergos' lightdm-webkit2-greeter. Please note that the deprecation errors - * are intrusive for a reason. - * - * Usage: - * Include the file in your theme that needs mocking - * - * Create a new instance of LightDMMock - * if(!("lightdm" in window)) { - * var LightDMMock = LightDMMock || {}; - * window.lightdm = new LightDMMock(autofill, timeout, autoGuest); - * } - * If you want to use the .face images don't forget to add the path to - * LightDMMock/src to the image src. The users.json file has absolute paths - * like you would expect on a real filesystem. - * - * @param {boolean} autofill [wether or not the arrays for users, languages, - * layouts, and sessions need to be filled with mock - * data. I advise to test both to make your theme - * less prone to crashing.] - * @param {number} timeout [Value to use for simulated autologin (this value - * is in seconds).] - * @param {boolean} autoGuest [Wether or not to simulate automatic guest login. - * This will also enable a guest account - * in lightdm.has_guest_account] + * window.lightdm = new LightDMMock(autofill, timeout, autoGuest); + * ``` + * + * If you want to use the `.face` images don't forget to add the path to + * `LightDMMock/src` to the image src. The `users.json` file has absolute paths + * like you would expect on a real filesystem. */ -"use strict"; - -function LightDMMock(autofill, timeout, autoGuest) { - window.checkForUpdate("v1.1.0"); - +class LightDMMock { // see + // and - this.authentication_user = null; - this.autologin_guest = false; - this.autologin_timeout = 0; - this.autologin_user = null; - this.can_hibernate = false; - this.can_restart = false; - this.can_shutdown = false; - this.can_suspend = false; - this.default_session = null; - this.has_guest_account = false; - this.hide_users = false; - this.hostname = null; - this.in_authentication = false; - this.is_authenticated = false; - this.language = null; - this.languages = null; - this.layout = null; - this.layouts = null; - this.lock_hint = false; - this.num_users = 0; - this.select_guest_hint = null; - this.select_user_hint = null; - this.sessions = null; - this.users = null; - this.default_language = null; // Deprecated - this.default_layout = null; // Deprecated - this.select_guest = null; // Deprecated - this.select_user = null; // Deprecated - this.timed_login_delay = null; // Deprecated - this.timed_login_user = null; // Deprecated + /** + * @type {?String} + * @desc + * The username of the authentication user being authenticated + * or null if no authentication is in progress. + */ + authentication_user = null; - if(typeof autofill === "boolean" && autofill) { - var me = document.querySelector("script[src$=\"LightDMMock.js\"]"); + /** + * @type {boolean} + * @desc + * Indicates the guest user should be used for autologin. + */ + autologin_guest = false; - if(!(me instanceof HTMLElement)) - return window.console.error("Could not find my script element."); + /** + * @type {number} + * @desc + * The number of seconds to wait before automatically logging + * in. The older variable {@link lightdm.timed_user_delay} has + * been deprecated. + */ + autologin_timeout = 0; - var includePath = me.src; + /** + * @type {?String} + * @desc + * The name of the user account that should be logged into + * automatically after timed login delay has passed. The older + * variable {@link lightdm.timed_login_user} has been deprecated. + */ + autologin_user = null; - if(includePath === undefined) - return window.console.error("Could not find my src attribute."); + /** + * @type {boolean} + * @desc + * Whether or not the system can be made to hibernate by the + * greeter. + */ + can_hibernate = false; - includePath = includePath.substr(0, includePath.lastIndexOf("/")); + /** + * @type {boolean} + * @desc + * Whether or not the system can be restarted by the greeter. + */ + can_restart = false; - var asyncLoadEnd = function(that) { - that.default_session = that.sessions[0].name; - that.language = that.languages[0].name; - that.layout = that.layouts[0].name; - that.num_users = that.users.length; + /** + * @type {boolean} + * @desc + * Whether or not the system can be shutdown by the greeter. + */ + can_shutdown = false; - if(typeof timeout === "number" && timeout > 0) { - if(typeof autoGuest === "boolean" && autoGuest) { - that.autologin_user = null; - that.autologin_guest = autoGuest; - } + /** + * @type {boolean} + * @desc + * Whether or not the system can be suspended by the greeter. + */ + can_suspend = false; - that.autologin_user = that.users[0].username; - that.autologin_timeout = timeout * 1000; + /** + * @deprecated + */ + default_language = null; - // @fixme: am I deprecated as well? - setTimeout(function() { - if((typeof autoGuest === "boolean" && autoGuest) || that.autologin_user !== null) - window.autologin_timer_expired(); - }.bind(that), that.timed_login_delay); + /** + * @deprecated + */ + default_layout = null; + + /** + * @type {?String} + * @desc + * The name of the default session (as configured in + * `lightdm.conf`). + */ + default_session = null; + + /** + * @type {boolean} + * @desc + * A guest account is available for login. + */ + has_guest_account = false; + + /** + * @type {boolean} + * @desc + * The whole list of users should not be displayed. + */ + hide_users = false; + + /** + * @type {String} + * @desc + * The hostname of the system. + */ + hostname = null; + + /** + * @type {boolean} + * @desc + * Indicates if the user has successfully authenticated. + */ + is_authenticated = false; + + /** + * @type {boolean} + * @desc + * Indicates if lightdm is currently in the authentication + * phase. + */ + in_authentication = false; + + /** + * @type {?String} + * @desc + * The currently selected language. The older variable name + * {@link lightdm.default_language} is deprecated. + */ + language = null; + + /** + * @type {LightDMLanguage[]} + * @desc + * The languages that are available on the system. + */ + languages = null; + + /** + * @type {String} + * @desc + * The name of the currently active keyboard layout. To change + * the layout, assign a valid layout name to this variable. The older + * variable name {@link lightdm.default_layout} is deprecated. + */ + layout = null; + + /** + * @type {LightDMLayout[]} + * @desc + * The keyboard layouts that are available on the system. + */ + layouts = null; + + /** + * @type {boolean} + * @desc + * `true` if the greeter was triggered by locking the seat. + */ + lock_hint = false; + + /** + * @type {number} + * @desc + * The number of users able to log in. + */ + num_users = 0; + + /** + * @deprecated + * @type {boolean} + * @desc + * The guest user should be selected by default for login. + */ + select_guest = null; + + /** + * @type {boolean} + * @desc + * `true` if the guest account should be selected by default. + */ + select_guest_hint = false; + + /** + * @deprecated + * @type {?String} + * @desc + * The username that should be selected by default for login. + */ + select_user = null; + + /** + * @type {?String} + * @desc + * A username or `null` if no particular user should be selected. + */ + select_user_hint = null; + + /** + * @type {LightDMSession[]} + * @desc + * The sessions that are available on the system. + */ + sessions = null; + + /** + * @deprecated + */ + timed_login_delay = null; + + /** + * @deprecated + */ + timed_login_user = null; + + /** + * @type {LightDMUser[]} + * @desc + * The users that are able to log in. + */ + users = null; + + /** + * @param {boolean} [autofill=false] + * Whether or not the arrays for users, languages, layouts, and sessions need to be filled with mock + * data. I advise to test both to make your theme less prone to crashing. + * + * @param {number} [timeout=0] + * Value to use for simulated autologin (this value is in seconds). + * + * @param {boolean} [autoGuest=false] + * Whether or not to simulate automatic guest login. This will also enable a guest account in + * {@link LightDMMock.has_guest_account lightdm.has_guest_account} + */ + constructor(autofill = false, timeout = 0, autoGuest = false) { + checkForUpdate("v1.1.0"); + + if (typeof autofill == "boolean" && autofill) { + let includePath = import.meta.url; + + if (includePath == undefined) { + return console.error("Could not get module URL."); } - for(var i = 0; i <= that.users; i++) { - that.users[i].logged_in = Boolean(Math.floor(Math.random() * 2)); - that.users[i].session = that.sessions[Math.floor((Math.random() * that.sessions.length))].name; - } - }; + includePath = includePath.substr(0, includePath.lastIndexOf("/")); - // see - window.loadJSON(includePath + "/json/users.json", function(that) { - if(this.status !== 200) - return window.console.warn("users.json did not load correctly."); + const rejectionHandler = response => { + console.warn(`${response.url.substr(response.url.lastIndexOf("/") + 1)} did not load correctly.`); + }; - that.users = JSON.parse(this.responseText); + // see + const usersJSON = loadJSON(`${includePath}/json/users.json`); + usersJSON + .then(json => { + this.users = json; + }, rejectionHandler) - if(that.users !== null && that.languages !== null && that.layouts !== null && that.sessions !== null) - asyncLoadEnd(that); - }, this, asyncLoadEnd); + // see + const languagesJSON = loadJSON(`${includePath}/json/languages.json`); + languagesJSON + .then(json => { + this.languages = json; + }, rejectionHandler); - // see - window.loadJSON(includePath + "/json/languages.json", function(that) { - if(this.status !== 200) - return window.console.warn("languages.json did not load correctly."); + // see + const layoutsJSON = loadJSON(`${includePath}/json/layouts.json`); + layoutsJSON + .then(json => { + this.layouts = json; + }, rejectionHandler); - that.languages = JSON.parse(this.responseText); + // see + const sessionsJSON = loadJSON(`${includePath}/json/sessions.json`); + sessionsJSON + .then(json => { + this.sessions = json; + }, rejectionHandler); - if(that.users !== null && that.languages !== null && that.layouts !== null && that.sessions !== null) - asyncLoadEnd(that); - }, this, asyncLoadEnd); + Promise.all([usersJSON, languagesJSON, layoutsJSON, sessionsJSON]) + .then(() => { + this.default_session = this.sessions[0].name; + this.language = this.languages[0].name; + this.layout = this.layouts[0].name; + this.num_users = this.users.length; - // see - window.loadJSON(includePath + "/json/layouts.json", function(that) { - if(this.status !== 200) - return window.console.warn("layouts.json did not load correctly."); + if (typeof timeout == "number" && timeout > 0) { + if (typeof autoGuest == "boolean" && autoGuest) { + this.autologin_user = null; + this.autologin_guest = autoGuest; + } else { + this.autologin_user = this.users[0].username; + } - that.layouts = JSON.parse(this.responseText); + this.autologin_timeout = timeout * 1000; - if(that.users !== null && that.languages !== null && that.layouts !== null && that.sessions !== null) - asyncLoadEnd(that); - }, this, asyncLoadEnd); + setTimeout(() => { + if ((typeof autoGuest == "boolean" && autoGuest) || this.autologin_user != null) + window.autologin_timer_expired(); + }, this.autologin_timeout); + } - // see - window.loadJSON(includePath + "/json/sessions.json", function(that) { - if(this.status !== 200) - return window.console.warn("sessions.json did not load correctly."); - - that.sessions = JSON.parse(this.responseText); - - if(that.users !== null && that.languages !== null && that.layouts !== null && that.sessions !== null) - asyncLoadEnd(that); - }, this, asyncLoadEnd); + for (let user of this.users) { + user.logged_in = Boolean(Math.floor(Math.random() * 2)); + user.session = this.sessions[Math.floor((Math.random() * this.sessions.length))].name; + } + }); + } } } @@ -525,72 +713,110 @@ window.logCall = function(name, args) { }; /** - * global helper checkForUpdate - * compares curentVersion with the tag name of GitHub's latest release and - * prompts the user to download a new version if it is available. + * @desc + * Compares `curentVersion` with the tag name of Gitea's latest release and + * prompts the user to download a new version if it is available. * - * @param {String} currentVersion [the current tag version] + * @param {String} currentVersion + * the current tag version */ -window.checkForUpdate = function(currentVersion) { - var request = new XMLHttpRequest(); +export const checkForUpdate = async currentVersion => { + const errMessage = "Could not check for new version of LightDMMock. Please check for a new version manually by visiting https://git.sfs.ddnss.org/EliasSchriefer/LightDMMock/releases/latest"; + const response = await fetch("https://git.sfs.ddnss.org/api/v1/repos/EliasSchriefer/LightDMMock/releases"); - request.onreadystatechange = function() { - if(request.readyState === XMLHttpRequest.DONE) { - switch(request.status) { - case 200: - try { - var latest; + if (response.ok) { + try { + const request = (await request.json())?.[0]?.tag_name; - if(request.responseText !== undefined) - latest = JSON.parse(request.responseText)[0].tag_name; - - if(currentVersion !== latest) - window.console.warn("You are using an outdated version of LightDMMock. Please download the new version from https://git.sfs.ddnss.org/EliasSchriefer/LightDMMock/releases/tag/" + latest); - } catch(e) { - window.console.error(e.toString()); - window.console.warn("Could not check for new version of LightDMMock. Please check for a new version manually by visiting https://git.sfs.ddnss.org/EliasSchriefer/LightDMMock/releases/latest"); - } - break; - case 404: - window.console.warn("Could not check for new version of LightDMMock. Please check for a new version manually by visiting https://git.sfs.ddnss.org/EliasSchriefer/LightDMMock/releases/latest"); - break; - default: - break; + if (currentVersion != latest) { + console.warn(`You are using an outdated version of LightDMMock. Please download the new version from https://git.sfs.ddnss.org/EliasSchriefer/LightDMMock/releases/tag/${latest}`); } + } catch (err) { + console.error(err.toString()); + console.warn(errMessage); } - }; - - request.open("GET", "https://git.sfs.ddnss.org/api/v1/repos/EliasSchriefer/LightDMMock/releases", true); - request.send(); + } else if (response.status == 404) { + console.warn(errMessage); + } }; /** - * global helper loadJSON - * Loads JSON from a path. Removes the need to b64 encode them in this file. + * @desc + * Loads JSON from a path. Removes the need to b64 encode them in this file. * - * @param {String} url [path to JSON file] - * @param {Function} callback [callback function] + * @param {String} url + * path to JSON file + * + * @throws {Response} Response with status code other than 200 Ok */ -window.loadJSON = function(url, callback) { - var request = new XMLHttpRequest(); +export const loadJSON = async url => { + let response = await fetch(url); - var onSuccess = function() { - this.callback.apply(request, request.arguments); - }; - - var onFailure = function() { - window.console.error(this.statusText); - }; - - request.callback = callback; - request.arguments = Array.prototype.slice.call(arguments, 2); - request.onload = onSuccess; - request.onerror = onFailure; - - request.open("get", url, true); - request.send(null); + if (response.ok) { + return await response.json(); + } else { + console.error(response.statusText); + throw response; + } }; +/****************************************************************************** + * External global functions * + ******************************************************************************/ + +/** + * @namespace window + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/window} + */ + +/** + * @function show_prompt + * @memberof window + * @desc + * This will be called when LightDM needs to prompt the user for some + * reason, such as asking for a password. + * + * @param {String} text + * Will be the text of the prompt + * + * @param {String} type + * Will either be + * - "text" for a visible prompt, or + * - "password" for a prompt that the input should be hidden. + */ + +/** + * @function show_message + * @memberof window + * @desc + * This will be called when LightDM needs to display some info for the + * user. + * + * @param {String} text + * Will be the text of the message + * + * @param {String} type + * Will either be + * - "info" for an information message, or + * - "error" for an error message that LightDM has encountered. + */ + +/** + * @function authentication_complete + * @memberof window + * @desc + * This function is called by LightDM when authentication has + * completed. + */ + +/** + * @function autologin_timer_expired + * @memberof window + * @desc + * This function is called by LightDM when an autologin user's login + * timer has expired. The greeter should reset the authentication + * process. + */ /****************************************************************************** * Object.watch shim * @@ -684,9 +910,4 @@ LightDMMock.watch('timed_login_delay', function() { * Module loading * ******************************************************************************/ -/* jshint node : true */ - -if(typeof module !== "undefined" && module.exports) - module.exports = LightDMMock; - -/* jshint node : false */ +export default LightDMMock; \ No newline at end of file