diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/chat/modules/OTRUI.sys.mjs | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/chat/modules/OTRUI.sys.mjs')
-rw-r--r-- | comm/chat/modules/OTRUI.sys.mjs | 998 |
1 files changed, 998 insertions, 0 deletions
diff --git a/comm/chat/modules/OTRUI.sys.mjs b/comm/chat/modules/OTRUI.sys.mjs new file mode 100644 index 0000000000..fdf4771607 --- /dev/null +++ b/comm/chat/modules/OTRUI.sys.mjs @@ -0,0 +1,998 @@ +/* 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/. */ + +import { IMServices } from "resource:///modules/IMServices.sys.mjs"; +import { OTR } from "resource:///modules/OTR.sys.mjs"; +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; + +XPCOMUtils.defineLazyGetter( + lazy, + "l10n", + () => new Localization(["messenger/otr/otrUI.ftl"], true) +); + +function _str(id) { + return lazy.l10n.formatValueSync(id); +} + +function _strArgs(id, args) { + return lazy.l10n.formatValueSync(id, args); +} + +const OTR_ADD_FINGER_DIALOG_URL = + "chrome://chat/content/otr-add-fingerprint.xhtml"; + +const AUTH_STATUS_UNVERIFIED = "otr-auth-unverified"; +var authLabelMap; +var trustMap; + +function initStrings() { + authLabelMap = new Map([ + ["otr:auth-error", _str("auth-error")], + ["otr:auth-success", _str("auth-success")], + ["otr:auth-success-them", _str("auth-success-them")], + ["otr:auth-fail", _str("auth-fail")], + ["otr:auth-waiting", _str("auth-waiting")], + ]); + + let sl = _str("start-label"); + let al = _str("auth-label"); + let rfl = _str("refresh-label"); + let ral = _str("reauth-label"); + + trustMap = new Map([ + [ + OTR.trustState.TRUST_NOT_PRIVATE, + { + startLabel: sl, + authLabel: al, + disableStart: false, + disableEnd: true, + disableAuth: true, + class: "not-private", + }, + ], + [ + OTR.trustState.TRUST_UNVERIFIED, + { + startLabel: rfl, + authLabel: al, + disableStart: false, + disableEnd: false, + disableAuth: false, + class: "unverified", + }, + ], + [ + OTR.trustState.TRUST_PRIVATE, + { + startLabel: rfl, + authLabel: ral, + disableStart: false, + disableEnd: false, + disableAuth: false, + class: "private", + }, + ], + [ + OTR.trustState.TRUST_FINISHED, + { + startLabel: sl, + authLabel: al, + disableStart: false, + disableEnd: false, + disableAuth: true, + class: "finished", + }, + ], + ]); +} + +var windowRefs = new Map(); + +export var OTRUI = { + enabled: false, + stringsLoaded: false, + globalDoc: null, + visibleConv: null, + + debug: false, + logMsg(msg) { + if (!OTRUI.debug) { + return; + } + Services.console.logStringMessage(msg); + }, + + addMenuObserver() { + for (let win of Services.ww.getWindowEnumerator()) { + OTRUI.addMenus(win); + } + Services.obs.addObserver(OTRUI, "domwindowopened"); + }, + + removeMenuObserver() { + for (let win of Services.ww.getWindowEnumerator()) { + OTRUI.removeMenus(win); + } + Services.obs.removeObserver(OTRUI, "domwindowopened"); + }, + + addMenus(win) { + let doc = win.document; + // Account for unready windows + if (doc.readyState !== "complete") { + let listen = function () { + win.removeEventListener("load", listen); + OTRUI.addMenus(win); + }; + win.addEventListener("load", listen); + } + }, + + removeMenus(win) { + let doc = win.document; + OTRUI.removeBuddyContextMenu(doc); + }, + + addBuddyContextMenu(buddyContextMenu, doc, contact) { + if (!buddyContextMenu || !OTR.libLoaded) { + return; // Not the buddy list context menu + } + + let sep = doc.createXULElement("menuseparator"); + sep.setAttribute("id", "otrsep"); + let menuitem = doc.createXULElement("menuitem"); + menuitem.setAttribute("label", _str("buddycontextmenu-label")); + menuitem.setAttribute("id", "otrcont"); + menuitem.addEventListener("command", () => { + let args = OTRUI.contactWrapper(contact); + args.wrappedJSObject = args; + let features = "chrome,modal,centerscreen,resizable=no,minimizable=no"; + Services.ww.openWindow( + null, + OTR_ADD_FINGER_DIALOG_URL, + "", + features, + args + ); + }); + + buddyContextMenu.addEventListener("popupshowing", e => { + let target = e.target.triggerNode; + if (target.localName == "richlistitem") { + menuitem.hidden = false; + sep.hidden = false; + } else { + /* probably imconv */ + menuitem.hidden = true; + sep.hidden = true; + } + }); + + buddyContextMenu.appendChild(sep); + buddyContextMenu.appendChild(menuitem); + }, + + removeBuddyContextMenu(doc) { + let s = doc.getElementById("otrsep"); + if (s) { + s.remove(); + } + let p = doc.getElementById("otrcont"); + if (p) { + p.remove(); + } + }, + + loopKeyGenSuccess() { + ChromeUtils.idleDispatch(OTRUI.genNextMissingKey); + }, + + loopKeyGenFailure(param) { + ChromeUtils.idleDispatch(OTRUI.genNextMissingKey); + OTRUI.reportKeyGenFailure(param); + }, + + reportKeyGenFailure(param) { + throw new Error(_strArgs("otr-genkey-failed", { error: String(param) })); + }, + + accountsToGenKey: [], + + genNextMissingKey() { + if (OTRUI.accountsToGenKey.length == 0) { + return; + } + + let acc = OTRUI.accountsToGenKey.pop(); + let fp = OTR.privateKeyFingerprint(acc.name, acc.prot); + if (!fp) { + OTR.generatePrivateKey(acc.name, acc.prot).then( + OTRUI.loopKeyGenSuccess, + OTRUI.loopKeyGenFailure + ); + } else { + ChromeUtils.idleDispatch(OTRUI.genNextMissingKey); + } + }, + + genMissingKeys() { + for (let acc of IMServices.accounts.getAccounts()) { + OTRUI.accountsToGenKey.push({ + name: acc.normalizedName, + prot: acc.protocol.normalizedName, + }); + } + ChromeUtils.idleDispatch(OTRUI.genNextMissingKey); + }, + + async init() { + if (!OTRUI.stringsLoaded) { + // HACK: calling initStrings may fail the first time due to synchronous + // loading of the .ftl files. If we load the files and wait for a known + // value asynchronously, no such failure will happen. + // + // If the value "start-label" is removed, this will fail. + // + // Also, we can't reuse this Localization object elsewhere because it + // fails to load values synchronously (even after calling setIsSync). + await new Localization(["messenger/otr/otrUI.ftl"]).formatValue( + "start-label" + ); + + initStrings(); + OTRUI.stringsLoaded = true; + } + + this.debug = Services.prefs.getBoolPref("chat.otr.trace", false); + + OTR.init({}); + if (!OTR.libLoaded) { + return; + } + + this.enabled = true; + this.notificationbox = null; + + OTR.addObserver(OTRUI); + OTR.loadFiles() + .then(function () { + Services.obs.addObserver(OTR, "new-ui-conversation"); + Services.obs.addObserver(OTR, "conversation-update-type"); + // Disabled until #76 is resolved. + // Services.obs.addObserver(OTRUI, "contact-added", false); + Services.obs.addObserver(OTRUI, "account-added"); + // Services.obs.addObserver(OTRUI, "contact-signed-off", false); + Services.obs.addObserver(OTRUI, "conversation-loaded"); + Services.obs.addObserver(OTRUI, "conversation-closed"); + Services.obs.addObserver(OTRUI, "prpl-quit"); + + for (let conv of IMServices.conversations.getConversations()) { + OTRUI.initConv(conv); + } + OTRUI.addMenuObserver(); + + ChromeUtils.idleDispatch(OTRUI.genMissingKeys); + }) + .catch(function (err) { + // console.log("===> " + err + "\n"); + throw err; + }); + }, + + disconnect(aConv) { + if (aConv) { + return OTR.disconnect(aConv, true); + } + let allGood = true; + for (let conv of IMServices.conversations.getConversations()) { + if (conv.isChat) { + continue; + } + if (!OTR.disconnect(conv, true)) { + allGood = false; + } + } + return allGood; + }, + + openAuth(window, name, mode, uiConv, contactInfo) { + let otrAuth = this.globalDoc.querySelector(".otr-auth"); + otrAuth.disabled = true; + let win = window.openDialog( + "chrome://chat/content/otr-auth.xhtml", + "auth=" + name, + "centerscreen,resizable=no,minimizable=no", + mode, + uiConv, + contactInfo + ); + windowRefs.set(name, win); + window.addEventListener("beforeunload", function () { + otrAuth.disabled = false; + windowRefs.delete(name); + }); + }, + + closeAuth(context) { + let win = windowRefs.get(context.username); + if (win) { + win.close(); + } + }, + + /** + * Hide the encryption state container and any pending notifications. + * + * @param {Element} otrContainer + * @param {Context} [context] + */ + noOtrPossible(otrContainer, context) { + otrContainer.hidden = true; + + if (context) { + OTRUI.hideUserNotifications(context); + } else { + OTRUI.hideAllOTRNotifications(); + } + }, + + sendSystemAlert(uiConv, conv, bundleId) { + uiConv.systemMessage( + _strArgs(bundleId, { name: conv.normalizedName }), + false, + true + ); + }, + + setNotificationBox(notificationbox) { + this.globalBox = notificationbox; + }, + + /* + * These states are only relevant if OTR is the only encryption available for + * the conversation. Protocol provided encryption takes priority. + * possible states: + * tab isn't a 1:1, isChat == true + * then OTR isn't possible, hide the button + * tab is a 1:1, isChat == false + * no conversation active, uiConv cannot be found + * then OTR isn't possible YET, hide the button + * conversation active, uiConv found + * disconnected? + * could the other side come back? should we keep the button? + * set the state based on the OTR library state + */ + + /** + * Store a reference to the document, as well as the current conversation. + * + * @param {Element} aObject - conversation-browser instance (most importantly, has a _conv field) + */ + addButton(aObject) { + this.globalDoc = aObject.ownerDocument; + let _conv = aObject._conv; + OTRUI.visibleConv = _conv; + if ( + _conv.encryptionState === Ci.prplIConversation.ENCRYPTION_NOT_SUPPORTED + ) { + OTRUI.setMsgState(_conv, null, this.globalDoc, true); + } + }, + + /** + * Hide the encryption state information for the current conversation. + */ + hideOTRButton() { + if (!OTR.libLoaded) { + return; + } + if (!this.globalDoc) { + return; + } + OTRUI.visibleConv = null; + let otrContainer = this.globalDoc.querySelector(".encryption-container"); + OTRUI.noOtrPossible(otrContainer); + }, + + /** + * Sets the visible conversation of the OTR UI state and ensures + * the encryption state button is set up correctly. + * + * @param {prplIConversation} _conv + */ + updateOTRButton(_conv) { + if ( + _conv.encryptionState !== Ci.prplIConversation.ENCRYPTION_NOT_SUPPORTED + ) { + return; + } + if (!OTR.libLoaded) { + return; + } + if (!this.globalDoc) { + return; + } + OTRUI.visibleConv = _conv; + let convBinding; + for (let element of this.globalDoc.getElementById("conversationsBox") + .children) { + if (!element.hidden) { + convBinding = element; + break; + } + } + if (convBinding && convBinding._conv && convBinding._conv.target) { + OTRUI.setMsgState(_conv, null, this.globalDoc, false); + } else { + this.hideOTRButton(); + } + }, + + /** + * Set encryption state on selector for conversation. + * + * @param {prplIConversation} _conv - Must match the visible conversation. + * @param {Context} [context] - The OTR context for the conversation. + * @param {DOMDocument} doc + * @param {boolean} [addSystemMessage] - If a system message with the conversation security. + */ + setMsgState(_conv, context, doc, addSystemMessage) { + if (!this.visibleConv) { + return; + } + if (_conv != null && !(_conv === this.visibleConv)) { + return; + } + + let otrContainer = doc.querySelector(".encryption-container"); + let otrButton = doc.querySelector(".encryption-button"); + if (_conv != null && _conv.isChat) { + OTRUI.noOtrPossible(otrContainer, context); + return; + } + + if (!context && _conv != null) { + context = OTR.getContext(_conv); + if (!context) { + OTRUI.noOtrPossible(otrContainer, null); + } + } + + try { + let uiConv = OTR.getUIConvFromContext(context); + if (uiConv != null && !(uiConv === this.visibleConv)) { + return; + } + if ( + uiConv.encryptionState === Ci.prplIConversation.ENCRYPTION_ENABLED || + uiConv.encryptionState === Ci.prplIConversation.ENCRYPTION_TRUSTED + ) { + return; + } + + if (uiConv.isChat) { + OTRUI.noOtrPossible(otrContainer, context); + return; + } + if (addSystemMessage) { + let trust = OTRUI.getTrustSettings(context); + let id = "state-" + trust.class; + let msg; + if (OTR.trust(context) == OTR.trustState.TRUST_NOT_PRIVATE) { + msg = lazy.l10n.formatValueSync(id); + } else { + msg = lazy.l10n.formatValueSync(id, { name: context.username }); + } + uiConv.systemMessage(msg, false, true); + } + } catch (e) { + OTRUI.noOtrPossible(otrContainer, context); + return; + } + + otrContainer.hidden = false; + let otrStart = doc.querySelector(".otr-start"); + let otrEnd = doc.querySelector(".otr-end"); + let otrAuth = doc.querySelector(".otr-auth"); + let trust = OTRUI.getTrustSettings(context); + otrButton.setAttribute( + "tooltiptext", + _strArgs("state-" + trust.class, { name: context.username }) + ); + otrButton.setAttribute("label", _str("state-" + trust.class + "-label")); + otrButton.className = "encryption-button encryption-" + trust.class; + otrStart.setAttribute("label", trust.startLabel); + otrStart.setAttribute("disabled", trust.disableStart); + otrEnd.setAttribute("disabled", trust.disableEnd); + otrAuth.setAttribute("label", trust.authLabel); + otrAuth.setAttribute("disabled", trust.disableAuth); + OTRUI.hideAllOTRNotifications(); + OTRUI.showUserNotifications(context); + }, + + alertTrust(context) { + let uiConv = OTR.getUIConvFromContext(context); + let trust = OTRUI.getTrustSettings(context); + uiConv.systemMessage( + _strArgs("afterauth-" + trust.class, { name: context.username }), + false, + true + ); + }, + + getTrustSettings(context) { + let result = trustMap.get(OTR.trust(context)); + return result; + }, + + askAuth(aObject) { + let uiConv = OTR.getUIConvFromContext(aObject.context); + if (!uiConv) { + return; + } + + let name = uiConv.target.normalizedName; + let msg = _strArgs("verify-request", { name }); + // Trigger the update of the unread message counter. + uiConv.notifyVerifyOTR(msg); + Services.obs.notifyObservers(uiConv, "new-otr-verification-request"); + + let window = this.globalDoc.defaultView; + let buttons = [ + { + label: _str("finger-verify"), + accessKey: _str("finger-verify-access-key"), + callback() { + OTRUI.openAuth(window, name, "ask", uiConv, aObject); + // prevent closing of notification bar when the button is hit + return true; + }, + }, + { + label: _str("finger-ignore"), + accessKey: _str("finger-ignore-access-key"), + callback() { + let context = OTR.getContext(uiConv.target); + OTR.abortSMP(context); + }, + }, + ]; + + let notification = this.globalBox.appendNotification( + `ask-auth-${name}`, + { + label: msg, + priority: this.globalBox.PRIORITY_WARNING_MEDIUM, + }, + buttons + ); + + notification.removeAttribute("dismissable"); + }, + + closeAskAuthNotification(aObject) { + let name = aObject.context.username; + let notification = this.globalBox.getNotificationWithValue( + `ask-auth-${name}` + ); + if (!notification) { + return; + } + + this.globalBox.removeNotification(notification); + }, + + closeUnverified(context) { + let uiConv = OTR.getUIConvFromContext(context); + if (!uiConv) { + return; + } + + for (let notification of this.globalBox.allNotifications) { + if ( + context.username == notification.getAttribute("user") && + notification.getAttribute("value") == AUTH_STATUS_UNVERIFIED + ) { + notification.close(); + } + } + }, + + hideUserNotifications(context) { + for (let notification of this.globalBox.allNotifications) { + if (context.username == notification.getAttribute("user")) { + notification.close(); + } + } + }, + + hideAllOTRNotifications() { + for (let notification of this.globalBox.allNotifications) { + if (notification.getAttribute("protocol") == "otr") { + notification.setAttribute("hidden", "true"); + } + } + }, + + showUserNotifications(context) { + let name = context.username; + for (let notification of this.globalBox.allNotifications) { + if (name == notification.getAttribute("user")) { + notification.removeAttribute("hidden"); + } + } + }, + + notifyUnverified(context, seen) { + let uiConv = OTR.getUIConvFromContext(context); + if (!uiConv) { + return; + } + + let name = context.username; + let window = this.globalDoc.defaultView; + + let buttons = [ + { + label: _str("finger-verify"), + accessKey: _str("finger-verify-access-key"), + callback() { + let name = uiConv.target.normalizedName; + OTRUI.openAuth(window, name, "start", uiConv); + // prevent closing of notification bar when the button is hit + return true; + }, + }, + { + label: _str("finger-ignore"), + accessKey: _str("finger-ignore-access-key"), + callback() { + let context = OTR.getContext(uiConv.target); + OTR.abortSMP(context); + }, + }, + ]; + + let notification = this.globalBox.appendNotification( + name, + { + label: _strArgs(`finger-${seen}`, { name }), + priority: this.globalBox.PRIORITY_WARNING_MEDIUM, + }, + buttons + ); + + // Set the user attribute so we can show and hide notifications based on the + // currently viewed conversation. + notification.setAttribute("user", name); + // Set custom attributes for CSS styling. + notification.setAttribute("protocol", "otr"); + notification.setAttribute("status", AUTH_STATUS_UNVERIFIED); + // Prevent users from dismissing this notification. + notification.removeAttribute("dismissable"); + + if (!this.visibleConv) { + return; + } + + if (name !== this.visibleConv.normalizedName) { + this.hideUserNotifications(context); + } + }, + + closeVerification(context) { + let uiConv = OTR.getUIConvFromContext(context); + if (!uiConv) { + return; + } + + let prevNotification = OTRUI.globalBox.getNotificationWithValue( + context.username + ); + if (prevNotification) { + prevNotification.close(); + } + }, + + notifyVerification(context, key, cancelable, verifiable) { + let uiConv = OTR.getUIConvFromContext(context); + if (!uiConv) { + return; + } + + OTRUI.closeVerification(context); + + let buttons = []; + if (cancelable) { + buttons = [ + { + label: _str("auth-cancel"), + accessKey: _str("auth-cancel-access-key"), + callback() { + let context = OTR.getContext(uiConv.target); + OTR.abortSMP(context); + }, + }, + ]; + } + + if (verifiable) { + let window = this.globalDoc.defaultView; + + buttons = [ + { + label: _str("finger-verify"), + accessKey: _str("finger-verify-access-key"), + callback() { + let name = uiConv.target.normalizedName; + OTRUI.openAuth(window, name, "start", uiConv); + // prevent closing of notification bar when the button is hit + return true; + }, + }, + { + label: _str("finger-ignore"), + accessKey: _str("finger-ignore-access-key"), + callback() { + let context = OTR.getContext(uiConv.target); + OTR.abortSMP(context); + }, + }, + ]; + } + + // Change priority type based on the passed key. + let priority = this.globalBox.PRIORITY_WARNING_HIGH; + let dismissable = true; + switch (key) { + case "otr:auth-error": + case "otr:auth-fail": + priority = this.globalBox.PRIORITY_CRITICAL_HIGH; + break; + case "otr:auth-waiting": + priority = this.globalBox.PRIORITY_INFO_MEDIUM; + dismissable = false; + break; + + default: + break; + } + + OTRUI.closeUnverified(context); + let notification = this.globalBox.appendNotification( + context.username, + { + label: authLabelMap.get(key), + priority, + }, + buttons + ); + + // Set the user attribute so we can show and hide notifications based on the + // currently viewed conversation. + notification.setAttribute("user", context.username); + // Set custom attributes for CSS styling. + notification.setAttribute("protocol", "otr"); + notification.setAttribute("status", key); + + // The notification API don't currently support a "success" PRIORITY flag, + // so we need to manually set it if we need to. + if (["otr:auth-success", "otr:auth-success-them"].includes(key)) { + notification.setAttribute("type", "success"); + } + + if (!dismissable) { + // Prevent users from dismissing this notification if something is in + // progress or an action is required. + notification.removeAttribute("dismissable"); + } + }, + + updateAuth(aObj) { + // let uiConv = OTR.getUIConvFromContext(aObj.context); + if (!aObj.progress) { + OTRUI.closeAuth(aObj.context); + OTRUI.notifyVerification(aObj.context, "otr:auth-error", false, false); + } else if (aObj.progress === 100) { + let key; + let verifiable = false; + if (aObj.success) { + if (aObj.context.trust) { + key = "otr:auth-success"; + OTR.notifyTrust(aObj.context); + } else { + key = "otr:auth-success-them"; + verifiable = true; + } + } else { + key = "otr:auth-fail"; + if (!aObj.context.trust) { + OTR.notifyTrust(aObj.context); + } + } + OTRUI.notifyVerification(aObj.context, key, false, verifiable); + } else { + // TODO: show the aObj.progress to the user with a + // <progressmeter mode="determined" value="10" /> + OTRUI.notifyVerification(aObj.context, "otr:auth-waiting", true, false); + } + OTRUI.closeAskAuthNotification(aObj); + }, + + onAccountCreated(acc) { + let account = acc.normalizedName; + let protocol = acc.protocol.normalizedName; + Promise.resolve(); + if (OTR.privateKeyFingerprint(account, protocol) === null) { + OTR.generatePrivateKey(account, protocol).catch( + OTRUI.reportKeyGenFailure + ); + } + }, + + contactWrapper(contact) { + // If the conversation already started. + if (contact.buddy) { + return { + account: contact.buddy.normalizedName, + protocol: contact.buddy.buddy.protocol.normalizedName, + screenname: contact.buddy.userName, + }; + } + + // For online and offline contacts without an open conversation. + return { + account: + contact.preferredBuddy.preferredAccountBuddy.account.normalizedName, + protocol: contact.preferredBuddy.protocol.normalizedName, + screenname: contact.preferredBuddy.preferredAccountBuddy.userName, + }; + }, + + onContactAdded(contact) { + let args = OTRUI.contactWrapper(contact); + if ( + OTR.getFingerprintsForRecipient( + args.account, + args.protocol, + args.screenname + ).length > 0 + ) { + return; + } + args.wrappedJSObject = args; + let features = "chrome,modal,centerscreen,resizable=no,minimizable=no"; + Services.ww.openWindow(null, OTR_ADD_FINGER_DIALOG_URL, "", features, args); + }, + + observe(aObject, aTopic, aMsg) { + let doc; + // console.log("====> observing topic: " + aTopic + " with msg: " + aMsg); + // console.log(aObject); + + switch (aTopic) { + case "nsPref:changed": + break; + case "conversation-loaded": + doc = aObject.ownerDocument; + let windowtype = doc.documentElement.getAttribute("windowtype"); + if (windowtype !== "mail:3pane") { + return; + } + OTRUI.addButton(aObject); + break; + case "conversation-closed": + if (aObject.isChat) { + return; + } + this.globalBox.removeAllNotifications(); + OTRUI.closeAuth(OTR.getContext(aObject)); + OTRUI.disconnect(aObject); + break; + // case "contact-signed-off": + // break; + case "prpl-quit": + OTRUI.disconnect(null); + break; + case "domwindowopened": + OTRUI.addMenus(aObject); + break; + case "otr:generate": { + let result = OTR.generatePrivateKeySync( + aObject.account, + aObject.protocol + ); + if (result != null) { + OTRUI.reportKeyGenFailure(result); + } + break; + } + case "otr:disconnected": + case "otr:msg-state": + if ( + aTopic === "otr:disconnected" || + OTR.trust(aObject) !== OTR.trustState.TRUST_UNVERIFIED + ) { + OTRUI.closeAuth(aObject); + OTRUI.closeUnverified(aObject); + OTRUI.closeVerification(aObject); + } + OTRUI.setMsgState(null, aObject, this.globalDoc, false); + break; + case "otr:unverified": + if (!this.globalDoc) { + let win = Services.wm.getMostRecentWindow("mail:3pane"); + if (!win) { + return; + } + win.focus(); + win.showChatTab(); + this.globalDoc = win.document; + } + OTRUI.notifyUnverified(aObject, aMsg); + break; + case "otr:trust-state": + OTRUI.alertTrust(aObject); + break; + case "otr:log": + OTRUI.logMsg("otr: " + aObject); + break; + case "account-added": + OTRUI.onAccountCreated(aObject); + break; + case "contact-added": + OTRUI.onContactAdded(aObject); + break; + case "otr:auth-ask": + OTRUI.askAuth(aObject); + break; + case "otr:auth-update": + OTRUI.updateAuth(aObject); + break; + case "otr:cancel-ask-auth": + OTRUI.closeAskAuthNotification(aObject); + break; + } + }, + + initConv(binding) { + OTR.addConversation(binding._conv); + OTRUI.addButton(binding); + }, + + /** + * Restore the conversation to a state before OTR knew about it. + * + * @param {Element} binding - conversation-browser instance. + */ + resetConv(binding) { + OTR.removeConversation(binding._conv); + }, + + destroy() { + if (!OTR.libLoaded) { + return; + } + OTRUI.disconnect(null); + Services.obs.removeObserver(OTR, "new-ui-conversation"); + Services.obs.removeObserver(OTR, "conversation-update-type"); + // Services.obs.removeObserver(OTRUI, "contact-added"); + // Services.obs.removeObserver(OTRUI, "contact-signed-off"); + Services.obs.removeObserver(OTRUI, "account-added"); + Services.obs.removeObserver(OTRUI, "conversation-loaded"); + Services.obs.removeObserver(OTRUI, "conversation-closed"); + Services.obs.removeObserver(OTRUI, "prpl-quit"); + + for (let conv of IMServices.conversations.getConversations()) { + OTRUI.resetConv(conv); + } + OTR.removeObserver(OTRUI); + OTR.close(); + OTRUI.removeMenuObserver(); + }, +}; |