/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // chat/content/imAccountOptionsHelper.js /* globals accountOptionsHelper */ var { IMServices } = ChromeUtils.importESModule( "resource:///modules/IMServices.sys.mjs" ); var { MailServices } = ChromeUtils.import( "resource:///modules/MailServices.jsm" ); var { ChatIcons } = ChromeUtils.importESModule( "resource:///modules/chatIcons.sys.mjs" ); var PREF_EXTENSIONS_GETMOREPROTOCOLSURL = "extensions.getMoreProtocolsURL"; var accountWizard = { onload() { document .querySelector("wizard") .addEventListener("wizardfinish", this.createAccount.bind(this)); let accountProtocolPage = document.getElementById("accountprotocol"); accountProtocolPage.addEventListener( "pageadvanced", this.selectProtocol.bind(this) ); let accountUsernamePage = document.getElementById("accountusername"); accountUsernamePage.addEventListener( "pageshow", this.showUsernamePage.bind(this) ); accountUsernamePage.addEventListener( "pagehide", this.hideUsernamePage.bind(this) ); let accountAdvancedPage = document.getElementById("accountadvanced"); accountAdvancedPage.addEventListener( "pageshow", this.showAdvanced.bind(this) ); let accountSummaryPage = document.getElementById("accountsummary"); accountSummaryPage.addEventListener( "pageshow", this.showSummary.bind(this) ); // Ensure the im core is initialized before we get a list of protocols. IMServices.core.init(); accountWizard.setGetMoreProtocols(); var protoList = document.getElementById("protolist"); var protos = IMServices.core.getProtocols(); protos.sort((a, b) => { if (a.name < b.name) { return -1; } return a.name > b.name ? 1 : 0; }); protos.forEach(function (proto) { let image = document.createElement("img"); image.setAttribute("src", ChatIcons.getProtocolIconURI(proto)); image.setAttribute("alt", ""); image.classList.add("protoIcon"); let label = document.createXULElement("label"); label.setAttribute("value", proto.name); let item = document.createXULElement("richlistitem"); item.setAttribute("value", proto.id); item.appendChild(image); item.appendChild(label); protoList.appendChild(item); }); // there is a strange selection bug without this timeout setTimeout(function () { protoList.selectedIndex = 0; }, 0); Services.obs.addObserver(this, "prpl-quit"); window.addEventListener("unload", this.unload); }, unload() { Services.obs.removeObserver(accountWizard, "prpl-quit"); }, observe(aObject, aTopic, aData) { if (aTopic == "prpl-quit") { // libpurple is being uninitialized. We can't create any new // account so keeping this wizard open would be pointless, close it. window.close(); } }, /** * Builds the full username from the username boxes. * * @returns {string} assembled username */ getUsername() { let usernameBoxIndex = 0; if (this.proto.usernamePrefix) { usernameBoxIndex = 1; } // If the first username input is empty, make sure we return an empty // string so that it blocks the 'next' button of the wizard. if (!this.userNameBoxes[usernameBoxIndex].value) { return ""; } return this.userNameBoxes.reduce((prev, elt) => prev + elt.value, ""); }, /** * Check that the username fields generate a new username, and if it is valid * allow advancing the wizard. */ checkUsername() { var wizard = document.querySelector("wizard"); var name = accountWizard.getUsername(); var duplicateWarning = document.getElementById("duplicateAccount"); if (!name) { wizard.canAdvance = false; duplicateWarning.hidden = true; return; } var exists = accountWizard.proto.accountExists(name); wizard.canAdvance = !exists; duplicateWarning.hidden = !exists; }, /** * Takes the value of the primary username field and splits it if the value * matches the split field syntax. */ splitUsername() { let usernameBoxIndex = 0; if (this.proto.usernamePrefix) { usernameBoxIndex = 1; } let username = this.userNameBoxes[usernameBoxIndex].value; let splitValues = this.proto.splitUsername(username); if (!splitValues.length) { return; } for (const box of this.userNameBoxes) { if (Element.isInstance(box)) { box.value = splitValues.shift(); } } this.checkUsername(); }, selectProtocol() { var protoList = document.getElementById("protolist"); var id = protoList.selectedItem.value; this.proto = IMServices.core.getProtocolById(id); }, /** * Create a new input field for receiving a username. * * @param {string} aName - The id for the input. * @param {string} aLabel - The text for the username label. * @param {Element} grid - A container with a two column grid display to * append the new elements to. * @param {string} [aDefaultValue] - The initial value for the username. * * @returns {HTMLInputElement} - The newly created username input. */ insertUsernameField(aName, aLabel, grid, aDefaultValue) { var label = document.createXULElement("label"); label.setAttribute("value", aLabel); label.setAttribute("control", aName); label.setAttribute("id", aName + "-label"); label.classList.add("label-inline"); grid.appendChild(label); var input = document.createElementNS( "http://www.w3.org/1999/xhtml", "input" ); input.setAttribute("id", aName); input.classList.add("input-inline"); if (aDefaultValue) { input.setAttribute("value", aDefaultValue); } input.addEventListener("input", event => { this.checkUsername(); }); // Only add the split logic to the first input field if (!this.userNameBoxes) { input.addEventListener("blur", event => { this.splitUsername(); }); } grid.appendChild(input); return input; }, /** * Builds the username input boxes from the username split defined by the * protocol. */ showUsernamePage() { var proto = this.proto.id; if ("userNameBoxes" in this && this.userNameProto == proto) { this.checkUsername(); return; } var bundle = document.getElementById("accountsBundle"); var usernameInfo; var emptyText = this.proto.usernameEmptyText; if (emptyText) { usernameInfo = bundle.getFormattedString( "accountUsernameInfoWithDescription", [emptyText, this.proto.name] ); } else { usernameInfo = bundle.getFormattedString("accountUsernameInfo", [ this.proto.name, ]); } document.getElementById("usernameInfo").textContent = usernameInfo; var grid = document.getElementById("userNameBox"); // remove anything that may be there for another protocol while (grid.hasChildNodes()) { grid.lastChild.remove(); } this.userNameBoxes = undefined; var splits = this.proto.getUsernameSplit(); var label = bundle.getString("accountUsername"); this.userNameBoxes = [this.insertUsernameField("name", label, grid)]; this.userNameBoxes[0].emptyText = emptyText; let usernameBoxIndex = 0; if (this.proto.usernamePrefix) { this.userNameBoxes.unshift({ value: this.proto.usernamePrefix }); usernameBoxIndex = 1; } for (let i = 0; i < splits.length; ++i) { this.userNameBoxes.push({ value: splits[i].separator }); label = bundle.getFormattedString("accountColon", [splits[i].label]); let defaultVal = splits[i].defaultValue; this.userNameBoxes.push( this.insertUsernameField("username-split-" + i, label, grid, defaultVal) ); } this.userNameBoxes[usernameBoxIndex].focus(); this.userNameProto = proto; this.checkUsername(); }, hideUsernamePage() { document.querySelector("wizard").canAdvance = true; var next = "account" + (this.proto.noPassword ? "advanced" : "password"); document.getElementById("accountusername").next = next; }, showAdvanced() { // ensure we don't destroy user data if it's not necessary var id = this.proto.id; if ("protoSpecOptId" in this && this.protoSpecOptId == id) { return; } this.protoSpecOptId = id; this.populateProtoSpecificBox(); // Make sure the protocol specific options and wizard buttons are visible. let wizard = document.querySelector("wizard"); if (wizard.scrollHeight > window.innerHeight) { window.resizeBy(0, wizard.scrollHeight - window.innerHeight); } let alias = document.getElementById("alias"); alias.focus(); }, populateProtoSpecificBox() { let haveOptions = accountOptionsHelper.addOptions( this.proto.id + "-", this.proto.getOptions() ); document.getElementById("protoSpecificGroupbox").hidden = !haveOptions; if (haveOptions) { var bundle = document.getElementById("accountsBundle"); document.getElementById("protoSpecificCaption").textContent = bundle.getFormattedString("protoOptions", [this.proto.name]); } }, /** * Create new summary field and value elements. * * @param {string} aLabel - The name of the field being summarised. * @param {string} aValue - The value of the field being summarised. * @param {Element} grid - A container with a two column grid display to * append the new elements to. */ createSummaryRow(aLabel, aValue, grid) { var label = document.createXULElement("label"); label.classList.add("header", "label-inline"); if (aLabel.length > 20) { aLabel = aLabel.substring(0, 20); aLabel += "…"; } label.setAttribute("value", aLabel); grid.appendChild(label); var input = document.createElementNS( "http://www.w3.org/1999/xhtml", "input" ); input.setAttribute("value", aValue); input.classList.add("plain", "input-inline"); input.setAttribute("readonly", true); grid.appendChild(input); }, showSummary() { var rows = document.getElementById("summaryRows"); var bundle = document.getElementById("accountsBundle"); while (rows.hasChildNodes()) { rows.lastChild.remove(); } var label = document.getElementById("protoLabel").value; this.createSummaryRow(label, this.proto.name, rows); this.username = this.getUsername(); label = bundle.getString("accountUsername"); this.createSummaryRow(label, this.username, rows); if (!this.proto.noPassword) { this.password = this.getValue("password"); if (this.password) { label = document.getElementById("passwordLabel").value; var pass = ""; for (let i = 0; i < this.password.length; ++i) { pass += "*"; } this.createSummaryRow(label, pass, rows); } } this.alias = this.getValue("alias"); if (this.alias) { label = document.getElementById("aliasLabel").value; this.createSummaryRow(label, this.alias, rows); } var id = this.proto.id; this.prefs = []; for (let opt of this.proto.getOptions()) { let name = opt.name; let eltName = id + "-" + name; let val = this.getValue(eltName); // The value will be undefined if the proto specific groupbox has never been opened if (val === undefined) { continue; } switch (opt.type) { case Ci.prplIPref.typeBool: if (val != opt.getBool()) { this.prefs.push({ opt, name, value: !!val }); } break; case Ci.prplIPref.typeInt: if (val != opt.getInt()) { this.prefs.push({ opt, name, value: val }); } break; case Ci.prplIPref.typeString: if (val != opt.getString()) { this.prefs.push({ opt, name, value: val }); } break; case Ci.prplIPref.typeList: if (val != opt.getListDefault()) { this.prefs.push({ opt, name, value: val }); } break; default: throw new Error("unknown preference type " + opt.type); } } for (let i = 0; i < this.prefs.length; ++i) { let opt = this.prefs[i]; let label = bundle.getFormattedString("accountColon", [opt.opt.label]); this.createSummaryRow(label, opt.value, rows); } }, createAccount() { var acc = IMServices.accounts.createAccount(this.username, this.proto.id); if (!this.proto.noPassword && this.password) { acc.password = this.password; } if (this.alias) { acc.alias = this.alias; } for (let i = 0; i < this.prefs.length; ++i) { let option = this.prefs[i]; let opt = option.opt; switch (opt.type) { case Ci.prplIPref.typeBool: acc.setBool(option.name, option.value); break; case Ci.prplIPref.typeInt: acc.setInt(option.name, option.value); break; case Ci.prplIPref.typeString: case Ci.prplIPref.typeList: acc.setString(option.name, option.value); break; default: throw new Error("unknown type"); } } var autologin = this.getValue("connectNow"); acc.autoLogin = autologin; acc.save(); try { if (autologin) { acc.connect(); } } catch (e) { // If the connection fails (for example if we are currently in // offline mode), we still want to close the account wizard } if (window.opener) { var am = window.opener.gAccountManager; if (am) { am.selectAccount(acc.id); } } var inServer = MailServices.accounts.createIncomingServer( this.username, this.proto.id, // hostname "im" ); inServer.wrappedJSObject.imAccount = acc; var account = MailServices.accounts.createAccount(); // Avoid new folder notifications. inServer.valid = false; account.incomingServer = inServer; inServer.valid = true; MailServices.accounts.notifyServerLoaded(inServer); return true; }, getValue(aId) { var elt = document.getElementById(aId); if ("selectedItem" in elt) { return elt.selectedItem.value; } // Strangely various input types also have a "checked" property defined, // so we check for the expected elements explicitly. if ( ((elt.localName == "input" && elt.getAttribute("type") == "checkbox") || elt.localName == "checkbox") && "checked" in elt ) { return elt.checked; } if ("value" in elt) { return elt.value; } // If the groupbox has never been opened, the binding isn't attached // so the attributes don't exist. The calling code in showSummary // has a special handling of the undefined value for this case. return undefined; }, *getIter(aEnumerator) { for (let iter of aEnumerator) { yield iter; } }, /* Check for correctness and set URL for the "Get more protocols..."-link * Stripped down code from preferences/themes.js */ setGetMoreProtocols() { let prefURL = PREF_EXTENSIONS_GETMOREPROTOCOLSURL; var getMore = document.getElementById("getMoreProtocols"); var showGetMore = false; const nsIPrefBranch = Ci.nsIPrefBranch; if (Services.prefs.getPrefType(prefURL) != nsIPrefBranch.PREF_INVALID) { try { var getMoreURL = Services.urlFormatter.formatURLPref(prefURL); getMore.setAttribute("getMoreURL", getMoreURL); showGetMore = getMoreURL != "about:blank"; } catch (e) {} } getMore.hidden = !showGetMore; }, openURL(aURL) { Cc["@mozilla.org/uriloader/external-protocol-service;1"] .getService(Ci.nsIExternalProtocolService) .loadURI(Services.io.newURI(aURL)); }, }; window.addEventListener("load", event => { accountWizard.onload(); });