new file mode 100644
--- /dev/null
+++ b/chat/components/src/components.conf
@@ -0,0 +1,57 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ 'cid': '{a94b5427-cd8d-40cf-b47e-b67671953e70}',
+ 'contract_ids': ['@mozilla.org/chat/accounts-service;1'],
+ 'jsm': 'resource:///modules/imAccounts.jsm',
+ 'constructor': 'AccountsService',
+ },
+ {
+ 'cid': '{7cb20c68-ccc8-4a79-b6f1-0b4771ed6c23}',
+ 'contract_ids': ['@mozilla.org/chat/commands-service;1'],
+ 'jsm': 'resource:///modules/imCommands.jsm',
+ 'constructor': 'CommandsService',
+ },
+ {
+ 'cid': '{8c3725dd-ee26-489d-8135-736015af8c7f}',
+ 'contract_ids': ['@mozilla.org/chat/contacts-service;1'],
+ 'jsm': 'resource:///modules/imContacts.jsm',
+ 'constructor': 'ContactsService',
+ },
+ {
+ 'cid': '{1fa92237-4303-4384-b8ac-4e65b50810a5}',
+ 'contract_ids': ['@mozilla.org/chat/tags-service;1'],
+ 'jsm': 'resource:///modules/imContacts.jsm',
+ 'constructor': 'TagsService',
+ },
+ {
+ 'cid': '{b2397cd5-c76d-4618-8410-f344c7c6443a}',
+ 'contract_ids': ['@mozilla.org/chat/conversations-service;1'],
+ 'jsm': 'resource:///modules/imConversations.jsm',
+ 'constructor': 'ConversationsService',
+ },
+ {
+ 'cid': '{073f5953-853c-4a38-bd81-255510c31c2e}',
+ 'contract_ids': ['@mozilla.org/chat/core-service;1'],
+ 'jsm': 'resource:///modules/imCore.jsm',
+ 'constructor': 'CoreService',
+ },
+ {
+ 'cid': '{fb0dc220-2c7a-4216-9f19-6b8f3480eae9}',
+ 'contract_ids': ['@mozilla.org/chat/logger;1'],
+ 'jsm': 'resource:///modules/logger.jsm',
+ 'constructor': 'Logger',
+ 'categories': {'profile-after-change': 'Logger'},
+ },
+ {
+ 'cid': '{04e58eae-dfbc-4c9e-8130-6d9ef19cbff4}',
+ 'contract_ids': ['@mozilla.org/network/protocol;1?name=smile'],
+ 'jsm': 'resource:///modules/smileProtocolHandler.jsm',
+ 'constructor': 'smileProtocolHandler',
+ },
+]
rename from chat/components/src/imAccounts.js
rename to chat/components/src/imAccounts.jsm
--- a/chat/components/src/imAccounts.js
+++ b/chat/components/src/imAccounts.jsm
@@ -1,11 +1,14 @@
/* 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/. */
+
+var EXPORTED_SYMBOLS = ["AccountsService"];
+
var {
ClassInfo,
EmptyEnumerator,
nsSimpleEnumerator,
XPCOMUtils,
setTimeout,
clearTimeout,
executeSoon,
@@ -1290,13 +1293,9 @@ AccountsService.prototype = {
this._accountList = list
.split(",")
.filter(k => k.trim() != aAccountId)
.join(",");
},
QueryInterface: ChromeUtils.generateQI([Ci.imIAccountsService]),
classDescription: "Accounts",
- classID: Components.ID("{a94b5427-cd8d-40cf-b47e-b67671953e70}"),
- contractID: "@mozilla.org/chat/accounts-service;1",
};
-
-var NSGetFactory = XPCOMUtils.generateNSGetFactory([AccountsService]);
deleted file mode 100644
--- a/chat/components/src/imAccounts.manifest
+++ /dev/null
@@ -1,2 +0,0 @@
-component {a94b5427-cd8d-40cf-b47e-b67671953e70} imAccounts.js
-contract @mozilla.org/chat/accounts-service;1 {a94b5427-cd8d-40cf-b47e-b67671953e70}
rename from chat/components/src/imCommands.js
rename to chat/components/src/imCommands.jsm
--- a/chat/components/src/imCommands.js
+++ b/chat/components/src/imCommands.jsm
@@ -1,12 +1,14 @@
/* 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/. */
+var EXPORTED_SYMBOLS = ["CommandsService"];
+
var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
var { XPCOMUtils, l10nHelper } = ChromeUtils.import(
"resource:///modules/imXPCOMUtils.jsm"
);
XPCOMUtils.defineLazyGetter(this, "_", () =>
l10nHelper("chrome://chat/locale/commands.properties")
);
@@ -280,13 +282,9 @@ CommandsService.prototype = {
// If they all failed, print help message.
this.executeCommand("/help " + name, aConversation);
}
return true;
},
QueryInterface: ChromeUtils.generateQI([Ci.imICommandsService]),
classDescription: "Commands",
- classID: Components.ID("{7cb20c68-ccc8-4a79-b6f1-0b4771ed6c23}"),
- contractID: "@mozilla.org/chat/commands-service;1",
};
-
-var NSGetFactory = XPCOMUtils.generateNSGetFactory([CommandsService]);
deleted file mode 100644
--- a/chat/components/src/imCommands.manifest
+++ /dev/null
@@ -1,2 +0,0 @@
-component {7cb20c68-ccc8-4a79-b6f1-0b4771ed6c23} imCommands.js
-contract @mozilla.org/chat/commands-service;1 {7cb20c68-ccc8-4a79-b6f1-0b4771ed6c23}
rename from chat/components/src/imContacts.js
rename to chat/components/src/imContacts.jsm
--- a/chat/components/src/imContacts.js
+++ b/chat/components/src/imContacts.jsm
@@ -1,12 +1,14 @@
/* 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/. */
+var EXPORTED_SYMBOLS = ["TagsService", "ContactsService"];
+
var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
var { XPCOMUtils, executeSoon, ClassInfo, l10nHelper } = ChromeUtils.import(
"resource:///modules/imXPCOMUtils.jsm"
);
XPCOMUtils.defineLazyGetter(this, "_", () =>
l10nHelper("chrome://chat/locale/contacts.properties")
);
@@ -196,18 +198,16 @@ TagsService.prototype = {
},
get otherContactsTag() {
otherContactsTag._initContacts();
return otherContactsTag;
},
QueryInterface: ChromeUtils.generateQI([Ci.imITagsService]),
classDescription: "Tags",
- classID: Components.ID("{1fa92237-4303-4384-b8ac-4e65b50810a5}"),
- contractID: "@mozilla.org/chat/tags-service;1",
};
// TODO move into the tagsService
var Tags = [];
var TagsById = {};
function Tag(aId, aName) {
this._id = aId;
@@ -1795,16 +1795,9 @@ ContactsService.prototype = {
statement.execute();
} finally {
statement.finalize();
}
},
QueryInterface: ChromeUtils.generateQI([Ci.imIContactsService]),
classDescription: "Contacts",
- classID: Components.ID("{8c3725dd-ee26-489d-8135-736015af8c7f}"),
- contractID: "@mozilla.org/chat/contacts-service;1",
};
-
-var NSGetFactory = XPCOMUtils.generateNSGetFactory([
- ContactsService,
- TagsService,
-]);
deleted file mode 100644
--- a/chat/components/src/imContacts.manifest
+++ /dev/null
@@ -1,4 +0,0 @@
-component {8c3725dd-ee26-489d-8135-736015af8c7f} imContacts.js
-contract @mozilla.org/chat/contacts-service;1 {8c3725dd-ee26-489d-8135-736015af8c7f}
-component {1fa92237-4303-4384-b8ac-4e65b50810a5} imContacts.js
-contract @mozilla.org/chat/tags-service;1 {1fa92237-4303-4384-b8ac-4e65b50810a5}
rename from chat/components/src/imConversations.js
rename to chat/components/src/imConversations.jsm
--- a/chat/components/src/imConversations.js
+++ b/chat/components/src/imConversations.jsm
@@ -1,12 +1,14 @@
/* 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/. */
+var EXPORTED_SYMBOLS = ["ConversationsService"];
+
var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
var { Status } = ChromeUtils.import("resource:///modules/imStatusUtils.jsm");
var { XPCOMUtils, nsSimpleEnumerator, ClassInfo } = ChromeUtils.import(
"resource:///modules/imXPCOMUtils.jsm"
);
var { Message } = ChromeUtils.import("resource:///modules/jsProtoHelper.jsm");
var gLastUIConvId = 0;
@@ -821,13 +823,9 @@ ConversationsService.prototype = {
return conv;
}
}
return null;
},
QueryInterface: ChromeUtils.generateQI([Ci.imIConversationsService]),
classDescription: "Conversations",
- classID: Components.ID("{b2397cd5-c76d-4618-8410-f344c7c6443a}"),
- contractID: "@mozilla.org/chat/conversations-service;1",
};
-
-var NSGetFactory = XPCOMUtils.generateNSGetFactory([ConversationsService]);
deleted file mode 100644
--- a/chat/components/src/imConversations.manifest
+++ /dev/null
@@ -1,2 +0,0 @@
-component {b2397cd5-c76d-4618-8410-f344c7c6443a} imConversations.js
-contract @mozilla.org/chat/conversations-service;1 {b2397cd5-c76d-4618-8410-f344c7c6443a}
rename from chat/components/src/imCore.js
rename to chat/components/src/imCore.jsm
--- a/chat/components/src/imCore.js
+++ b/chat/components/src/imCore.jsm
@@ -1,12 +1,14 @@
/* 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/. */
+var EXPORTED_SYMBOLS = ["CoreService"];
+
var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
var {
XPCOMUtils,
ClassInfo,
initLogModule,
nsSimpleEnumerator,
} = ChromeUtils.import("resource:///modules/imXPCOMUtils.jsm");
@@ -268,20 +270,17 @@ UserStatus.prototype = {
},
_notifyObservers(aTopic, aData) {
for (let observer of this._observers) {
observer.observe(this, aTopic, aData);
}
},
};
-var gCoreService;
-function CoreService() {
- gCoreService = this;
-}
+function CoreService() {}
CoreService.prototype = {
globalUserStatus: null,
_initialized: false,
get initialized() {
return this._initialized;
},
init() {
@@ -408,13 +407,9 @@ CoreService.prototype = {
}
this._protos[aPrplId] = proto;
return proto;
},
QueryInterface: ChromeUtils.generateQI([Ci.imICoreService]),
classDescription: "Core",
- classID: Components.ID("{073f5953-853c-4a38-bd81-255510c31c2e}"),
- contractID: "@mozilla.org/chat/core-service;1",
};
-
-var NSGetFactory = XPCOMUtils.generateNSGetFactory([CoreService]);
deleted file mode 100644
--- a/chat/components/src/imCore.manifest
+++ /dev/null
@@ -1,2 +0,0 @@
-component {073f5953-853c-4a38-bd81-255510c31c2e} imCore.js
-contract @mozilla.org/chat/core-service;1 {073f5953-853c-4a38-bd81-255510c31c2e}
rename from chat/components/src/logger.js
rename to chat/components/src/logger.jsm
--- a/chat/components/src/logger.js
+++ b/chat/components/src/logger.jsm
@@ -1,13 +1,13 @@
/* 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/. */
-var CC = Components.Constructor;
+var EXPORTED_SYMBOLS = ["Logger"];
var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
var { EmptyEnumerator, l10nHelper, XPCOMUtils } = ChromeUtils.import(
"resource:///modules/imXPCOMUtils.jsm"
);
var { GenericMessagePrototype } = ChromeUtils.import(
"resource:///modules/jsProtoHelper.jsm"
);
@@ -1125,13 +1125,9 @@ Logger.prototype = {
break;
default:
throw new Error("Unexpected notification " + aTopic);
}
},
QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver, Ci.imILogger]),
classDescription: "Logger",
- classID: Components.ID("{fb0dc220-2c7a-4216-9f19-6b8f3480eae9}"),
- contractID: "@mozilla.org/chat/logger;1",
};
-
-var NSGetFactory = XPCOMUtils.generateNSGetFactory([Logger]);
deleted file mode 100644
--- a/chat/components/src/logger.manifest
+++ /dev/null
@@ -1,3 +0,0 @@
-component {fb0dc220-2c7a-4216-9f19-6b8f3480eae9} logger.js
-contract @mozilla.org/chat/logger;1 {fb0dc220-2c7a-4216-9f19-6b8f3480eae9}
-category profile-after-change Logger @mozilla.org/chat/logger;1
--- a/chat/components/src/moz.build
+++ b/chat/components/src/moz.build
@@ -1,24 +1,20 @@
# vim: set filetype=python:
# 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/.
XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
-EXTRA_COMPONENTS += [
- 'imAccounts.js',
- 'imAccounts.manifest',
- 'imCommands.js',
- 'imCommands.manifest',
- 'imContacts.js',
- 'imContacts.manifest',
- 'imConversations.js',
- 'imConversations.manifest',
- 'imCore.js',
- 'imCore.manifest',
- 'logger.js',
- 'logger.manifest',
- 'smileProtocolHandler.js',
- 'smileProtocolHandler.manifest',
+EXTRA_JS_MODULES += [
+ 'imAccounts.jsm',
+ 'imCommands.jsm',
+ 'imContacts.jsm',
+ 'imConversations.jsm',
+ 'imCore.jsm',
+ 'logger.jsm',
+ 'smileProtocolHandler.jsm'
]
+XPCOM_MANIFESTS += [
+ 'components.conf',
+]
rename from chat/components/src/smileProtocolHandler.js
rename to chat/components/src/smileProtocolHandler.jsm
--- a/chat/components/src/smileProtocolHandler.js
+++ b/chat/components/src/smileProtocolHandler.jsm
@@ -1,16 +1,15 @@
/* 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/. */
+var EXPORTED_SYMBOLS = ["smileProtocolHandler"];
+
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-var { XPCOMUtils } = ChromeUtils.import(
- "resource://gre/modules/XPCOMUtils.jsm"
-);
var { getSmileRealURI } = ChromeUtils.import(
"resource:///modules/imSmileys.jsm"
);
var kSmileRegexp = /^smile:\/\//;
function smileProtocolHandler() {}
@@ -28,15 +27,11 @@ smileProtocolHandler.prototype = {
let channel = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
channel.originalURI = aURI;
return channel;
},
allowPort(aPort, aScheme) {
return false;
},
+ QueryInterface: ChromeUtils.generateQI([Ci.nsIProtocolHandler]),
classDescription: "Smile Protocol Handler",
- classID: Components.ID("{04e58eae-dfbc-4c9e-8130-6d9ef19cbff4}"),
- contractID: "@mozilla.org/network/protocol;1?name=smile",
- QueryInterface: ChromeUtils.generateQI([Ci.nsIProtocolHandler]),
};
-
-var NSGetFactory = XPCOMUtils.generateNSGetFactory([smileProtocolHandler]);
deleted file mode 100644
--- a/chat/components/src/smileProtocolHandler.manifest
+++ /dev/null
@@ -1,2 +0,0 @@
-component {04e58eae-dfbc-4c9e-8130-6d9ef19cbff4} smileProtocolHandler.js
-contract @mozilla.org/network/protocol;1?name=smile {04e58eae-dfbc-4c9e-8130-6d9ef19cbff4}
--- a/chat/components/src/test/test_commands.js
+++ b/chat/components/src/test/test_commands.js
@@ -2,17 +2,17 @@
* 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/. */
var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
// We don't load the command service via Services as we want to access
// _findCommands in order to avoid having to intercept command execution.
var imCommands = {};
Services.scriptloader.loadSubScript(
- "resource:///components/imCommands.js",
+ "resource:///modules/imCommands.jsm",
imCommands
);
var kPrplId = "green";
var kPrplId2 = "red";
var fakeAccount = {
connected: true,
--- a/chat/components/src/test/test_conversations.js
+++ b/chat/components/src/test/test_conversations.js
@@ -1,19 +1,18 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
var { GenericConvIMPrototype, Message } = ChromeUtils.import(
"resource:///modules/jsProtoHelper.jsm"
);
-
var imConversations = {};
Services.scriptloader.loadSubScript(
- "resource:///components/imConversations.js",
+ "resource:///modules/imConversations.jsm",
imConversations
);
// Fake prplConversation
var _id = 0;
function Conversation(aName) {
this._name = aName;
this._observers = [];
--- a/chat/components/src/test/test_logger.js
+++ b/chat/components/src/test/test_logger.js
@@ -3,20 +3,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
do_get_profile();
var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
var gLogger = {};
-Services.scriptloader.loadSubScript(
- "resource:///components/logger.js",
- gLogger
-);
+Services.scriptloader.loadSubScript("resource:///modules/logger.jsm", gLogger);
var logDirPath = OS.Path.join(OS.Constants.Path.profileDir, "logs");
var dummyAccount = {
name: "dummy-account",
normalizedName: "dummyaccount",
protocol: {
normalizedName: "dummy",
new file mode 100644
--- /dev/null
+++ b/chat/protocols/facebook/components.conf
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ 'cid': '{1d1d0bc5-610c-472f-b2cb-4b89857d80dc}',
+ 'contract_ids': ['@mozilla.org/chat/facebook;1'],
+ 'jsm': 'resource:///modules/facebook.jsm',
+ 'constructor': 'FacebookProtocol',
+ 'categories': {'im-protocol-plugin': 'prpl-facebook'},
+ },
+]
rename from chat/protocols/facebook/facebook.js
rename to chat/protocols/facebook/facebook.jsm
--- a/chat/protocols/facebook/facebook.js
+++ b/chat/protocols/facebook/facebook.jsm
@@ -1,12 +1,14 @@
/* 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/. */
+var EXPORTED_SYMBOLS = ["FacebookProtocol"];
+
var { XPCOMUtils, l10nHelper } = ChromeUtils.import(
"resource:///modules/imXPCOMUtils.jsm"
);
var { GenericAccountPrototype, GenericProtocolPrototype } = ChromeUtils.import(
"resource:///modules/jsProtoHelper.jsm"
);
XPCOMUtils.defineLazyGetter(this, "_", () =>
@@ -45,12 +47,9 @@ FacebookProtocol.prototype = {
return _("facebook.chat.name");
},
get iconBaseURI() {
return "chrome://prpl-facebook/skin/";
},
getAccount(aImAccount) {
return new FacebookAccount(this, aImAccount);
},
- classID: Components.ID("{1d1d0bc5-610c-472f-b2cb-4b89857d80dc}"),
};
-
-var NSGetFactory = XPCOMUtils.generateNSGetFactory([FacebookProtocol]);
deleted file mode 100644
--- a/chat/protocols/facebook/facebook.manifest
+++ /dev/null
@@ -1,3 +0,0 @@
-component {1d1d0bc5-610c-472f-b2cb-4b89857d80dc} facebook.js
-contract @mozilla.org/chat/facebook;1 {1d1d0bc5-610c-472f-b2cb-4b89857d80dc}
-category im-protocol-plugin prpl-facebook @mozilla.org/chat/facebook;1
--- a/chat/protocols/facebook/moz.build
+++ b/chat/protocols/facebook/moz.build
@@ -1,11 +1,14 @@
# vim: set filetype=python:
# 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/.
-EXTRA_COMPONENTS += [
- 'facebook.js',
- 'facebook.manifest',
+JAR_MANIFESTS += ['jar.mn']
+
+EXTRA_JS_MODULES += [
+ 'facebook.jsm',
]
-JAR_MANIFESTS += ['jar.mn']
+XPCOM_MANIFESTS += [
+ 'components.conf',
+]
new file mode 100644
--- /dev/null
+++ b/chat/protocols/gtalk/components.conf
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ 'cid': '{38a224c1-6748-49a9-8ab2-efc362b1000d}',
+ 'contract_ids': ['@mozilla.org/chat/gtalk;1'],
+ 'jsm': 'resource:///modules/gtalk.jsm',
+ 'constructor': 'GTalkProtocol',
+ 'categories': {'im-protocol-plugin': 'prpl-gtalk'},
+ },
+]
rename from chat/protocols/gtalk/gtalk.js
rename to chat/protocols/gtalk/gtalk.jsm
--- a/chat/protocols/gtalk/gtalk.js
+++ b/chat/protocols/gtalk/gtalk.jsm
@@ -1,20 +1,22 @@
/* 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/. */
+var EXPORTED_SYMBOLS = ["GTalkProtocol"];
+
var { XPCOMUtils, l10nHelper } = ChromeUtils.import(
"resource:///modules/imXPCOMUtils.jsm"
);
var { GenericProtocolPrototype } = ChromeUtils.import(
"resource:///modules/jsProtoHelper.jsm"
);
var { XMPPAccountPrototype } = ChromeUtils.import(
- "resource:///modules/xmpp.jsm"
+ "resource:///modules/xmpp-base.jsm"
);
var { XMPPSession } = ChromeUtils.import(
"resource:///modules/xmpp-session.jsm"
);
var { Stanza } = ChromeUtils.import("resource:///modules/xmpp-xml.jsm");
XPCOMUtils.defineLazyGetter(this, "_", () =>
l10nHelper("chrome://chat/locale/xmpp.properties")
@@ -107,12 +109,9 @@ GTalkProtocol.prototype = {
return _("options.resource");
},
default: "",
},
},
get chatHasTopic() {
return true;
},
- classID: Components.ID("{38a224c1-6748-49a9-8ab2-efc362b1000d}"),
};
-
-var NSGetFactory = XPCOMUtils.generateNSGetFactory([GTalkProtocol]);
deleted file mode 100644
--- a/chat/protocols/gtalk/gtalk.manifest
+++ /dev/null
@@ -1,3 +0,0 @@
-component {38a224c1-6748-49a9-8ab2-efc362b1000d} gtalk.js
-contract @mozilla.org/chat/gtalk;1 {38a224c1-6748-49a9-8ab2-efc362b1000d}
-category im-protocol-plugin prpl-gtalk @mozilla.org/chat/gtalk;1
--- a/chat/protocols/gtalk/moz.build
+++ b/chat/protocols/gtalk/moz.build
@@ -1,11 +1,14 @@
# vim: set filetype=python:
# 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/.
-EXTRA_COMPONENTS += [
- 'gtalk.js',
- 'gtalk.manifest',
+JAR_MANIFESTS += ['jar.mn']
+
+EXTRA_JS_MODULES += [
+ 'gtalk.jsm',
]
-JAR_MANIFESTS += ['jar.mn']
+XPCOM_MANIFESTS += [
+ 'components.conf',
+]
new file mode 100644
--- /dev/null
+++ b/chat/protocols/irc/components.conf
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ 'cid': '{607b2c0b-9504-483f-ad62-41de09238aec}',
+ 'contract_ids': ['@mozilla.org/chat/irc;1'],
+ 'jsm': 'resource:///modules/irc.jsm',
+ 'constructor': 'ircProtocol',
+ 'categories': {'im-protocol-plugin': 'prpl-irc'},
+ },
+]
rename from chat/protocols/irc/irc.js
rename to chat/protocols/irc/irc.jsm
--- a/chat/protocols/irc/irc.js
+++ b/chat/protocols/irc/irc.jsm
@@ -1,12 +1,14 @@
/* 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/. */
+var EXPORTED_SYMBOLS = ["ircProtocol"];
+
var {
ClassInfo,
clearTimeout,
EmptyEnumerator,
setTimeout,
executeSoon,
l10nHelper,
XPCOMUtils,
@@ -2412,12 +2414,9 @@ ircProtocol.prototype = {
// Passwords in IRC are optional, and are needed for certain functionality.
get passwordOptional() {
return true;
},
getAccount(aImAccount) {
return new ircAccount(this, aImAccount);
},
- classID: Components.ID("{607b2c0b-9504-483f-ad62-41de09238aec}"),
};
-
-var NSGetFactory = XPCOMUtils.generateNSGetFactory([ircProtocol]);
deleted file mode 100644
--- a/chat/protocols/irc/irc.manifest
+++ /dev/null
@@ -1,3 +0,0 @@
-component {607b2c0b-9504-483f-ad62-41de09238aec} irc.js
-contract @mozilla.org/chat/irc;1 {607b2c0b-9504-483f-ad62-41de09238aec}
-category im-protocol-plugin prpl-irc @mozilla.org/chat/irc;1
--- a/chat/protocols/irc/moz.build
+++ b/chat/protocols/irc/moz.build
@@ -1,21 +1,17 @@
# vim: set filetype=python:
# 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/.
XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
-EXTRA_COMPONENTS += [
- 'irc.js',
- 'irc.manifest',
-]
-
EXTRA_JS_MODULES += [
+ 'irc.jsm',
'ircBase.jsm',
'ircCAP.jsm',
'ircCommands.jsm',
'ircCTCP.jsm',
'ircDCC.jsm',
'ircEchoMessage.jsm',
'ircHandlers.jsm',
'ircISUPPORT.jsm',
@@ -24,8 +20,12 @@ EXTRA_JS_MODULES += [
'ircSASL.jsm',
'ircServerTime.jsm',
'ircServices.jsm',
'ircUtils.jsm',
'ircWatchMonitor.jsm',
]
JAR_MANIFESTS += ['jar.mn']
+
+XPCOM_MANIFESTS += [
+ 'components.conf',
+]
--- a/chat/protocols/irc/test/test_ctcpQuote.js
+++ b/chat/protocols/irc/test/test_ctcpQuote.js
@@ -1,14 +1,14 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var irc = {};
-Services.scriptloader.loadSubScript("resource:///components/irc.js", irc);
+Services.scriptloader.loadSubScript("resource:///modules/irc.jsm", irc);
var input = [
undefined,
"test",
"\\test",
"te\\st",
"test\\",
"\\\\test",
--- a/chat/protocols/irc/test/test_ircCommands.js
+++ b/chat/protocols/irc/test/test_ircCommands.js
@@ -1,16 +1,15 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
var { commands } = ChromeUtils.import("resource:///modules/ircCommands.jsm");
-
var irc = {};
-Services.scriptloader.loadSubScript("resource:///components/irc.js", irc);
+Services.scriptloader.loadSubScript("resource:///modules/irc.jsm", irc);
// Ensure the commands have been initialized.
Services.conversations.initConversations();
var fakeProto = {
id: "fake-proto",
};
--- a/chat/protocols/irc/test/test_ircMessage.js
+++ b/chat/protocols/irc/test/test_ircMessage.js
@@ -1,14 +1,14 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var irc = {};
-Services.scriptloader.loadSubScript("resource:///components/irc.js", irc);
+Services.scriptloader.loadSubScript("resource:///modules/irc.jsm", irc);
var testData = [
// First off, let's test the messages from RFC 2812.
"PASS secretpasswordhere",
"NICK Wiz",
":WiZ!jto@tolsun.oulu.fi NICK Kilroy",
"USER guest 0 * :Ronnie Reagan",
"USER guest 8 * :Ronnie Reagan",
@@ -148,17 +148,17 @@ function testRFC2812Messages() {
}
equal(stringMessage, expectedStringMessage);
}
run_next_test();
}
-// Unreal sends a couple of broken messages, see ircMessage in irc.js for a
+// Unreal sends a couple of broken messages, see ircMessage in irc.jsm for a
// description of what's wrong.
function testBrokenUnrealMessages() {
let messages = {
// Two spaces after command.
":gravel.mozilla.org 432 #momo :Erroneous Nickname: Illegal characters": {
rawMessage:
":gravel.mozilla.org 432 #momo :Erroneous Nickname: Illegal characters",
command: "432",
--- a/chat/protocols/irc/test/test_ircNonStandard.js
+++ b/chat/protocols/irc/test/test_ircNonStandard.js
@@ -1,14 +1,14 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var irc = {};
-Services.scriptloader.loadSubScript("resource:///components/irc.js", irc);
+Services.scriptloader.loadSubScript("resource:///modules/irc.jsm", irc);
const { ircNonStandard } = ChromeUtils.import(
"resource:///modules/ircNonStandard.jsm"
);
// The function that is under test here.
var NOTICE = ircNonStandard.commands.NOTICE;
function FakeConversation() {}
--- a/chat/protocols/irc/test/test_ircServerTime.js
+++ b/chat/protocols/irc/test/test_ircServerTime.js
@@ -2,17 +2,17 @@
* http://creativecommons.org/publicdomain/zero/1.0/ */
var { tagServerTime } = ChromeUtils.import(
"resource:///modules/ircServerTime.jsm"
);
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var irc = {};
-Services.scriptloader.loadSubScript("resource:///components/irc.js", irc);
+Services.scriptloader.loadSubScript("resource:///modules/irc.jsm", irc);
function getTags(aRawMsg) {
const { tags } = irc.ircMessage(aRawMsg, "doesnt@matter");
return tags;
}
function run_test() {
--- a/chat/protocols/irc/test/test_sendBufferedCommand.js
+++ b/chat/protocols/irc/test/test_sendBufferedCommand.js
@@ -1,15 +1,15 @@
/* 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/. */
var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
var irc = {};
-Services.scriptloader.loadSubScript("resource:///components/irc.js", irc);
+Services.scriptloader.loadSubScript("resource:///modules/irc.jsm", irc);
function FakeAccount() {
this._commandBuffers = new Map();
this.callbacks = [];
}
FakeAccount.prototype = {
__proto__: irc.ircAccount.prototype,
maxMessageLength: 60,
--- a/chat/protocols/irc/test/test_setMode.js
+++ b/chat/protocols/irc/test/test_setMode.js
@@ -1,14 +1,14 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
var irc = {};
-Services.scriptloader.loadSubScript("resource:///components/irc.js", irc);
+Services.scriptloader.loadSubScript("resource:///modules/irc.jsm", irc);
Services.conversations.initConversations();
function FakeAccount() {
this.normalizeNick = irc.ircAccount.prototype.normalizeNick.bind(this);
}
FakeAccount.prototype = {
__proto__: irc.ircAccount.prototype,
setWhois: (n, f) => true,
--- a/chat/protocols/irc/test/test_splitLongMessages.js
+++ b/chat/protocols/irc/test/test_splitLongMessages.js
@@ -1,14 +1,14 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var irc = {};
-Services.scriptloader.loadSubScript("resource:///components/irc.js", irc);
+Services.scriptloader.loadSubScript("resource:///modules/irc.jsm", irc);
var messages = {
// Exactly 51 characters.
"This is a test.": ["This is a test."],
// Too long.
"This is a message that is too long.": [
"This is a",
"message that is",
--- a/chat/protocols/irc/test/test_tryNewNick.js
+++ b/chat/protocols/irc/test/test_tryNewNick.js
@@ -1,14 +1,14 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var irc = {};
-Services.scriptloader.loadSubScript("resource:///components/irc.js", irc);
+Services.scriptloader.loadSubScript("resource:///modules/irc.jsm", irc);
var fakeProto = {
id: "fake-proto",
options: { alternateNicks: "" },
_getOptionDefault(aOption) {
return this.options[aOption];
},
};
new file mode 100644
--- /dev/null
+++ b/chat/protocols/jsTest/components.conf
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ 'cid': '{a0774c5a-4aea-458b-9fbc-8d3cbf1a4630}',
+ 'contract_ids': ['@mozilla.org/chat/jstest;1'],
+ 'jsm': 'resource:///modules/jsTestProtocol.jsm',
+ 'constructor': 'JSTestProtocol',
+ 'categories': {'im-protocol-plugin': 'prpl-jstest'},
+ },
+]
rename from chat/protocols/jsTest/jsTestProtocol.js
rename to chat/protocols/jsTest/jsTestProtocol.jsm
--- a/chat/protocols/jsTest/jsTestProtocol.js
+++ b/chat/protocols/jsTest/jsTestProtocol.jsm
@@ -1,15 +1,15 @@
/* 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/. */
-var { XPCOMUtils, setTimeout } = ChromeUtils.import(
- "resource:///modules/imXPCOMUtils.jsm"
-);
+var EXPORTED_SYMBOLS = ["JSTestProtocol"];
+
+var { setTimeout } = ChromeUtils.import("resource:///modules/imXPCOMUtils.jsm");
var {
GenericAccountPrototype,
GenericConvIMPrototype,
GenericProtocolPrototype,
} = ChromeUtils.import("resource:///modules/jsProtoHelper.jsm");
function Conversation(aAccount) {
this._init(aAccount);
@@ -102,18 +102,18 @@ Account.prototype = {
required: true,
},
},
// Nothing to do.
unInit() {},
};
-function jsTestProtocol() {}
-jsTestProtocol.prototype = {
+function JSTestProtocol() {}
+JSTestProtocol.prototype = {
__proto__: GenericProtocolPrototype,
get name() {
return "JS Test";
},
options: {
text: { label: "Text option", default: "foo" },
bool: { label: "Boolean option", default: true },
int: { label: "Integer option", default: 42 },
@@ -133,12 +133,9 @@ jsTestProtocol.prototype = {
separator: "@",
defaultValue: "default.server",
reverse: true,
},
],
getAccount(aImAccount) {
return new Account(this, aImAccount);
},
- classID: Components.ID("{a0774c5a-4aea-458b-9fbc-8d3cbf1a4630}"),
};
-
-var NSGetFactory = XPCOMUtils.generateNSGetFactory([jsTestProtocol]);
deleted file mode 100644
--- a/chat/protocols/jsTest/jsTestProtocol.manifest
+++ /dev/null
@@ -1,3 +0,0 @@
-component {a0774c5a-4aea-458b-9fbc-8d3cbf1a4630} jsTestProtocol.js
-contract @mozilla.org/chat/jstest;1 {a0774c5a-4aea-458b-9fbc-8d3cbf1a4630}
-category im-protocol-plugin prpl-jstest @mozilla.org/chat/jstest;1
--- a/chat/protocols/jsTest/moz.build
+++ b/chat/protocols/jsTest/moz.build
@@ -1,11 +1,14 @@
# vim: set filetype=python:
# 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/.
if CONFIG['MOZ_DEBUG']:
- EXTRA_COMPONENTS += [
- 'jsTestProtocol.js',
- 'jsTestProtocol.manifest',
+ EXTRA_JS_MODULES += [
+ 'jsTestProtocol.jsm',
]
+ XPCOM_MANIFESTS += [
+ 'components.conf',
+ ]
+
new file mode 100644
--- /dev/null
+++ b/chat/protocols/matrix/components.conf
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ 'cid': '{e9653ac6-a671-11e6-bf84-60a44c717042}',
+ 'contract_ids': ['@mozilla.org/chat/matrix;1'],
+ 'jsm': 'resource:///modules/matrix.jsm',
+ 'constructor': 'MatrixProtocol',
+ 'categories': {'im-protocol-plugin': 'prpl-matrix'},
+ },
+]
rename from chat/protocols/matrix/matrix.js
rename to chat/protocols/matrix/matrix.jsm
--- a/chat/protocols/matrix/matrix.js
+++ b/chat/protocols/matrix/matrix.jsm
@@ -1,12 +1,14 @@
/* 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/. */
+var EXPORTED_SYMBOLS = ["MatrixProtocol"];
+
var { XPCOMUtils, nsSimpleEnumerator, l10nHelper } = ChromeUtils.import(
"resource:///modules/imXPCOMUtils.jsm"
);
var {
GenericAccountPrototype,
GenericConvChatPrototype,
GenericConvChatBuddyPrototype,
GenericProtocolPrototype,
@@ -355,13 +357,9 @@ MatrixProtocol.prototype = {
},
default: 443,
},
},
get chatHasTopic() {
return true;
},
-
- classID: Components.ID("{e9653ac6-a671-11e6-bf84-60a44c717042}"),
};
-
-var NSGetFactory = XPCOMUtils.generateNSGetFactory([MatrixProtocol]);
deleted file mode 100644
--- a/chat/protocols/matrix/matrix.manifest
+++ /dev/null
@@ -1,4 +0,0 @@
-component {e9653ac6-a671-11e6-bf84-60a44c717042} matrix.js
-contract @mozilla.org/chat/matrix;1 {e9653ac6-a671-11e6-bf84-60a44c717042}
-category im-protocol-plugin prpl-matrix @mozilla.org/chat/matrix;1
-
--- a/chat/protocols/matrix/moz.build
+++ b/chat/protocols/matrix/moz.build
@@ -5,20 +5,20 @@
# XPCSHELL_TESTS_MANIFESTS += []
DIRS += [
'lib',
'shims',
]
-EXTRA_COMPONENTS += [
- 'matrix.js',
- 'matrix.manifest',
-]
-
EXTRA_JS_MODULES += [
'matrix-sdk.jsm',
+ 'matrix.jsm',
]
JAR_MANIFESTS += [
'jar.mn',
]
+
+XPCOM_MANIFESTS += [
+ 'components.conf',
+]
new file mode 100644
--- /dev/null
+++ b/chat/protocols/odnoklassniki/components.conf
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ 'cid': '{29b09a83-81c1-2032-11e2-6d9bc4f8e969}',
+ 'contract_ids': ['@mozilla.org/chat/odnoklassniki;1'],
+ 'jsm': 'resource:///modules/odnoklassniki.jsm',
+ 'constructor': 'OdnoklassnikiProtocol',
+ 'categories': {'im-protocol-plugin': 'prpl-odnoklassniki'},
+ },
+]
--- a/chat/protocols/odnoklassniki/moz.build
+++ b/chat/protocols/odnoklassniki/moz.build
@@ -1,11 +1,14 @@
# vim: set filetype=python:
# 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/.
-EXTRA_COMPONENTS += [
- 'odnoklassniki.js',
- 'odnoklassniki.manifest',
+JAR_MANIFESTS += ['jar.mn']
+
+XPCOM_MANIFESTS += [
+ 'components.conf',
]
-JAR_MANIFESTS += ['jar.mn']
+EXTRA_JS_MODULES += [
+ 'odnoklassniki.jsm',
+]
rename from chat/protocols/odnoklassniki/odnoklassniki.js
rename to chat/protocols/odnoklassniki/odnoklassniki.jsm
--- a/chat/protocols/odnoklassniki/odnoklassniki.js
+++ b/chat/protocols/odnoklassniki/odnoklassniki.jsm
@@ -1,20 +1,22 @@
/* 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/. */
+var EXPORTED_SYMBOLS = ["OdnoklassnikiProtocol"];
+
var { XPCOMUtils, l10nHelper } = ChromeUtils.import(
"resource:///modules/imXPCOMUtils.jsm"
);
var { GenericProtocolPrototype } = ChromeUtils.import(
"resource:///modules/jsProtoHelper.jsm"
);
var { XMPPAccountPrototype } = ChromeUtils.import(
- "resource:///modules/xmpp.jsm"
+ "resource:///modules/xmpp-base.jsm"
);
var { XMPPSession, XMPPDefaultResource } = ChromeUtils.import(
"resource:///modules/xmpp-session.jsm"
);
XPCOMUtils.defineLazyGetter(this, "_", () =>
l10nHelper("chrome://chat/locale/xmpp.properties")
);
@@ -70,12 +72,9 @@ OdnoklassnikiProtocol.prototype = {
return "chrome://prpl-odnoklassniki/skin/";
},
get usernameEmptyText() {
return _("odnoklassniki.usernameHint");
},
getAccount(aImAccount) {
return new OdnoklassnikiAccount(this, aImAccount);
},
- classID: Components.ID("{29b09a83-81c1-2032-11e2-6d9bc4f8e969}"),
};
-
-var NSGetFactory = XPCOMUtils.generateNSGetFactory([OdnoklassnikiProtocol]);
deleted file mode 100644
--- a/chat/protocols/odnoklassniki/odnoklassniki.manifest
+++ /dev/null
@@ -1,3 +0,0 @@
-component {29b09a83-81c1-2032-11e2-6d9bc4f8e969} odnoklassniki.js
-contract @mozilla.org/chat/odnoklassniki;1 {29b09a83-81c1-2032-11e2-6d9bc4f8e969}
-category im-protocol-plugin prpl-odnoklassniki @mozilla.org/chat/odnoklassniki;1
new file mode 100644
--- /dev/null
+++ b/chat/protocols/skype/components.conf
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ 'cid': '{8446c0f6-9f59-4710-844e-eaa6c1f49d35}',
+ 'contract_ids': ['@mozilla.org/chat/skype;1'],
+ 'jsm': 'resource:///modules/skype.jsm',
+ 'constructor': 'SkypeProtocol',
+ 'categories': {'im-protocol-plugin': 'prpl-skype'},
+ },
+]
--- a/chat/protocols/skype/moz.build
+++ b/chat/protocols/skype/moz.build
@@ -1,13 +1,16 @@
# vim: set filetype=python:
# 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/.
XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
-EXTRA_COMPONENTS += [
- 'skype.js',
- 'skype.manifest',
+JAR_MANIFESTS += ['jar.mn']
+
+EXTRA_JS_MODULES += [
+ 'skype.jsm',
]
-JAR_MANIFESTS += ['jar.mn']
+XPCOM_MANIFESTS += [
+ 'components.conf',
+]
rename from chat/protocols/skype/skype.js
rename to chat/protocols/skype/skype.jsm
--- a/chat/protocols/skype/skype.js
+++ b/chat/protocols/skype/skype.jsm
@@ -1,12 +1,13 @@
/* 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/. */
+var EXPORTED_SYMBOLS = ["SkypeProtocol"];
var { httpRequest } = ChromeUtils.import("resource://gre/modules/Http.jsm");
var { StringToArrayBuffer } = ChromeUtils.import(
"resource:///modules/ArrayBufferUtils.jsm"
);
var { bigInt } = ChromeUtils.import("resource:///modules/BigInteger.jsm");
var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
var {
XPCOMUtils,
@@ -951,12 +952,9 @@ SkypeProtocol.prototype = {
get passwordOptional() {
return false;
},
getAccount(aImAccount) {
return new SkypeAccount(this, aImAccount);
},
- classID: Components.ID("{8446c0f6-9f59-4710-844e-eaa6c1f49d35}"),
};
-
-var NSGetFactory = XPCOMUtils.generateNSGetFactory([SkypeProtocol]);
deleted file mode 100644
--- a/chat/protocols/skype/skype.manifest
+++ /dev/null
@@ -1,3 +0,0 @@
-component {8446c0f6-9f59-4710-844e-eaa6c1f49d35} skype.js
-contract @mozilla.org/chat/skype;1 {8446c0f6-9f59-4710-844e-eaa6c1f49d35}
-category im-protocol-plugin prpl-skype @mozilla.org/chat/skype;1
--- a/chat/protocols/skype/test/test_MagicSha256.js
+++ b/chat/protocols/skype/test/test_MagicSha256.js
@@ -1,14 +1,14 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var skype = {};
-Services.scriptloader.loadSubScript("resource:///components/skype.js", skype);
+Services.scriptloader.loadSubScript("resource:///modules/skype.jsm", skype);
var data = {
"1416264993": "3a33ac47fe2ec1a33d569f4be5c69ddc",
"1416387358": "eca9716e1eedcbe93320ba794cea3388",
"1416392361": "2ed6fc80c3303caa137ae3fd4fcc7d80",
};
function run_test() {
--- a/chat/protocols/skype/test/test_contactUrlToName.js
+++ b/chat/protocols/skype/test/test_contactUrlToName.js
@@ -1,14 +1,14 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var skype = {};
-Services.scriptloader.loadSubScript("resource:///components/skype.js", skype);
+Services.scriptloader.loadSubScript("resource:///modules/skype.jsm", skype);
var data = {
"https://bay-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:clokep":
"clokep",
"https://bay-client-s.gateway.messenger.live.com/v1/users/8:clokep/presenceDocs/messagingService":
"clokep",
};
new file mode 100644
--- /dev/null
+++ b/chat/protocols/twitter/components.conf
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ 'cid': '{31082ff6-1de8-422b-ab60-ca0ac0b2af13}',
+ 'contract_ids': ['@mozilla.org/chat/twitter;1'],
+ 'jsm': 'resource:///modules/twitter.jsm',
+ 'constructor': 'TwitterProtocol',
+ 'categories': {'im-protocol-plugin': 'prpl-twitter'},
+ },
+]
--- a/chat/protocols/twitter/moz.build
+++ b/chat/protocols/twitter/moz.build
@@ -1,14 +1,15 @@
# vim: set filetype=python:
# 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/.
-EXTRA_COMPONENTS += [
- 'twitter.js',
- 'twitter.manifest',
+EXTRA_JS_MODULES += [
+ 'twitter-text.jsm',
+ 'twitter.jsm',
]
-EXTRA_JS_MODULES += [
- 'twitter-text.jsm',
+JAR_MANIFESTS += ['jar.mn']
+
+XPCOM_MANIFESTS += [
+ 'components.conf',
]
-JAR_MANIFESTS += ['jar.mn']
rename from chat/protocols/twitter/twitter.js
rename to chat/protocols/twitter/twitter.jsm
--- a/chat/protocols/twitter/twitter.js
+++ b/chat/protocols/twitter/twitter.jsm
@@ -1,12 +1,14 @@
/* 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/. */
+var EXPORTED_SYMBOLS = ["TwitterProtocol"];
+
var { httpRequest, percentEncode } = ChromeUtils.import(
"resource://gre/modules/Http.jsm"
);
var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
var {
XPCOMUtils,
setTimeout,
clearTimeout,
@@ -1567,12 +1569,9 @@ TwitterProtocol.prototype = {
aMsg.split(" ").forEach(account.stopFollowing, account);
return true;
},
},
],
getAccount(aImAccount) {
return new Account(this, aImAccount);
},
- classID: Components.ID("{31082ff6-1de8-422b-ab60-ca0ac0b2af13}"),
};
-
-var NSGetFactory = XPCOMUtils.generateNSGetFactory([TwitterProtocol]);
deleted file mode 100644
--- a/chat/protocols/twitter/twitter.manifest
+++ /dev/null
@@ -1,3 +0,0 @@
-component {31082ff6-1de8-422b-ab60-ca0ac0b2af13} twitter.js
-contract @mozilla.org/chat/twitter;1 {31082ff6-1de8-422b-ab60-ca0ac0b2af13}
-category im-protocol-plugin prpl-twitter @mozilla.org/chat/twitter;1
new file mode 100644
--- /dev/null
+++ b/chat/protocols/xmpp/components.conf
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ 'cid': '{dde786d1-6f59-43d0-9bc8-b505a757fb30}',
+ 'contract_ids': ['@mozilla.org/chat/xmpp;1'],
+ 'jsm': 'resource:///modules/xmpp.jsm',
+ 'constructor': 'XMPPProtocol',
+ 'categories': {'im-protocol-plugin': 'prpl-jabber'},
+ },
+]
--- a/chat/protocols/xmpp/moz.build
+++ b/chat/protocols/xmpp/moz.build
@@ -1,21 +1,21 @@
# vim: set filetype=python:
# 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/.
XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
-EXTRA_COMPONENTS += [
- 'xmpp.js',
- 'xmpp.manifest',
-]
-
EXTRA_JS_MODULES += [
'xmpp-authmechs.jsm',
+ 'xmpp-base.jsm',
'xmpp-commands.jsm',
'xmpp-session.jsm',
'xmpp-xml.jsm',
'xmpp.jsm',
]
JAR_MANIFESTS += ['jar.mn']
+
+XPCOM_MANIFESTS += [
+ 'components.conf',
+]
--- a/chat/protocols/xmpp/test/test_dnsSrv.js
+++ b/chat/protocols/xmpp/test/test_dnsSrv.js
@@ -1,14 +1,14 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var { XMPPAccountPrototype } = ChromeUtils.import(
- "resource:///modules/xmpp.jsm"
+ "resource:///modules/xmpp-base.jsm"
);
var { XMPPSession } = ChromeUtils.import(
"resource:///modules/xmpp-session.jsm"
);
var dns = {};
Services.scriptloader.loadSubScript("resource:///modules/DNS.jsm", dns);
--- a/chat/protocols/xmpp/test/test_parseJidAndNormalization.js
+++ b/chat/protocols/xmpp/test/test_parseJidAndNormalization.js
@@ -1,14 +1,14 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var { XMPPAccountPrototype } = ChromeUtils.import(
- "resource:///modules/xmpp.jsm"
+ "resource:///modules/xmpp-base.jsm"
);
var TEST_DATA = {
"abdelrhman@instantbird": {
node: "abdelrhman",
domain: "instantbird",
jid: "abdelrhman@instantbird",
normalized: "abdelrhman@instantbird",
--- a/chat/protocols/xmpp/test/test_parseVCard.js
+++ b/chat/protocols/xmpp/test/test_parseVCard.js
@@ -1,13 +1,13 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
var { XMPPAccountPrototype } = ChromeUtils.import(
- "resource:///modules/xmpp.jsm"
+ "resource:///modules/xmpp-base.jsm"
);
var { XMPPParser } = ChromeUtils.import("resource:///modules/xmpp-xml.jsm");
/*
* Open an input stream, instantiate an XMPP parser, and feed the input string
* into it. Then assert that the resulting vCard matches the expected result.
*/
function _test_vcard(aInput, aExpectedResult) {
copy from chat/protocols/xmpp/xmpp.jsm
copy to chat/protocols/xmpp/xmpp-base.jsm
deleted file mode 100644
--- a/chat/protocols/xmpp/xmpp.js
+++ /dev/null
@@ -1,106 +0,0 @@
-/* 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/. */
-
-var { XPCOMUtils, l10nHelper } = ChromeUtils.import(
- "resource:///modules/imXPCOMUtils.jsm"
-);
-var { GenericProtocolPrototype } = ChromeUtils.import(
- "resource:///modules/jsProtoHelper.jsm"
-);
-var { XMPPAccountPrototype } = ChromeUtils.import(
- "resource:///modules/xmpp.jsm"
-);
-
-XPCOMUtils.defineLazyGetter(this, "_", () =>
- l10nHelper("chrome://chat/locale/xmpp.properties")
-);
-
-function XMPPAccount(aProtoInstance, aImAccount) {
- this._init(aProtoInstance, aImAccount);
-}
-XMPPAccount.prototype = XMPPAccountPrototype;
-
-function XMPPProtocol() {
- ChromeUtils.import("resource:///modules/xmpp-commands.jsm", this);
- this.registerCommands();
-}
-XMPPProtocol.prototype = {
- __proto__: GenericProtocolPrototype,
- get normalizedName() {
- return "jabber";
- },
- get name() {
- return "XMPP";
- },
- get iconBaseURI() {
- return "chrome://prpl-jabber/skin/";
- },
- getAccount(aImAccount) {
- return new XMPPAccount(this, aImAccount);
- },
-
- usernameSplits: [
- {
- get label() {
- return _("options.domain");
- },
- separator: "@",
- defaultValue: "jabber.org",
- reverse: true,
- },
- ],
-
- options: {
- resource: {
- get label() {
- return _("options.resource");
- },
- default: "",
- },
- priority: {
- get label() {
- return _("options.priority");
- },
- default: 0,
- },
- connection_security: {
- get label() {
- return _("options.connectionSecurity");
- },
- listValues: {
- get require_tls() {
- return _("options.connectionSecurity.requireEncryption");
- },
- get opportunistic_tls() {
- return _("options.connectionSecurity.opportunisticTLS");
- },
- get allow_unencrypted_plain_auth() {
- return _("options.connectionSecurity.allowUnencryptedAuth");
- },
- // "old_ssl" and "none" are also supported, but not exposed in the UI.
- // Any unknown value will fallback to the opportunistic_tls behavior.
- },
- default: "require_tls",
- },
- server: {
- get label() {
- return _("options.connectServer");
- },
- default: "",
- },
- port: {
- get label() {
- return _("options.connectPort");
- },
- default: 5222,
- },
- },
- get chatHasTopic() {
- return true;
- },
-
- classID: Components.ID("{dde786d1-6f59-43d0-9bc8-b505a757fb30}"),
-};
-
-var NSGetFactory = XPCOMUtils.generateNSGetFactory([XMPPProtocol]);
--- a/chat/protocols/xmpp/xmpp.jsm
+++ b/chat/protocols/xmpp/xmpp.jsm
@@ -1,3324 +1,104 @@
/* 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/. */
-this.EXPORTED_SYMBOLS = [
- "XMPPConversationPrototype",
- "XMPPMUCConversationPrototype",
- "XMPPAccountBuddyPrototype",
- "XMPPAccountPrototype",
-];
+var EXPORTED_SYMBOLS = ["XMPPProtocol"];
-const { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
-const { Status } = ChromeUtils.import("resource:///modules/imStatusUtils.jsm");
-const {
- XPCOMUtils,
- setTimeout,
- clearTimeout,
- executeSoon,
- nsSimpleEnumerator,
- EmptyEnumerator,
- ClassInfo,
- l10nHelper,
-} = ChromeUtils.import("resource:///modules/imXPCOMUtils.jsm");
-var {
- GenericAccountPrototype,
- GenericAccountBuddyPrototype,
- GenericConvIMPrototype,
- GenericConvChatPrototype,
- GenericConversationPrototype,
- TooltipInfo,
-} = ChromeUtils.import("resource:///modules/jsProtoHelper.jsm");
-const { NormalizedMap } = ChromeUtils.import(
- "resource:///modules/NormalizedMap.jsm"
+var { XPCOMUtils, l10nHelper } = ChromeUtils.import(
+ "resource:///modules/imXPCOMUtils.jsm"
);
-var { Stanza, SupportedFeatures } = ChromeUtils.import(
- "resource:///modules/xmpp-xml.jsm"
-);
-var { XMPPSession } = ChromeUtils.import(
- "resource:///modules/xmpp-session.jsm"
-);
-
-ChromeUtils.defineModuleGetter(
- this,
- "DownloadUtils",
- "resource://gre/modules/DownloadUtils.jsm"
-);
-ChromeUtils.defineModuleGetter(
- this,
- "FileUtils",
- "resource://gre/modules/FileUtils.jsm"
+var { GenericProtocolPrototype } = ChromeUtils.import(
+ "resource:///modules/jsProtoHelper.jsm"
);
-ChromeUtils.defineModuleGetter(
- this,
- "NetUtil",
- "resource://gre/modules/NetUtil.jsm"
-);
-XPCOMUtils.defineLazyServiceGetter(
- this,
- "imgTools",
- "@mozilla.org/image/tools;1",
- "imgITools"
-);
-XPCOMUtils.defineLazyServiceGetter(
- this,
- "UuidGenerator",
- "@mozilla.org/uuid-generator;1",
- "nsIUUIDGenerator"
+var { XMPPAccountPrototype } = ChromeUtils.import(
+ "resource:///modules/xmpp-base.jsm"
);
XPCOMUtils.defineLazyGetter(this, "_", () =>
l10nHelper("chrome://chat/locale/xmpp.properties")
);
-XPCOMUtils.defineLazyGetter(this, "TXTToHTML", function() {
- let cs = Cc["@mozilla.org/txttohtmlconv;1"].getService(Ci.mozITXTToHTMLConv);
- return aTxt => cs.scanTXT(aTxt, cs.kEntities);
-});
-
-// Parses the status from a presence stanza into an object of statusType,
-// statusText and idleSince.
-function parseStatus(aStanza) {
- let statusType = Ci.imIStatusInfo.STATUS_AVAILABLE;
- let show = aStanza.getElement(["show"]);
- if (show) {
- show = show.innerText;
- if (show == "away") {
- statusType = Ci.imIStatusInfo.STATUS_AWAY;
- } else if (show == "chat") {
- statusType = Ci.imIStatusInfo.STATUS_AVAILABLE; // FIXME
- } else if (show == "dnd") {
- statusType = Ci.imIStatusInfo.STATUS_UNAVAILABLE;
- } else if (show == "xa") {
- statusType = Ci.imIStatusInfo.STATUS_IDLE;
- }
- }
-
- let idleSince = 0;
- let date = _getDelay(aStanza);
- if (date) {
- idleSince = date.getTime();
- }
-
- let query = aStanza.getElement(["query"]);
- if (query && query.uri == Stanza.NS.last) {
- let now = Math.floor(Date.now() / 1000);
- idleSince = now - parseInt(query.attributes.seconds, 10);
- statusType = Ci.imIStatusInfo.STATUS_IDLE;
- }
-
- // Mark official Android clients as mobile.
- const kAndroidNodeURI = "http://www.android.com/gtalk/client/caps";
- if (
- aStanza
- .getChildrenByNS(Stanza.NS.caps)
- .some(s => s.localName == "c" && s.attributes.node == kAndroidNodeURI)
- ) {
- statusType = Ci.imIStatusInfo.STATUS_MOBILE;
- }
-
- let status = aStanza.getElement(["status"]);
- status = status ? status.innerText : "";
-
- return { statusType, statusText: status, idleSince };
-}
-
-// Returns a Date object for the delay value (stamp) in aStanza if it exists,
-// otherwise returns undefined.
-function _getDelay(aStanza) {
- // XEP-0203: Delayed Delivery.
- let date;
- let delay = aStanza.getElement(["delay"]);
- if (delay && delay.uri == Stanza.NS.delay) {
- if (delay.attributes.stamp) {
- date = new Date(delay.attributes.stamp);
- }
- }
- if (date && isNaN(date.getTime())) {
- return undefined;
- }
-
- return date;
-}
-
-// Writes aMsg in aConv as an outgoing message with optional date as the
-// message may be sent from another client.
-function _displaySentMsg(aConv, aMsg, aDate) {
- let who;
- if (aConv._account._connection) {
- who = aConv._account._connection._jid.jid;
- }
- if (!who) {
- who = aConv._account.name;
- }
-
- let flags = { outgoing: true };
- flags._alias = aConv.account.alias || aConv.account.statusInfo.displayName;
-
- if (aDate) {
- flags.time = aDate / 1000;
- flags.delayed = true;
- }
- aConv.writeMessage(who, aMsg, flags);
-}
-
-// The timespan after which we consider roomInfo to be stale.
-var kListRefreshInterval = 12 * 60 * 60 * 1000; // 12 hours.
-
-/* This is an ordered list, used to determine chat buddy flags:
- * index < member -> noFlags
- * index = member -> voiced
- * moderator -> halfOp
- * admin -> op
- * owner -> founder
- */
-var kRoles = [
- "outcast",
- "visitor",
- "participant",
- "member",
- "moderator",
- "admin",
- "owner",
-];
-
-function MUCParticipant(aNick, aJid, aPresenceStanza) {
- this._jid = aJid;
- this.name = aNick;
- this.onPresenceStanza(aPresenceStanza);
+function XMPPAccount(aProtoInstance, aImAccount) {
+ this._init(aProtoInstance, aImAccount);
}
-MUCParticipant.prototype = {
- __proto__: ClassInfo("prplIConvChatBuddy", "XMPP ConvChatBuddy object"),
-
- buddy: false,
-
- // The occupant jid of the participant which is of the form room@domain/nick.
- _jid: null,
-
- // The real jid of the participant which is of the form local@domain/resource.
- accountJid: null,
-
- statusType: null,
- statusText: null,
- get alias() {
- return this.name;
- },
-
- role: 2, // "participant" by default
-
- // Called when a presence stanza is received for this participant.
- onPresenceStanza(aStanza) {
- let statusInfo = parseStatus(aStanza);
- this.statusType = statusInfo.statusType;
- this.statusText = statusInfo.statusText;
-
- let x = aStanza.children.filter(
- child => child.localName == "x" && child.uri == Stanza.NS.muc_user
- );
- if (x.length == 0) {
- return;
- }
-
- // XEP-0045 (7.2.3): We only expect a single <x/> element of this namespace,
- // so we ignore any others.
- x = x[0];
-
- let item = x.getElement(["item"]);
- if (!item) {
- return;
- }
-
- this.role = Math.max(
- kRoles.indexOf(item.attributes.role),
- kRoles.indexOf(item.attributes.affiliation)
- );
-
- let accountJid = item.attributes.jid;
- if (accountJid) {
- this.accountJid = accountJid;
- }
- },
-
- get noFlags() {
- return this.role < kRoles.indexOf("member");
- },
- get voiced() {
- return this.role == kRoles.indexOf("member");
- },
- get halfOp() {
- return this.role == kRoles.indexOf("moderator");
- },
- get op() {
- return this.role == kRoles.indexOf("admin");
- },
- get founder() {
- return this.role == kRoles.indexOf("owner");
- },
- typing: false,
-};
-
-// MUC (Multi-User Chat)
-var XMPPMUCConversationPrototype = {
- __proto__: GenericConvChatPrototype,
- // By default users are not in a MUC.
- _left: true,
-
- // Tracks all received messages to avoid possible duplication if the server
- // sends us the last few messages again when we rejoin a room.
- _messageIds: new Set(),
-
- _init(aAccount, aJID, aNick) {
- this._messageIds = new Set();
- GenericConvChatPrototype._init.call(this, aAccount, aJID, aNick);
- },
-
- _targetResource: "",
-
- // True while we are rejoining a room previously parted by the user.
- _rejoined: false,
-
- get topic() {
- return this._topic;
- },
- set topic(aTopic) {
- // XEP-0045 (8.1): Modifying the room subject.
- let subject = Stanza.node("subject", null, null, aTopic.trim());
- let s = Stanza.message(
- this.name,
- null,
- null,
- { type: "groupchat" },
- subject
- );
- let notAuthorized = _("conversation.error.changeTopicFailedNotAuthorized");
- this._account.sendStanza(
- s,
- this._account.handleErrors(
- {
- forbidden: notAuthorized,
- notAcceptable: notAuthorized,
- itemNotFound: notAuthorized,
- },
- this
- )
- );
- },
- get topicSettable() {
- return true;
- },
-
- /* Called when the user enters a chat message */
- sendMsg(aMsg) {
- // XEP-0045 (7.4): Sending a message to all occupants in a room.
- let s = Stanza.message(this.name, aMsg, null, { type: "groupchat" });
- let notInRoom = _(
- "conversation.error.sendFailedAsNotInRoom",
- this.name,
- aMsg
- );
- this._account.sendStanza(
- s,
- this._account.handleErrors(
- {
- itemNotFound: notInRoom,
- notAcceptable: notInRoom,
- },
- this
- )
- );
- },
-
- /* Called by the account when a presence stanza is received for this muc */
- onPresenceStanza(aStanza) {
- let from = aStanza.attributes.from;
- let nick = this._account._parseJID(from).resource;
- let jid = this._account.normalize(from);
- let x = aStanza.getElements(["x"]).find(e => e.uri == Stanza.NS.muc_user);
-
- // Check if the join failed.
- if (this.left && aStanza.attributes.type == "error") {
- let error = this._account.parseError(aStanza);
- let message;
- switch (error.condition) {
- case "not-authorized":
- case "registration-required":
- // XEP-0045 (7.2.7): Members-Only Rooms.
- message = _("conversation.error.joinFailedNotAuthorized");
- break;
- case "not-allowed":
- message = _("conversation.error.creationFailedNotAllowed");
- break;
- case "remote-server-not-found":
- message = _(
- "conversation.error.joinFailedRemoteServerNotFound",
- this.name
- );
- break;
- case "forbidden":
- // XEP-0045 (7.2.8): Banned users.
- message = _("conversation.error.joinForbidden", this.name);
- break;
- default:
- message = _("conversation.error.joinFailed", this.name);
- this.ERROR("Failed to join MUC: " + aStanza.convertToString());
- break;
- }
- this.writeMessage(this.name, message, { system: true, error: true });
- this.joining = false;
- return;
- }
-
- if (!x) {
- this.WARN(
- "Received a MUC presence stanza without an x element or " +
- "with a namespace we don't handle."
- );
- return;
- }
- let codes = x.getElements(["status"]).map(elt => elt.attributes.code);
- let item = x.getElement(["item"]);
-
- // Changes the nickname of a participant for this muc.
- let changeNick = () => {
- if (!item || !item.attributes.nick) {
- this.WARN(
- "Received a MUC presence code 303 or 210 stanza without an " +
- "item element or a nick attribute."
- );
- return;
- }
- let newNick = item.attributes.nick;
- this.updateNick(nick, newNick, nick == this.nick);
- };
-
- if (aStanza.attributes.type == "unavailable") {
- if (!this._participants.has(nick)) {
- this.WARN(
- "received unavailable presence for an unknown MUC participant: " +
- from
- );
- return;
- }
- if (codes.includes("303")) {
- // XEP-0045 (7.6): Changing Nickname.
- // Service Updates Nick for user.
- changeNick();
- return;
- }
- if (item && item.attributes.role == "none") {
- // XEP-0045: an occupant has left the room.
- this.removeParticipant(nick);
-
- // Who caused the participant to leave the room.
- let actor = item.getElement(["actor"]);
- let actorNick = actor ? actor.attributes.nick : "";
- let isActor = actorNick ? ".actor" : "";
-
- // Why the participant left.
- let reasonNode = item.getElement(["reason"]);
- let reason = reasonNode ? reasonNode.innerText : "";
- let isReason = reason ? ".reason" : "";
+XMPPAccount.prototype = XMPPAccountPrototype;
- let isYou = nick == this.nick ? ".you" : "";
- let affectedNick = isYou ? "" : nick;
- if (isYou) {
- this.left = true;
- }
-
- let message;
- if (codes.includes("301")) {
- // XEP-0045 (9.1): Banning a User.
- message = "conversation.message.banned";
- } else if (codes.includes("307")) {
- // XEP-0045 (8.2): Kicking an Occupant.
- message = "conversation.message.kicked";
- } else if (codes.includes("322") || codes.includes("321")) {
- // XEP-0045: Inform user that he or she is being removed from the
- // room because the room has been changed to members-only and the
- // user is not a member.
- message = "conversation.message.removedNonMember";
- } else if (codes.includes("332")) {
- // XEP-0045: Inform user that he or she is being removed from the
- // room because the MUC service is being shut down.
- message = "conversation.message.mucShutdown";
-
- // The reason here just duplicates what's in the system message.
- reason = isReason = "";
- } else {
- // XEP-0045 (7.14): Received when the user parts a room.
- message = "conversation.message.parted";
-
- // The reason is in a status element in this case.
- reasonNode = aStanza.getElement(["status"]);
- reason = reasonNode ? reasonNode.innerText : "";
- isReason = reason ? ".reason" : "";
- }
-
- if (message) {
- let messageID = message + isYou + isActor + isReason;
- let params = [actorNick, affectedNick, reason].filter(s => s);
- this.writeMessage(this.name, _(messageID, ...params), {
- system: true,
- });
- }
- } else {
- this.WARN("Unhandled type==unavailable MUC presence stanza.");
- }
- return;
- }
-
- if (codes.includes("201")) {
- // XEP-0045 (10.1): Creating room.
- // Service Acknowledges Room Creation
- // and Room is awaiting configuration.
- // XEP-0045 (10.1.2): Instant room.
- let query = Stanza.node(
- "query",
- Stanza.NS.muc_owner,
- null,
- Stanza.node("x", Stanza.NS.xdata, { type: "submit" })
- );
- let s = Stanza.iq("set", null, jid, query);
- this._account.sendStanza(s, aStanzaReceived => {
- if (aStanzaReceived.attributes.type != "result") {
- return false;
- }
-
- // XEP-0045: Service Informs New Room Owner of Success
- // for instant and reserved rooms.
- this.left = false;
- this.joining = false;
- return true;
- });
- } else if (codes.includes("210")) {
- // XEP-0045 (7.6): Changing Nickname.
- // Service modifies this user's nickname in accordance with local service
- // policies.
- changeNick();
- return;
- } else if (codes.includes("110")) {
- // XEP-0045: Room exists and joined successfully.
- this.left = false;
- this.joining = false;
- // TODO (Bug 1172350): Implement Service Discovery Extensions (XEP-0128) to obtain
- // configuration of this room.
- }
-
- if (!this._participants.get(nick)) {
- let participant = new MUCParticipant(nick, from, aStanza);
- this._participants.set(nick, participant);
- this.notifyObservers(
- new nsSimpleEnumerator([participant]),
- "chat-buddy-add"
- );
- if (this.nick != nick && !this.joining) {
- this.writeMessage(this.name, _("conversation.message.join", nick), {
- system: true,
- });
- } else if (this.nick == nick && this._rejoined) {
- this.writeMessage(this.name, _("conversation.message.rejoined"), {
- system: true,
- });
- this._rejoined = false;
- }
- } else {
- this._participants.get(nick).onPresenceStanza(aStanza);
- this.notifyObservers(this._participants.get(nick), "chat-buddy-update");
- }
- },
-
- /* Called by the account when a message is received for this muc */
- incomingMessage(aMsg, aStanza, aDate) {
- let from = this._account._parseJID(aStanza.attributes.from).resource;
- let id = aStanza.attributes.id;
- let flags = {};
- if (!from) {
- flags.system = true;
- from = this.name;
- } else if (aStanza.attributes.type == "error") {
- aMsg = _("conversation.error.notDelivered", aMsg);
- flags.system = true;
- flags.error = true;
- } else if (from == this._nick) {
- flags.outgoing = true;
- } else {
- flags.incoming = true;
- }
- if (aDate) {
- flags.time = aDate / 1000;
- flags.delayed = true;
- }
- if (id) {
- // Checks if a message exists in conversation to avoid duplication.
- if (this._messageIds.has(id)) {
- return;
- }
- this._messageIds.add(id);
- }
- this.writeMessage(from, aMsg, flags);
- },
-
- getNormalizedChatBuddyName(aNick) {
- return this._account.normalizeFullJid(this.name + "/" + aNick);
- },
-
- // Leaves MUC conversation.
- part(aMsg = null) {
- let s = Stanza.presence(
- { to: this.name + "/" + this._nick, type: "unavailable" },
- aMsg ? Stanza.node("status", null, null, aMsg.trim()) : null
- );
- this._account.sendStanza(s);
- delete this.chatRoomFields;
+function XMPPProtocol() {
+ ChromeUtils.import("resource:///modules/xmpp-commands.jsm", this);
+ this.registerCommands();
+}
+XMPPProtocol.prototype = {
+ __proto__: GenericProtocolPrototype,
+ get normalizedName() {
+ return "jabber";
},
-
- // Invites a user to MUC conversation.
- invite(aJID, aMsg = null) {
- // XEP-0045 (7.8): Inviting Another User to a Room.
- // XEP-0045 (7.8.2): Mediated Invitation.
- let invite = Stanza.node(
- "invite",
- null,
- { to: aJID },
- aMsg ? Stanza.node("reason", null, null, aMsg) : null
- );
- let x = Stanza.node("x", Stanza.NS.muc_user, null, invite);
- let s = Stanza.node("message", null, { to: this.name }, x);
- this._account.sendStanza(
- s,
- this._account.handleErrors(
- {
- forbidden: _("conversation.error.inviteFailedForbidden"),
- // ejabberd uses error not-allowed to indicate that this account does not
- // have the required privileges to invite users instead of forbidden error,
- // and this is not mentioned in the spec (XEP-0045).
- notAllowed: _("conversation.error.inviteFailedForbidden"),
- itemNotFound: _("conversation.error.failedJIDNotFound", aJID),
- },
- this
- )
- );
- },
-
- // Bans a participant from MUC conversation.
- ban(aNickName, aMsg = null) {
- // XEP-0045 (9.1): Banning a User.
- let participant = this._participants.get(aNickName);
- if (!participant) {
- this.writeMessage(
- this.name,
- _("conversation.error.nickNotInRoom", aNickName),
- { system: true }
- );
- return;
- }
- if (!participant.accountJid) {
- this.writeMessage(
- this.name,
- _("conversation.error.banCommandAnonymousRoom"),
- { system: true }
- );
- return;
- }
-
- let attributes = { affiliation: "outcast", jid: participant.accountJid };
- let item = Stanza.node(
- "item",
- null,
- attributes,
- aMsg ? Stanza.node("reason", null, null, aMsg) : null
- );
- let s = Stanza.iq(
- "set",
- null,
- this.name,
- Stanza.node("query", Stanza.NS.muc_admin, null, item)
- );
- this._account.sendStanza(s, this._banKickHandler, this);
- },
-
- // Kicks a participant from MUC conversation.
- kick(aNickName, aMsg = null) {
- // XEP-0045 (8.2): Kicking an Occupant.
- let attributes = { role: "none", nick: aNickName };
- let item = Stanza.node(
- "item",
- null,
- attributes,
- aMsg ? Stanza.node("reason", null, null, aMsg) : null
- );
- let s = Stanza.iq(
- "set",
- null,
- this.name,
- Stanza.node("query", Stanza.NS.muc_admin, null, item)
- );
- this._account.sendStanza(s, this._banKickHandler, this);
- },
-
- // Callback for ban and kick commands.
- _banKickHandler(aStanza) {
- return this._account._handleResult(
- {
- notAllowed: _("conversation.error.banKickCommandNotAllowed"),
- conflict: _("conversation.error.banKickCommandConflict"),
- },
- this
- )(aStanza);
+ get name() {
+ return "XMPP";
},
-
- // Changes nick in MUC conversation to a new one.
- setNick(aNewNick) {
- // XEP-0045 (7.6): Changing Nickname.
- let s = Stanza.presence({ to: this.name + "/" + aNewNick }, null);
- this._account.sendStanza(
- s,
- this._account.handleErrors(
- {
- // XEP-0045 (7.6): Changing Nickname (example 53).
- // TODO: We should discover if the user has a reserved nickname (maybe
- // before joining a room), cf. XEP-0045 (7.12).
- notAcceptable: _(
- "conversation.error.changeNickFailedNotAcceptable",
- aNewNick
- ),
- // XEP-0045 (7.2.9): Nickname Conflict.
- conflict: _("conversation.error.changeNickFailedConflict", aNewNick),
- },
- this
- )
- );
- },
-
- // Called by the account when a message stanza is received for this muc and
- // needs to be handled.
- onMessageStanza(aStanza) {
- let x = aStanza.getElement(["x"]);
- let decline = x.getElement(["decline"]);
- if (decline) {
- // XEP-0045 (7.8): Inviting Another User to a Room.
- // XEP-0045 (7.8.2): Mediated Invitation.
- let invitee = decline.attributes.jid;
- let reasonNode = decline.getElement(["reason"]);
- let reason = reasonNode ? reasonNode.innerText : "";
- let msg;
- if (reason) {
- msg = _(
- "conversation.message.invitationDeclined.reason",
- invitee,
- reason
- );
- } else {
- msg = _("conversation.message.invitationDeclined", invitee);
- }
-
- this.writeMessage(this.name, msg, { system: true });
- } else {
- this.WARN("Unhandled message stanza.");
- }
+ get iconBaseURI() {
+ return "chrome://prpl-jabber/skin/";
},
-
- /* Called when the user closed the conversation */
- close() {
- if (!this.left) {
- this.part();
- }
- GenericConvChatPrototype.close.call(this);
- },
- unInit() {
- this._account.removeConversation(this.name);
- GenericConvChatPrototype.unInit.call(this);
- },
-};
-function XMPPMUCConversation(aAccount, aJID, aNick) {
- this._init(aAccount, aJID, aNick);
-}
-XMPPMUCConversation.prototype = XMPPMUCConversationPrototype;
-
-/* Helper class for buddy conversations */
-var XMPPConversationPrototype = {
- __proto__: GenericConvIMPrototype,
-
- _typingTimer: null,
- supportChatStateNotifications: true,
- _typingState: "active",
-
- // Indicates that current conversation is with a MUC participant and the
- // recipient jid (stored in the userName) is of the form room@domain/nick.
- _isMucParticipant: false,
-
- get buddy() {
- return this._account._buddies.get(this.name);
- },
- get title() {
- return this.contactDisplayName;
- },
- get contactDisplayName() {
- return this.buddy ? this.buddy.contactDisplayName : this.name;
- },
- get userName() {
- return this.buddy ? this.buddy.userName : this.name;
- },
-
- // Returns jid (room@domain/nick) if it is with a MUC participant, and the
- // name of conversation otherwise.
- get normalizedName() {
- if (this._isMucParticipant) {
- return this._account.normalizeFullJid(this.name);
- }
- return this._account.normalize(this.name);
+ getAccount(aImAccount) {
+ return new XMPPAccount(this, aImAccount);
},
- // Used to avoid showing full jids in typing notifications.
- get shortName() {
- if (this.buddy) {
- return this.buddy.contactDisplayName;
- }
-
- let jid = this._account._parseJID(this.name);
- if (!jid) {
- return this.name;
- }
-
- // Returns nick of the recipient if conversation is with a participant of
- // a MUC we are in as jid of the recipient is of the form room@domain/nick.
- if (this._isMucParticipant) {
- return jid.resource;
- }
-
- return jid.node;
- },
-
- get shouldSendTypingNotifications() {
- return (
- this.supportChatStateNotifications &&
- Services.prefs.getBoolPref("purple.conversations.im.send_typing")
- );
- },
-
- /* Called when the user is typing a message
- * aString - the currently typed message
- * Returns the number of characters that can still be typed */
- sendTyping(aString) {
- if (!this.shouldSendTypingNotifications) {
- return Ci.prplIConversation.NO_TYPING_LIMIT;
- }
-
- this._cancelTypingTimer();
- if (aString.length) {
- this._typingTimer = setTimeout(this.finishedComposing.bind(this), 10000);
- }
-
- this._setTypingState(aString.length ? "composing" : "active");
-
- return Ci.prplIConversation.NO_TYPING_LIMIT;
- },
-
- finishedComposing() {
- if (!this.shouldSendTypingNotifications) {
- return;
- }
-
- this._setTypingState("paused");
- },
-
- _setTypingState(aNewState) {
- if (this._typingState == aNewState) {
- return;
- }
-
- let s = Stanza.message(this.to, null, aNewState);
-
- // We don't care about errors in response to typing notifications
- // (e.g. because the user has left the room when talking to a MUC
- // participant).
- this._account.sendStanza(s, () => true);
-
- this._typingState = aNewState;
- },
- _cancelTypingTimer() {
- if (this._typingTimer) {
- clearTimeout(this._typingTimer);
- delete this._typingTimer;
- }
- },
-
- // Holds the resource of user that you are currently talking to, but if the
- // user is a participant of a MUC we are in, holds the nick of user you are
- // talking to.
- _targetResource: "",
-
- get to() {
- if (!this._targetResource || this._isMucParticipant) {
- return this.userName;
- }
- return this.userName + "/" + this._targetResource;
- },
-
- /* Called when the user enters a chat message */
- sendMsg(aMsg) {
- this._cancelTypingTimer();
- let cs = this.shouldSendTypingNotifications ? "active" : null;
- let s = Stanza.message(this.to, aMsg, cs);
- this._account.sendStanza(s);
- _displaySentMsg(this, aMsg);
- delete this._typingState;
- },
-
- // Invites the contact to a MUC room.
- invite(aRoomJid, aPassword = null) {
- // XEP-0045 (7.8): Inviting Another User to a Room.
- // XEP-0045 (7.8.1) and XEP-0249: Direct Invitation.
- let x = Stanza.node("x", Stanza.NS.conference, {
- jid: aRoomJid,
- password: aPassword,
- });
- this._account.sendStanza(Stanza.node("message", null, { to: this.to }, x));
- },
-
- // Query the user for its Software Version.
- // XEP-0092: Software Version.
- getVersion() {
- // TODO: Use Service Discovery to determine if the user's client supports
- // jabber:iq:version protocol.
-
- let s = Stanza.iq(
- "get",
- null,
- this.to,
- Stanza.node("query", Stanza.NS.version)
- );
- this._account.sendStanza(s, aStanza => {
- // TODO: handle other errors that can result from querying
- // user for its software version.
- if (
- this._account.handleErrors(
- {
- default: _("conversation.error.version.unknown"),
- },
- this
- )(aStanza)
- ) {
- return;
- }
-
- let query = aStanza.getElement(["query"]);
- if (!query || query.uri != Stanza.NS.version) {
- this.WARN(
- "Received a response to version query which does not " +
- "contain query element or 'jabber:iq:version' namespace."
- );
- return;
- }
-
- let name = query.getElement(["name"]);
- let version = query.getElement(["version"]);
- if (!name || !version) {
- // XEP-0092: name and version elements are REQUIRED.
- this.WARN(
- "Received a response to version query which does not " +
- "contain name or version."
- );
- return;
- }
-
- let messageID = "conversation.message.version";
- let params = [this.shortName, name.innerText, version.innerText];
-
- // XEP-0092: os is OPTIONAL.
- let os = query.getElement(["os"]);
- if (os) {
- params.push(os.innerText);
- messageID += "WithOS";
- }
-
- this.writeMessage(this.name, _(messageID, ...params), { system: true });
- });
- },
-
- /* Perform entity escaping before displaying the message. We assume incoming
- messages have already been escaped, and will otherwise be filtered. */
- prepareForDisplaying(aMsg) {
- if (aMsg.outgoing && !aMsg.system) {
- aMsg.displayMessage = TXTToHTML(aMsg.displayMessage);
- }
- GenericConversationPrototype.prepareForDisplaying.apply(this, arguments);
- },
-
- /* Called by the account when a message is received from the buddy */
- incomingMessage(aMsg, aStanza, aDate) {
- let from = aStanza.attributes.from;
- this._targetResource = this._account._parseJID(from).resource;
- let flags = {};
- let error = this._account.parseError(aStanza);
- if (error) {
- let norm = this._account.normalize(from);
- let muc = this._account._mucs.get(norm);
-
- if (!aMsg) {
- // Failed outgoing message.
- switch (error.condition) {
- case "remote-server-not-found":
- aMsg = _("conversation.error.remoteServerNotFound");
- break;
- case "service-unavailable":
- aMsg = _(
- "conversation.error.sendServiceUnavailable",
- this.shortName
- );
- break;
- default:
- aMsg = _("conversation.error.unknownSendError");
- break;
- }
- } else if (
- this._isMucParticipant &&
- muc &&
- !muc.left &&
- error.condition == "item-not-found"
- ) {
- // XEP-0045 (7.5): MUC private messages.
- // If we try to send to participant not in a room we are in.
- aMsg = _(
- "conversation.error.sendFailedAsRecipientNotInRoom",
- this._targetResource,
- aMsg
- );
- } else if (
- this._isMucParticipant &&
- (error.condition == "item-not-found" ||
- error.condition == "not-acceptable")
- ) {
- // If we left a room and try to send to a participant in it or the
- // room is removed.
- aMsg = _(
- "conversation.error.sendFailedAsNotInRoom",
- this._account.normalize(from),
- aMsg
- );
- } else {
- aMsg = _("conversation.error.notDelivered", aMsg);
- }
- flags.system = true;
- flags.error = true;
- } else {
- flags = { incoming: true, _alias: this.contactDisplayName };
- }
- if (aDate) {
- flags.time = aDate / 1000;
- flags.delayed = true;
- }
- this.writeMessage(from, aMsg, flags);
- },
-
- /* Called when the user closed the conversation */
- close() {
- // TODO send the stanza indicating we have left the conversation?
- GenericConvIMPrototype.close.call(this);
- },
- unInit() {
- this._account.removeConversation(this.normalizedName);
- GenericConvIMPrototype.unInit.call(this);
- },
-};
-
-// Creates XMPP conversation.
-function XMPPConversation(aAccount, aNormalizedName, aMucParticipant) {
- this._init(aAccount, aNormalizedName);
- if (aMucParticipant) {
- this._isMucParticipant = true;
- }
-}
-XMPPConversation.prototype = XMPPConversationPrototype;
-
-/* Helper class for buddies */
-var XMPPAccountBuddyPrototype = {
- __proto__: GenericAccountBuddyPrototype,
-
- subscription: "none",
- // Returns a list of TooltipInfo objects to be displayed when the user
- // hovers over the buddy.
- getTooltipInfo() {
- if (!this._account.connected) {
- return null;
- }
-
- let tooltipInfo = [];
- if (this._resources) {
- for (let r in this._resources) {
- let status = this._resources[r];
- let statusString = Status.toLabel(status.statusType);
- if (
- status.statusType == Ci.imIStatusInfo.STATUS_IDLE &&
- status.idleSince
- ) {
- let now = Math.floor(Date.now() / 1000);
- let valuesAndUnits = DownloadUtils.convertTimeUnits(
- now - status.idleSince
- );
- if (!valuesAndUnits[2]) {
- valuesAndUnits.splice(2, 2);
- }
- statusString += " (" + valuesAndUnits.join(" ") + ")";
- }
- if (status.statusText) {
- statusString += " - " + status.statusText;
- }
- let label = r ? _("tooltip.status", r) : _("tooltip.statusNoResource");
- tooltipInfo.push(new TooltipInfo(label, statusString));
- }
- }
-
- // The subscription value is interesting to display only in unusual cases.
- if (this.subscription != "both") {
- tooltipInfo.push(
- new TooltipInfo(_("tooltip.subscription"), this.subscription)
- );
- }
-
- return new nsSimpleEnumerator(tooltipInfo);
- },
-
- // _rosterAlias is the value stored in the roster on the XMPP
- // server. For most servers we will be read/write.
- _rosterAlias: "",
- set rosterAlias(aNewAlias) {
- let old = this.displayName;
- this._rosterAlias = aNewAlias;
- if (old != this.displayName) {
- this._notifyObservers("display-name-changed", old);
- }
- },
- _vCardReceived: false,
- // _vCardFormattedName is the display name the contact has set for
- // himself in his vCard. It's read-only from our point of view.
- _vCardFormattedName: "",
- set vCardFormattedName(aNewFormattedName) {
- let old = this.displayName;
- this._vCardFormattedName = aNewFormattedName;
- if (old != this.displayName) {
- this._notifyObservers("display-name-changed", old);
- }
- },
-
- // _serverAlias is set by jsProtoHelper to the value we cached in sqlite.
- // Use it only if we have neither of the other two values; usually because
- // we haven't connected to the server yet.
- get serverAlias() {
- return this._rosterAlias || this._vCardFormattedName || this._serverAlias;
- },
- set serverAlias(aNewAlias) {
- if (!this._rosterItem) {
- this.ERROR(
- "attempting to update the server alias of an account buddy " +
- "for which we haven't received a roster item."
- );
- return;
- }
-
- let item = this._rosterItem;
- if (aNewAlias) {
- item.attributes.name = aNewAlias;
- } else if ("name" in item.attributes) {
- delete item.attributes.name;
- }
+ usernameSplits: [
+ {
+ get label() {
+ return _("options.domain");
+ },
+ separator: "@",
+ defaultValue: "jabber.org",
+ reverse: true,
+ },
+ ],
- let s = Stanza.iq(
- "set",
- null,
- null,
- Stanza.node("query", Stanza.NS.roster, null, item)
- );
- this._account.sendStanza(s);
-
- // If we are going to change the alias on the server, discard the cached
- // value that we got from our local sqlite storage at startup.
- delete this._serverAlias;
- },
-
- /* Display name of the buddy */
- get contactDisplayName() {
- return this.buddy.contact.displayName || this.displayName;
- },
-
- get tag() {
- return this._tag;
- },
- set tag(aNewTag) {
- let oldTag = this._tag;
- if (oldTag.name == aNewTag.name) {
- this.ERROR("attempting to set the tag to the same value");
- return;
- }
-
- this._tag = aNewTag;
- Services.contacts.accountBuddyMoved(this, oldTag, aNewTag);
-
- if (!this._rosterItem) {
- this.ERROR(
- "attempting to change the tag of an account buddy without roster item"
- );
- return;
- }
-
- let item = this._rosterItem;
- let oldXML = item.getXML();
- // Remove the old tag if it was listed in the roster item.
- item.children = item.children.filter(
- c => c.qName != "group" || c.innerText != oldTag.name
- );
- // Ensure the new tag is listed.
- let newTagName = aNewTag.name;
- if (!item.getChildren("group").some(g => g.innerText == newTagName)) {
- item.addChild(Stanza.node("group", null, null, newTagName));
- }
- // Avoid sending anything to the server if the roster item hasn't changed.
- // It's possible that the roster item hasn't changed if the roster
- // item had several groups and the user moved locally the contact
- // to another group where it already was on the server.
- if (item.getXML() == oldXML) {
- return;
- }
-
- let s = Stanza.iq(
- "set",
- null,
- null,
- Stanza.node("query", Stanza.NS.roster, null, item)
- );
- this._account.sendStanza(s);
- },
-
- remove() {
- if (!this._account.connected) {
- return;
- }
-
- let s = Stanza.iq(
- "set",
- null,
- null,
- Stanza.node(
- "query",
- Stanza.NS.roster,
- null,
- Stanza.node("item", null, {
- jid: this.normalizedName,
- subscription: "remove",
- })
- )
- );
- this._account.sendStanza(s);
- },
-
- _photoHash: null,
- _saveIcon(aPhotoNode) {
- // Some servers seem to send a photo node without a type declared.
- let type = aPhotoNode.getElement(["TYPE"]);
- if (!type) {
- return;
- }
- type = type.innerText;
- const kExt = {
- "image/gif": "gif",
- "image/jpeg": "jpg",
- "image/png": "png",
- };
- if (!kExt.hasOwnProperty(type)) {
- return;
- }
-
- let content = "",
- data = "";
- // Strip all characters not allowed in base64 before parsing.
- let parseBase64 = aBase => atob(aBase.replace(/[^A-Za-z0-9\+\/\=]/g, ""));
- for (let line of aPhotoNode.getElement(["BINVAL"]).innerText.split("\n")) {
- data += line;
- // Mozilla's atob() doesn't handle padding with "=" or "=="
- // unless it's at the end of the string, so we have to work around that.
- if (line.endsWith("=")) {
- content += parseBase64(data);
- data = "";
- }
- }
- content += parseBase64(data);
-
- // Store a sha1 hash of the photo we have just received.
- let ch = Cc["@mozilla.org/security/hash;1"].createInstance(
- Ci.nsICryptoHash
- );
- ch.init(ch.SHA1);
- let dataArray = Object.keys(content).map(i => content.charCodeAt(i));
- ch.update(dataArray, dataArray.length);
- let hash = ch.finish(false);
- function toHexString(charCode) {
- return ("0" + charCode.toString(16)).slice(-2);
- }
- this._photoHash = Object.keys(hash)
- .map(i => toHexString(hash.charCodeAt(i)))
- .join("");
-
- let istream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
- Ci.nsIStringInputStream
- );
- istream.setData(content, content.length);
-
- let fileName = this._photoHash + "." + kExt[type];
- let file = FileUtils.getFile("ProfD", [
- "icons",
- this.account.protocol.normalizedName,
- this.account.normalizedName,
- fileName,
- ]);
- let ostream = FileUtils.openSafeFileOutputStream(file);
- let buddy = this;
- NetUtil.asyncCopy(istream, ostream, function(rc) {
- if (Components.isSuccessCode(rc)) {
- buddy.buddyIconFilename = Services.io.newFileURI(file).spec;
- }
- });
- },
-
- _preferredResource: undefined,
- _resources: null,
- onAccountDisconnected() {
- delete this._preferredResource;
- delete this._resources;
- },
- // Called by the account when a presence stanza is received for this buddy.
- onPresenceStanza(aStanza) {
- let preferred = this._preferredResource;
-
- // Facebook chat's XMPP server doesn't send resources, let's
- // replace undefined resources with empty resources.
- let resource =
- this._account._parseJID(aStanza.attributes.from).resource || "";
-
- let type = aStanza.attributes.type;
-
- // Reset typing status if the buddy is in a conversation and becomes unavailable.
- let conv = this._account._conv.get(this.normalizedName);
- if (type == "unavailable" && conv) {
- conv.updateTyping(Ci.prplIConvIM.NOT_TYPING, this.contactDisplayName);
- }
-
- if (type == "unavailable" || type == "error") {
- if (!this._resources || !(resource in this._resources)) {
- // Ignore for already offline resources.
- return;
- }
- delete this._resources[resource];
- if (preferred == resource) {
- preferred = undefined;
- }
- } else {
- let statusInfo = parseStatus(aStanza);
- let priority = aStanza.getElement(["priority"]);
- priority = priority ? parseInt(priority.innerText, 10) : 0;
-
- if (!this._resources) {
- this._resources = {};
- }
- this._resources[resource] = {
- statusType: statusInfo.statusType,
- statusText: statusInfo.statusText,
- idleSince: statusInfo.idleSince,
- priority,
- stanza: aStanza,
- };
- }
-
- let photo = aStanza.getElement(["x", "photo"]);
- if (photo && photo.uri == Stanza.NS.vcard_update) {
- let hash = photo.innerText;
- if (hash && hash != this._photoHash) {
- this._account._addVCardRequest(this.normalizedName);
- } else if (!hash && this._photoHash) {
- delete this._photoHash;
- this.buddyIconFilename = "";
- }
- }
-
- for (let r in this._resources) {
- if (
- preferred === undefined ||
- this._resources[r].statusType > this._resources[preferred].statusType
- ) {
- // FIXME also compare priorities...
- preferred = r;
- }
- }
- if (
- preferred != undefined &&
- preferred == this._preferredResource &&
- resource != preferred
- ) {
- // The presence information change is only for an unused resource,
- // only potential buddy tooltips need to be refreshed.
- this._notifyObservers("status-detail-changed");
- return;
- }
-
- // Presence info has changed enough that if we are having a
- // conversation with one resource of this buddy, we should send
- // the next message to all resources.
- // FIXME: the test here isn't exactly right...
- if (
- this._preferredResource != preferred &&
- this._account._conv.has(this.normalizedName)
- ) {
- delete this._account._conv.get(this.normalizedName)._targetResource;
- }
-
- this._preferredResource = preferred;
- if (preferred === undefined) {
- let statusType = Ci.imIStatusInfo.STATUS_UNKNOWN;
- if (type == "unavailable") {
- statusType = Ci.imIStatusInfo.STATUS_OFFLINE;
- }
- this.setStatus(statusType, "");
- } else {
- preferred = this._resources[preferred];
- this.setStatus(preferred.statusType, preferred.statusText);
- }
- },
-
- /* Can send messages to buddies who appear offline */
- get canSendMessage() {
- return this.account.connected;
- },
-
- /* Called when the user wants to chat with the buddy */
- createConversation() {
- return this._account.createConversation(this.normalizedName);
- },
-};
-function XMPPAccountBuddy(aAccount, aBuddy, aTag, aUserName) {
- this._init(aAccount, aBuddy, aTag, aUserName);
-}
-XMPPAccountBuddy.prototype = XMPPAccountBuddyPrototype;
-
-var XMPPRoomInfoPrototype = {
- __proto__: ClassInfo("prplIRoomInfo", "XMPP RoomInfo Object"),
- get topic() {
- return "";
- },
- get participantCount() {
- return Ci.prplIRoomInfo.NO_PARTICIPANT_COUNT;
- },
- get chatRoomFieldValues() {
- let roomJid = this._account._roomList.get(this.name);
- return this._account.getChatRoomDefaultFieldValues(roomJid);
- },
-};
-function XMPPRoomInfo(aName, aAccount) {
- this.name = aName;
- this._account = aAccount;
-}
-XMPPRoomInfo.prototype = XMPPRoomInfoPrototype;
-
-/* Helper class for account */
-var XMPPAccountPrototype = {
- __proto__: GenericAccountPrototype,
-
- _jid: null, // parsed Jabber ID: node, domain, resource
- _connection: null, // XMPPSession socket
- authMechanisms: null, // hook to let prpls tweak the list of auth mechanisms
-
- // Contains the domain of MUC service which is obtained using service
- // discovery.
- _mucService: null,
-
- // Maps room names to room jid.
- _roomList: new Map(),
-
- // Callbacks used when roomInfo is available.
- _roomInfoCallbacks: new Set(),
-
- // Determines if roomInfo that we have is expired or not.
- _lastListTime: 0,
- get isRoomInfoStale() {
- return Date.now() - this._lastListTime > kListRefreshInterval;
- },
-
- // If true, we are waiting for replies.
- _pendingList: false,
-
- // An array of jids for which we still need to request vCards.
- _pendingVCardRequests: [],
-
- // XEP-0280: Message Carbons.
- // If true, message carbons are currently enabled.
- _isCarbonsEnabled: false,
-
- /* Generate unique id for a stanza. Using id and unique sid is defined in
- * RFC 6120 (Section 8.2.3, 4.7.3).
- */
- generateId: () =>
- UuidGenerator.generateUUID()
- .toString()
- .slice(1, -1),
-
- _init(aProtoInstance, aImAccount) {
- GenericAccountPrototype._init.call(this, aProtoInstance, aImAccount);
-
- // Ongoing conversations.
- // The keys of this._conv are assumed to be normalized like account@domain
- // for normal conversations and like room@domain/nick for MUC participant
- // convs.
- this._conv = new NormalizedMap(this.normalizeFullJid.bind(this));
-
- this._buddies = new NormalizedMap(this.normalize.bind(this));
- this._mucs = new NormalizedMap(this.normalize.bind(this));
- },
-
- get canJoinChat() {
- return true;
- },
- chatRoomFields: {
- room: {
+ options: {
+ resource: {
+ get label() {
+ return _("options.resource");
+ },
+ default: "",
+ },
+ priority: {
+ get label() {
+ return _("options.priority");
+ },
+ default: 0,
+ },
+ connection_security: {
get label() {
- return _("chatRoomField.room");
+ return _("options.connectionSecurity");
},
- required: true,
+ listValues: {
+ get require_tls() {
+ return _("options.connectionSecurity.requireEncryption");
+ },
+ get opportunistic_tls() {
+ return _("options.connectionSecurity.opportunisticTLS");
+ },
+ get allow_unencrypted_plain_auth() {
+ return _("options.connectionSecurity.allowUnencryptedAuth");
+ },
+ // "old_ssl" and "none" are also supported, but not exposed in the UI.
+ // Any unknown value will fallback to the opportunistic_tls behavior.
+ },
+ default: "require_tls",
},
server: {
get label() {
- return _("chatRoomField.server");
- },
- required: true,
- },
- nick: {
- get label() {
- return _("chatRoomField.nick");
+ return _("options.connectServer");
},
- required: true,
+ default: "",
},
- password: {
+ port: {
get label() {
- return _("chatRoomField.password");
+ return _("options.connectPort");
},
- isPassword: true,
+ default: 5222,
},
},
- parseDefaultChatName(aDefaultChatName) {
- if (!aDefaultChatName) {
- return { nick: this._jid.node };
- }
-
- let params = aDefaultChatName.trim().split(/\s+/);
- let jid = this._parseJID(params[0]);
-
- // We swap node and domain as domain is required for parseJID, but node and
- // resource are optional. In MUC join command, Node is required as it
- // represents a room, but domain and resource are optional as we get muc
- // domain from service discovery.
- if (!jid.node && jid.domain) {
- [jid.node, jid.domain] = [jid.domain, jid.node];
- }
-
- let chatFields = {
- room: jid.node,
- server: jid.domain || this._mucService,
- nick: jid.resource || this._jid.node,
- };
- if (params.length > 1) {
- chatFields.password = params[1];
- }
- return chatFields;
- },
- getChatRoomDefaultFieldValues(aDefaultChatName) {
- let rv = GenericAccountPrototype.getChatRoomDefaultFieldValues.call(
- this,
- aDefaultChatName
- );
- if (!rv.values.nick) {
- rv.values.nick = this._jid.node;
- }
- if (!rv.values.server && this._mucService) {
- rv.values.server = this._mucService;
- }
-
- return rv;
- },
-
- // XEP-0045: Requests joining room if it exists or
- // creating room if it does not exist.
- joinChat(aComponents) {
- let jid =
- aComponents.getValue("room") + "@" + aComponents.getValue("server");
- let nick = aComponents.getValue("nick");
-
- let muc = this._mucs.get(jid);
- if (muc) {
- if (!muc.left) {
- // We are already in this conversation.
- return muc;
- } else if (!muc.chatRoomFields) {
- // We are rejoining a room that was parted by the user.
- muc._rejoined = true;
- }
- } else {
- muc = new this._MUCConversationConstructor(this, jid, nick);
- this._mucs.set(jid, muc);
- }
-
- // Store the prplIChatRoomFieldValues to enable later reconnections.
- muc.chatRoomFields = aComponents;
- muc.joining = true;
- muc.removeAllParticipants();
-
- let password = aComponents.getValue("password");
- let x = Stanza.node(
- "x",
- Stanza.NS.muc,
- null,
- password ? Stanza.node("password", null, null, password) : null
- );
- let logString;
- if (password) {
- logString =
- "<presence .../> (Stanza containing password to join MUC " +
- jid +
- "/" +
- nick +
- " not logged)";
- }
- this.sendStanza(
- Stanza.presence({ to: jid + "/" + nick }, x),
- undefined,
- undefined,
- logString
- );
- return muc;
- },
-
- _idleSince: 0,
- observe(aSubject, aTopic, aData) {
- if (aTopic == "idle-time-changed") {
- let idleTime = parseInt(aData, 10);
- if (idleTime) {
- this._idleSince = Math.floor(Date.now() / 1000) - idleTime;
- } else {
- delete this._idleSince;
- }
- this._shouldSendPresenceForIdlenessChange = true;
- executeSoon(
- function() {
- if ("_shouldSendPresenceForIdlenessChange" in this) {
- this._sendPresence();
- }
- }.bind(this)
- );
- } else if (aTopic == "status-changed") {
- this._sendPresence();
- } else if (aTopic == "user-icon-changed") {
- delete this._cachedUserIcon;
- this._forceUserIconUpdate = true;
- this._sendVCard();
- } else if (aTopic == "user-display-name-changed") {
- this._forceUserDisplayNameUpdate = true;
- }
- this._sendVCard();
- },
-
- /* GenericAccountPrototype events */
- /* Connect to the server */
- connect() {
- this._jid = this._parseJID(this.name);
-
- // For the resource, if the user has edited the option, always use that.
- if (this.prefs.prefHasUserValue("resource")) {
- let resource = this.getString("resource");
-
- // this._jid needs to be updated. This value is however never used
- // because while connected it's the jid of the session that's
- // interesting.
- this._jid = this._setJID(this._jid.domain, this._jid.node, resource);
- } else if (this._jid.resource) {
- // If there is a resource in the account name (inherited from libpurple),
- // migrate it to the pref so it appears correctly in the advanced account
- // options next time.
- this.prefs.setStringPref("resource", this._jid.resource);
- }
-
- this._connection = new XMPPSession(
- this.getString("server") || this._jid.domain,
- this.getInt("port") || 5222,
- this.getString("connection_security"),
- this._jid,
- this.imAccount.password,
- this
- );
- },
-
- remove() {
- this._conv.forEach(conv => conv.close());
- this._mucs.forEach(muc => muc.close());
- this._buddies.forEach((buddy, jid) => this._forgetRosterItem(jid));
- },
-
- unInit() {
- if (this._connection) {
- this._disconnect(undefined, undefined, true);
- }
- delete this._jid;
- delete this._conv;
- delete this._buddies;
- delete this._mucs;
- },
-
- /* Disconnect from the server */
- disconnect() {
- this._disconnect();
- },
-
- addBuddy(aTag, aName) {
- if (!this._connection) {
- throw new Error("The account isn't connected");
- }
-
- let jid = this.normalize(aName);
- if (!jid || !jid.includes("@")) {
- throw new Error("Invalid username");
- }
-
- if (this._buddies.has(jid)) {
- let subscription = this._buddies.get(jid).subscription;
- if (subscription && (subscription == "both" || subscription == "to")) {
- this.DEBUG("not re-adding an existing buddy");
- return;
- }
- } else {
- let s = Stanza.iq(
- "set",
- null,
- null,
- Stanza.node(
- "query",
- Stanza.NS.roster,
- null,
- Stanza.node(
- "item",
- null,
- { jid },
- Stanza.node("group", null, null, aTag.name)
- )
- )
- );
- this.sendStanza(
- s,
- this._handleResult({
- default: aError => {
- this.WARN(
- "Unable to add a roster item due to " + aError + " error."
- );
- },
- })
- );
- }
- this.sendStanza(Stanza.presence({ to: jid, type: "subscribe" }));
- },
-
- /* Loads a buddy from the local storage.
- * Called for each buddy locally stored before connecting
- * to the server. */
- loadBuddy(aBuddy, aTag) {
- let buddy = new this._accountBuddyConstructor(this, aBuddy, aTag);
- this._buddies.set(buddy.normalizedName, buddy);
- return buddy;
- },
-
- /* Replies to a buddy request in order to accept it or deny it. */
- replyToBuddyRequest(aReply, aRequest) {
- if (!this._connection) {
- return;
- }
- let s = Stanza.presence({ to: aRequest.userName, type: aReply });
- this.sendStanza(s);
- this.removeBuddyRequest(aRequest);
- },
-
- requestBuddyInfo(aJid) {
- if (!this.connected) {
- Services.obs.notifyObservers(EmptyEnumerator, "user-info-received", aJid);
- return;
- }
-
- let userName;
- let tooltipInfo = [];
- let jid = this._parseJID(aJid);
- let muc = this._mucs.get(jid.node + "@" + jid.domain);
- let participant;
- if (muc) {
- participant = muc._participants.get(jid.resource);
- if (participant) {
- if (participant.accountJid) {
- userName = participant.accountJid;
- }
- if (!muc.left) {
- let statusType = participant.statusType;
- let statusText = participant.statusText;
- tooltipInfo.push(
- new TooltipInfo(statusType, statusText, Ci.prplITooltipInfo.status)
- );
-
- if (participant.buddyIconFilename) {
- tooltipInfo.push(
- new TooltipInfo(
- null,
- participant.buddyIconFilename,
- Ci.prplITooltipInfo.icon
- )
- );
- }
- }
- }
- }
- Services.obs.notifyObservers(
- new nsSimpleEnumerator(tooltipInfo),
- "user-info-received",
- aJid
- );
-
- let iq = Stanza.iq(
- "get",
- null,
- aJid,
- Stanza.node("vCard", Stanza.NS.vcard)
- );
- this.sendStanza(iq, aStanza => {
- let vCardInfo = {};
- let vCardNode = aStanza.getElement(["vCard"]);
-
- // In the case of an error response, we just notify the observers with
- // what info we already have.
- if (aStanza.attributes.type == "result" && vCardNode) {
- vCardInfo = this.parseVCard(vCardNode);
- }
-
- // The real jid of participant which is of the form local@domain/resource.
- // We consider the jid is provided by server is more correct than jid is
- // set by the user.
- if (userName) {
- vCardInfo.userName = userName;
- }
-
- // vCard fields we want to display in the tooltip.
- const kTooltipFields = [
- "userName",
- "fullName",
- "nickname",
- "title",
- "organization",
- "email",
- "birthday",
- "locality",
- "country",
- "telephone",
- ];
-
- let tooltipInfo = [];
- for (let field of kTooltipFields) {
- if (vCardInfo.hasOwnProperty(field)) {
- tooltipInfo.push(
- new TooltipInfo(_("tooltip." + field), vCardInfo[field])
- );
- }
- }
- if (vCardInfo.photo) {
- let dataURI = this._getPhotoURI(vCardInfo.photo);
-
- // Store the photo URI for this participant.
- if (participant) {
- participant.buddyIconFilename = dataURI;
- }
-
- tooltipInfo.push(
- new TooltipInfo(null, dataURI, Ci.prplITooltipInfo.icon)
- );
- }
- Services.obs.notifyObservers(
- new nsSimpleEnumerator(tooltipInfo),
- "user-info-received",
- aJid
- );
- });
- },
-
- // Parses the photo node of a received vCard if exists and returns string of
- // data URI, otherwise returns null.
- _getPhotoURI(aPhotoNode) {
- if (!aPhotoNode) {
- return null;
- }
-
- let type = aPhotoNode.getElement(["TYPE"]);
- let value = aPhotoNode.getElement(["BINVAL"]);
- if (!type || !value) {
- return null;
- }
-
- return "data:" + type.innerText + ";base64," + value.innerText;
- },
-
- // Parses the vCard into the properties of the returned object.
- parseVCard(aVCardNode) {
- // XEP-0054: vcard-temp.
- let aResult = {};
- for (let node of aVCardNode.children.filter(
- child => child.type == "node"
- )) {
- let localName = node.localName;
- let innerText = node.innerText;
- if (innerText) {
- if (localName == "FN") {
- aResult.fullName = innerText;
- } else if (localName == "NICKNAME") {
- aResult.nickname = innerText;
- } else if (localName == "TITLE") {
- aResult.title = innerText;
- } else if (localName == "BDAY") {
- aResult.birthday = innerText;
- } else if (localName == "JABBERID") {
- aResult.userName = innerText;
- }
- }
- if (localName == "ORG") {
- let organization = node.getElement(["ORGNAME"]);
- if (organization && organization.innerText) {
- aResult.organization = organization.innerText;
- }
- } else if (localName == "EMAIL") {
- let userID = node.getElement(["USERID"]);
- if (userID && userID.innerText) {
- aResult.email = userID.innerText;
- }
- } else if (localName == "ADR") {
- let locality = node.getElement(["LOCALITY"]);
- if (locality && locality.innerText) {
- aResult.locality = locality.innerText;
- }
-
- let country = node.getElement(["CTRY"]);
- if (country && country.innerText) {
- aResult.country = country.innerText;
- }
- } else if (localName == "PHOTO") {
- aResult.photo = node;
- } else if (localName == "TEL") {
- let number = node.getElement(["NUMBER"]);
- if (number && number.innerText) {
- aResult.telephone = number.innerText;
- }
- }
- // TODO: Parse the other fields of vCard and display it in system messages
- // in response to /whois.
- }
- return aResult;
- },
-
- // Returns undefined if not an error stanza, and an object
- // describing the error otherwise:
- parseError(aStanza) {
- if (aStanza.attributes.type != "error") {
- return undefined;
- }
-
- let retval = { stanza: aStanza };
- let error = aStanza.getElement(["error"]);
-
- // RFC 6120 Section 8.3.2: Type must be one of
- // auth -- retry after providing credentials
- // cancel -- do not retry (the error cannot be remedied)
- // continue -- proceed (the condition was only a warning)
- // modify -- retry after changing the data sent
- // wait -- retry after waiting (the error is temporary).
- retval.type = error.attributes.type;
-
- // RFC 6120 Section 8.3.3.
- const kDefinedConditions = [
- "bad-request",
- "conflict",
- "feature-not-implemented",
- "forbidden",
- "gone",
- "internal-server-error",
- "item-not-found",
- "jid-malformed",
- "not-acceptable",
- "not-allowed",
- "not-authorized",
- "policy-violation",
- "recipient-unavailable",
- "redirect",
- "registration-required",
- "remote-server-not-found",
- "remote-server-timeout",
- "resource-constraint",
- "service-unavailable",
- "subscription-required",
- "undefined-condition",
- "unexpected-request",
- ];
- let condition = kDefinedConditions.find(c => error.getElement([c]));
- if (!condition) {
- // RFC 6120 Section 8.3.2.
- this.WARN(
- "Nonstandard or missing defined-condition element in error stanza."
- );
- condition = "undefined-condition";
- }
- retval.condition = condition;
-
- let errortext = error.getElement(["text"]);
- if (errortext) {
- retval.text = errortext.innerText;
- }
-
- return retval;
- },
-
- // Returns an error-handling callback for use with sendStanza generated
- // from aHandlers, an object defining the error handlers.
- // If the stanza passed to the callback is an error stanza, it checks if
- // aHandlers contains a property with the name of the defined condition
- // of the error.
- // * If the property is a function, it is called with the parsed error
- // as its argument, bound to aThis (if provided).
- // It should return true if the error was handled.
- // * If the property is a string, it is displayed as a system message
- // in the conversation given by aThis.
- handleErrors(aHandlers, aThis) {
- return aStanza => {
- if (!aHandlers) {
- return false;
- }
-
- let error = this.parseError(aStanza);
- if (!error) {
- return false;
- }
-
- let toCamelCase = aStr => {
- // Turn defined condition string into a valid camelcase
- // JS property name.
- let capitalize = s => s[0].toUpperCase() + s.slice(1);
- let uncapitalize = s => s[0].toLowerCase() + s.slice(1);
- return uncapitalize(
- aStr
- .split("-")
- .map(capitalize)
- .join("")
- );
- };
- let condition = toCamelCase(error.condition);
- // Check if we have a handler property for this kind of error or a
- // default handler.
- if (!(condition in aHandlers) && !("default" in aHandlers)) {
- return false;
- }
-
- // Try to get the handler for condition, if we cannot get it, try to get
- // the default handler.
- let handler = aHandlers[condition];
- if (!handler) {
- handler = aHandlers.default;
- }
-
- if (typeof handler == "string") {
- // The string is an error message to be displayed in the conversation.
- if (!aThis || !aThis.writeMessage) {
- this.ERROR(
- "HandleErrors was passed an error message string, but " +
- "no conversation to display it in:\n" +
- handler
- );
- return true;
- }
- aThis.writeMessage(aThis.name, handler, { system: true, error: true });
- return true;
- } else if (typeof handler == "function") {
- // If we're given a function, call this error handler.
- return handler.call(aThis, error);
- }
-
- // If this happens, there's a bug somewhere.
- this.ERROR(
- "HandleErrors was passed a handler for '" +
- condition +
- "'' which is neither a function nor a string."
- );
- return false;
- };
- },
-
- // Returns a callback suitable for use in sendStanza, to handle type==result
- // responses. aHandlers and aThis are passed on to handleErrors for error
- // handling.
- _handleResult(aHandlers, aThis) {
- return aStanza => {
- if (aStanza.attributes.type == "result") {
- return true;
- }
- return this.handleErrors(aHandlers, aThis)(aStanza);
- };
- },
-
- /* XMPPSession events */
-
- /* Called when the XMPP session is started */
- onConnection() {
- // Request the roster. The account will be marked as connected when this is
- // complete.
- this.reportConnecting(_("connection.downloadingRoster"));
- let s = Stanza.iq(
- "get",
- null,
- null,
- Stanza.node("query", Stanza.NS.roster)
- );
- this.sendStanza(s, this.onRoster, this);
-
- // XEP-0030 and XEP-0045 (6): Service Discovery.
- // Queries Server for Associated Services.
- let iq = Stanza.iq(
- "get",
- null,
- this._jid.domain,
- Stanza.node("query", Stanza.NS.disco_items)
- );
- this.sendStanza(iq, this.onServiceDiscovery, this);
-
- // XEP-0030: Service Discovery Information Features.
- iq = Stanza.iq(
- "get",
- null,
- this._jid.domain,
- Stanza.node("query", Stanza.NS.disco_info)
- );
- this.sendStanza(iq, this.onServiceDiscoveryInfo, this);
- },
-
- /* Called whenever a stanza is received */
- onXmppStanza(aStanza) {},
-
- /* Called when a iq stanza is received */
- onIQStanza(aStanza) {
- let type = aStanza.attributes.type;
- if (type == "set") {
- for (let query of aStanza.getChildren("query")) {
- if (query.uri != Stanza.NS.roster) {
- continue;
- }
-
- // RFC 6121 2.1.6 (Roster push):
- // A receiving client MUST ignore the stanza unless it has no 'from'
- // attribute (i.e., implicitly from the bare JID of the user's
- // account) or it has a 'from' attribute whose value matches the
- // user's bare JID <user@domainpart>.
- let from = aStanza.attributes.from;
- if (from && from != this._jid.node + "@" + this._jid.domain) {
- this.WARN("Ignoring potentially spoofed roster push.");
- return;
- }
-
- for (let item of query.getChildren("item")) {
- this._onRosterItem(item, true);
- }
- return;
- }
- } else if (type == "get") {
- let id = aStanza.attributes.id;
- let from = aStanza.attributes.from;
-
- // XEP-0199: XMPP server-to-client ping (XEP-0199)
- let ping = aStanza.getElement(["ping"]);
- if (ping && ping.uri == Stanza.NS.ping) {
- if (from == this._jid.domain) {
- this.sendStanza(Stanza.iq("result", id, this._jid.domain));
- }
- return;
- }
-
- let query = aStanza.getElement(["query"]);
- if (query && query.uri == Stanza.NS.version) {
- // XEP-0092: Software Version.
- let children = [];
- children.push(Stanza.node("name", null, null, Services.appinfo.name));
- children.push(
- Stanza.node("version", null, null, Services.appinfo.version)
- );
- let versionQuery = Stanza.node(
- "query",
- Stanza.NS.version,
- null,
- children
- );
- this.sendStanza(Stanza.iq("result", id, from, versionQuery));
- return;
- }
- if (query && query.uri == Stanza.NS.disco_info) {
- // XEP-0030: Service Discovery.
- let children = [];
- if (aStanza.attributes.node == Stanza.NS.muc_rooms) {
- // XEP-0045 (6.7): Room query.
- // TODO: Currently, we return an empty <query/> element, but we
- // should return non-private rooms.
- } else {
- children = SupportedFeatures.map(feature =>
- Stanza.node("feature", null, { var: feature })
- );
- children.unshift(
- Stanza.node("identity", null, {
- category: "client",
- type: "pc",
- name: Services.appinfo.name,
- })
- );
- }
- let discoveryQuery = Stanza.node(
- "query",
- Stanza.NS.disco_info,
- null,
- children
- );
- this.sendStanza(Stanza.iq("result", id, from, discoveryQuery));
- return;
- }
- }
- this.WARN(`Unhandled IQ ${type} stanza.`);
- },
-
- /* Called when a presence stanza is received */
- onPresenceStanza(aStanza) {
- let from = aStanza.attributes.from;
- this.DEBUG("Received presence stanza for " + from);
-
- let jid = this.normalize(from);
- let type = aStanza.attributes.type;
- if (type == "subscribe") {
- this.addBuddyRequest(
- jid,
- this.replyToBuddyRequest.bind(this, "subscribed"),
- this.replyToBuddyRequest.bind(this, "unsubscribed")
- );
- } else if (
- type == "unsubscribe" ||
- type == "unsubscribed" ||
- type == "subscribed"
- ) {
- // Nothing useful to do for these presence stanzas, as we will also
- // receive a roster push containing more or less the same information
- } else if (this._buddies.has(jid)) {
- this._buddies.get(jid).onPresenceStanza(aStanza);
- } else if (this._mucs.has(jid)) {
- this._mucs.get(jid).onPresenceStanza(aStanza);
- } else if (jid != this.normalize(this._connection._jid.jid)) {
- this.WARN("received presence stanza for unknown buddy " + from);
- } else if (
- jid == this._jid.node + "@" + this._jid.domain &&
- this._connection._resource != this._parseJID(from).resource
- ) {
- // Ignore presence stanzas for another resource.
- } else {
- this.WARN("Unhandled presence stanza.");
- }
- },
-
- // XEP-0030: Discovering services and their features that are supported by
- // the server.
- onServiceDiscovery(aStanza) {
- let query = aStanza.getElement(["query"]);
- if (
- aStanza.attributes.type != "result" ||
- !query ||
- query.uri != Stanza.NS.disco_items
- ) {
- this.LOG("Could not get services for this server: " + this._jid.domain);
- return true;
- }
-
- // Discovering the Features that are Supported by each service.
- query.getElements(["item"]).forEach(item => {
- let jid = item.attributes.jid;
- if (!jid) {
- return;
- }
- let iq = Stanza.iq(
- "get",
- null,
- jid,
- Stanza.node("query", Stanza.NS.disco_info)
- );
- this.sendStanza(iq, receivedStanza => {
- let query = receivedStanza.getElement(["query"]);
- let from = receivedStanza.attributes.from;
- if (
- aStanza.attributes.type != "result" ||
- !query ||
- query.uri != Stanza.NS.disco_info
- ) {
- this.LOG("Could not get features for this service: " + from);
- return true;
- }
- let features = query
- .getElements(["feature"])
- .map(elt => elt.attributes.var);
- let identity = query.getElement(["identity"]);
- if (
- identity &&
- identity.attributes.category == "conference" &&
- identity.attributes.type == "text" &&
- features.includes(Stanza.NS.muc)
- ) {
- // XEP-0045 (6.2): this feature is for a MUC Service.
- // XEP-0045 (15.2): Service Discovery Category/Type.
- this._mucService = from;
- }
- // TODO: Handle other services that are supported by XMPP through
- // their features.
-
- return true;
- });
- });
- return true;
- },
-
- // XEP-0030: Discovering Service Information and its features that are
- // supported by the server.
- onServiceDiscoveryInfo(aStanza) {
- let query = aStanza.getElement(["query"]);
- if (
- aStanza.attributes.type != "result" ||
- !query ||
- query.uri != Stanza.NS.disco_info
- ) {
- this.LOG("Could not get features for this server: " + this._jid.domain);
- return true;
- }
-
- let features = query
- .getElements(["feature"])
- .map(elt => elt.attributes.var);
- if (features.includes(Stanza.NS.carbons)) {
- // XEP-0280: Message Carbons.
- // Enabling Carbons on server, as it's disabled by default on server.
- if (Services.prefs.getBoolPref("chat.xmpp.messageCarbons")) {
- let iqStanza = Stanza.iq(
- "set",
- null,
- null,
- Stanza.node("enable", Stanza.NS.carbons)
- );
- this.sendStanza(iqStanza, aStanza => {
- let error = this.parseError(aStanza);
- if (error) {
- this.WARN(
- "Unable to enable message carbons due to " +
- error.condition +
- " error."
- );
- return true;
- }
-
- let type = aStanza.attributes.type;
- if (type != "result") {
- this.WARN(
- "Received unexpected stanza with " +
- type +
- " type " +
- "while enabling message carbons."
- );
- return true;
- }
-
- this.LOG("Message carbons enabled.");
- this._isCarbonsEnabled = true;
- return true;
- });
- }
- }
- // TODO: Handle other features that are supported by the server.
+ get chatHasTopic() {
return true;
},
-
- requestRoomInfo(aCallback) {
- if (this._roomInfoCallbacks.has(aCallback)) {
- return;
- }
-
- if (this.isRoomInfoStale && !this._pendingList) {
- this._roomList = new Map();
- this._lastListTime = Date.now();
- this._roomInfoCallback = aCallback;
- this._pendingList = true;
-
- // XEP-0045 (6.3): Discovering Rooms.
- let iq = Stanza.iq(
- "get",
- null,
- this._mucService,
- Stanza.node("query", Stanza.NS.disco_items)
- );
- this.sendStanza(iq, this.onRoomDiscovery, this);
- } else {
- let rooms = [...this._roomList.keys()];
- aCallback.onRoomInfoAvailable(rooms, !this._pendingList);
- }
-
- if (this._pendingList) {
- this._roomInfoCallbacks.add(aCallback);
- }
- },
-
- onRoomDiscovery(aStanza) {
- let query = aStanza.getElement(["query"]);
- if (!query || query.uri != Stanza.NS.disco_items) {
- this.LOG("Could not get rooms for this server: " + this._jid.domain);
- return;
- }
-
- // XEP-0059: Result Set Management.
- let set = query.getElement(["set"]);
- let last = set ? set.getElement(["last"]) : null;
- if (last) {
- let iq = Stanza.iq(
- "get",
- null,
- this._mucService,
- Stanza.node("query", Stanza.NS.disco_items)
- );
- this.sendStanza(iq, this.onRoomDiscovery, this);
- } else {
- this._pendingList = false;
- }
-
- let rooms = [];
- query.getElements(["item"]).forEach(item => {
- let jid = this._parseJID(item.attributes.jid);
- if (!jid) {
- return;
- }
-
- let name = item.attributes.name;
- if (!name) {
- name = jid.node ? jid.node : jid.jid;
- }
-
- this._roomList.set(name, jid.jid);
- rooms.push(name);
- });
-
- this._roomInfoCallback.onRoomInfoAvailable(rooms, !this._pendingList);
- },
-
- getRoomInfo(aName) {
- return new XMPPRoomInfo(aName, this);
- },
-
- // Returns null if not an invitation stanza, and an object
- // describing the invitation otherwise.
- parseInvitation(aStanza) {
- let x = aStanza.getElement(["x"]);
- if (!x) {
- return null;
- }
- let retVal = {};
-
- // XEP-0045. Direct Invitation (7.8.1)
- // Described in XEP-0249.
- // jid (chatroom) is required.
- // Password, reason, continue and thread are optional.
- if (x.uri == Stanza.NS.conference) {
- if (!x.attributes.jid) {
- this.WARN("Received an invitation with missing MUC jid.");
- return null;
- }
- retVal.mucJid = this.normalize(x.attributes.jid);
- retVal.from = this.normalize(aStanza.attributes.from);
- retVal.password = x.attributes.password;
- retVal.reason = x.attributes.reason;
- retVal.continue = x.attributes.continue;
- retVal.thread = x.attributes.thread;
- return retVal;
- }
-
- // XEP-0045. Mediated Invitation (7.8.2)
- // Sent by the chatroom on behalf of someone in the chatroom.
- // jid (chatroom) and from (inviter) are required.
- // password and reason are optional.
- if (x.uri == Stanza.NS.muc_user) {
- let invite = x.getElement(["invite"]);
- if (!invite || !invite.attributes.from) {
- this.WARN("Received an invitation with missing MUC invite or from.");
- return null;
- }
- retVal.mucJid = this.normalize(aStanza.attributes.from);
- retVal.from = this.normalize(invite.attributes.from);
- let continueElement = invite.getElement(["continue"]);
- retVal.continue = !!continueElement;
- if (continueElement) {
- retVal.thread = continueElement.attributes.thread;
- }
- if (x.getElement(["password"])) {
- retVal.password = x.getElement(["password"]).innerText;
- }
- if (invite.getElement(["reason"])) {
- retVal.reason = invite.getElement(["reason"]).innerText;
- }
- return retVal;
- }
-
- return null;
- },
-
- /* Called when a message stanza is received */
- onMessageStanza(aStanza) {
- // XEP-0280: Message Carbons.
- // Sending and Receiving Messages.
- // Indicates that the forwarded message was sent or received.
- let isSent = false;
- let carbonStanza =
- aStanza.getElement(["sent"]) || aStanza.getElement(["received"]);
- if (carbonStanza) {
- if (carbonStanza.uri != Stanza.NS.carbons) {
- this.WARN(
- "Received a forwarded message which does not '" +
- Stanza.NS.carbons +
- "' namespace."
- );
- return;
- }
-
- isSent = carbonStanza.localName == "sent";
- carbonStanza = carbonStanza.getElement(["forwarded", "message"]);
- if (this._isCarbonsEnabled) {
- aStanza = carbonStanza;
- } else {
- this.WARN(
- "Received an unexpected forwarded message while message " +
- "carbons are not enabled."
- );
- return;
- }
- }
-
- // For forwarded sent messages, we need to use "to" attribute to
- // get the right conversation as from in this case is this account.
- let convJid = isSent ? aStanza.attributes.to : aStanza.attributes.from;
-
- let normConvJid = this.normalize(convJid);
- let isMuc = this._mucs.has(normConvJid);
-
- let type = aStanza.attributes.type;
- let x = aStanza.getElement(["x"]);
- let body;
- let b = aStanza.getElement(["body"]);
- if (b) {
- // If there's a <body> child we have more than just typing notifications.
- // Prefer HTML (in <html><body>) and use plain text (<body>) as fallback.
- let htmlBody = aStanza.getElement(["html", "body"]);
- if (htmlBody) {
- body = htmlBody.innerXML;
- } else {
- // Even if the message is in plain text, the prplIMessage
- // should contain a string that's correctly escaped for
- // insertion in an HTML document.
- body = TXTToHTML(b.innerText);
- }
- }
-
- let subject = aStanza.getElement(["subject"]);
- // Ignore subject when !isMuc. We're being permissive about subject changes
- // in the comment below, so we need to be careful about where that makes
- // sense. Psi+'s OTR plugin includes a subject and body in its message
- // stanzas.
- if (subject && isMuc) {
- // XEP-0045 (7.2.16): Check for a subject element in the stanza and update
- // the topic if it exists.
- // We are breaking the spec because only a message that contains a
- // <subject/> but no <body/> element shall be considered a subject change
- // for MUC, but we ignore that to be compatible with ejabberd versions
- // before 15.06.
- let muc = this._mucs.get(normConvJid);
- let nick = this._parseJID(convJid).resource;
- // TODO There can be multiple subject elements with different xml:lang
- // attributes.
- muc.setTopic(subject.innerText, nick);
- return;
- }
-
- let invitation = this.parseInvitation(aStanza);
- if (invitation) {
- let messageID;
- if (invitation.reason) {
- messageID = "conversation.muc.invitationWithReason2";
- } else {
- messageID = "conversation.muc.invitationWithoutReason";
- }
- if (invitation.password) {
- messageID += ".password";
- }
- let params = [
- invitation.from,
- invitation.mucJid,
- invitation.password,
- invitation.reason,
- ].filter(s => s);
- let message = _(messageID, ...params);
-
- if (
- Services.prefs.getIntPref(
- "messenger.conversations.autoAcceptChatInvitations"
- ) == 1
- ) {
- // Auto-accept the invitation.
- let chatRoomFields = this.getChatRoomDefaultFieldValues(
- invitation.mucJid
- );
- if (invitation.password) {
- chatRoomFields.setValue("password", invitation.password);
- }
- let muc = this.joinChat(chatRoomFields);
- muc.writeMessage(muc.name, message, { system: true });
- } else {
- // Otherwise, just notify the user.
- let conv = this.createConversation(invitation.from);
- if (conv) {
- conv.writeMessage(invitation.from, message, { system: true });
- }
- }
- }
-
- if (body) {
- let date = _getDelay(aStanza);
- if (
- type == "groupchat" ||
- (type == "error" && isMuc && !this._conv.has(convJid))
- ) {
- if (!isMuc) {
- this.WARN(
- "Received a groupchat message for unknown MUC " + normConvJid
- );
- return;
- }
- let muc = this._mucs.get(normConvJid);
- muc.incomingMessage(body, aStanza, date);
- return;
- }
-
- let conv = this.createConversation(convJid);
- if (!conv) {
- return;
- }
-
- if (isSent) {
- _displaySentMsg(conv, body, date);
- return;
- }
- conv.incomingMessage(body, aStanza, date);
- } else if (type == "error") {
- let conv = this.createConversation(convJid);
- if (conv) {
- conv.incomingMessage(null, aStanza);
- }
- } else if (x && x.uri == Stanza.NS.muc_user) {
- let muc = this._mucs.get(normConvJid);
- if (!muc) {
- this.WARN(
- "Received a groupchat message for unknown MUC " + normConvJid
- );
- return;
- }
- muc.onMessageStanza(aStanza);
- return;
- }
-
- // If this is a sent message carbon, the user is typing on another client.
- if (isSent) {
- return;
- }
-
- // Don't create a conversation to only display the typing notifications.
- if (!this._conv.has(normConvJid) && !this._conv.has(convJid)) {
- return;
- }
-
- // Ignore errors while delivering typing notifications.
- if (type == "error") {
- return;
- }
-
- let typingState = Ci.prplIConvIM.NOT_TYPING;
- let state;
- let s = aStanza.getChildrenByNS(Stanza.NS.chatstates);
- if (s.length > 0) {
- state = s[0].localName;
- }
- if (state) {
- this.DEBUG(state);
- if (state == "composing") {
- typingState = Ci.prplIConvIM.TYPING;
- } else if (state == "paused") {
- typingState = Ci.prplIConvIM.TYPED;
- }
- }
- let convName = normConvJid;
-
- // If the bare JID is a MUC that we have joined, use the full JID as this
- // is a private message to a MUC participant.
- if (isMuc) {
- convName = convJid;
- }
-
- let conv = this._conv.get(convName);
- if (!conv) {
- return;
- }
- conv.updateTyping(typingState, conv.shortName);
- conv.supportChatStateNotifications = !!state;
- },
-
- /* Called when there is an error in the xmpp session */
- onError(aError, aException) {
- if (aError === null || aError === undefined) {
- aError = Ci.prplIAccount.ERROR_OTHER_ERROR;
- }
- this._disconnect(aError, aException.toString());
- },
-
- onVCard(aStanza) {
- let jid = this._pendingVCardRequests.shift();
- this._requestNextVCard();
- if (!this._buddies.has(jid)) {
- this.WARN("Received a vCard for unknown buddy " + jid);
- return;
- }
-
- let vCard = aStanza.getElement(["vCard"]);
- let error = this.parseError(aStanza);
- if (
- (error &&
- (error.condition == "item-not-found" ||
- error.condition == "service-unavailable")) ||
- !vCard ||
- !vCard.children.length
- ) {
- this.LOG("No vCard exists (or the user does not exist) for " + jid);
- return;
- } else if (error) {
- this.WARN("Received unexpected vCard error " + error.condition);
- return;
- }
-
- let buddy = this._buddies.get(jid);
- let stanzaJid = this.normalize(aStanza.attributes.from);
- if (jid && jid != stanzaJid) {
- this.ERROR(
- "Received vCard for a different jid (" +
- stanzaJid +
- ") " +
- "than the requested " +
- jid
- );
- }
-
- let foundFormattedName = false;
- let vCardInfo = this.parseVCard(vCard);
- if (vCardInfo.fullName) {
- buddy.vCardFormattedName = vCardInfo.fullName;
- foundFormattedName = true;
- }
- if (vCardInfo.photo) {
- buddy._saveIcon(vCardInfo.photo);
- }
- if (!foundFormattedName && buddy._vCardFormattedName) {
- buddy.vCardFormattedName = "";
- }
- buddy._vCardReceived = true;
- },
-
- _requestNextVCard() {
- if (!this._pendingVCardRequests.length) {
- return;
- }
- let s = Stanza.iq(
- "get",
- null,
- this._pendingVCardRequests[0],
- Stanza.node("vCard", Stanza.NS.vcard)
- );
- this.sendStanza(s, this.onVCard, this);
- },
-
- _addVCardRequest(aJID) {
- let requestPending = !!this._pendingVCardRequests.length;
- this._pendingVCardRequests.push(aJID);
- if (!requestPending) {
- this._requestNextVCard();
- }
- },
-
- // XEP-0029 (Section 2) and RFC 6122 (Section 2): The node and domain are
- // lowercase, while resources are case sensitive and can contain spaces.
- normalizeFullJid(aJID) {
- return this._parseJID(aJID.trim()).jid;
- },
-
- // Standard normalization for XMPP removes the resource part of jids.
- normalize(aJID) {
- return aJID
- .trim()
- .split("/", 1)[0] // up to first slash
- .toLowerCase();
- },
-
- // RFC 6122 (Section 2): [ localpart "@" ] domainpart [ "/" resourcepart ] is
- // the form of jid.
- // Localpart is parsed as node and optional.
- // Domainpart is parsed as domain and required.
- // resourcepart is parsed as resource and optional.
- _parseJID(aJid) {
- let match = /^(?:([^"&'/:<>@]+)@)?([^@/<>'\"]+)(?:\/(.*))?$/.exec(
- aJid.trim()
- );
- if (!match) {
- return null;
- }
-
- let result = {
- node: match[1],
- domain: match[2].toLowerCase(),
- resource: match[3],
- };
- return this._setJID(result.domain, result.node, result.resource);
- },
-
- // Constructs jid as an object from domain, node and resource parts.
- // The object has properties (node, domain, resource and jid).
- // aDomain is required, but aNode and aResource are optional.
- _setJID(aDomain, aNode = null, aResource = null) {
- if (!aDomain) {
- throw new Error("aDomain must have a value");
- }
-
- let result = {
- node: aNode,
- domain: aDomain.toLowerCase(),
- resource: aResource,
- };
- let jid = result.domain;
- if (result.node) {
- result.node = result.node.toLowerCase();
- jid = result.node + "@" + jid;
- }
- if (result.resource) {
- jid += "/" + result.resource;
- }
- result.jid = jid;
- return result;
- },
-
- _onRosterItem(aItem, aNotifyOfUpdates) {
- let jid = aItem.attributes.jid;
- if (!jid) {
- this.WARN("Received a roster item without jid: " + aItem.getXML());
- return "";
- }
- jid = this.normalize(jid);
-
- let subscription = "";
- if ("subscription" in aItem.attributes) {
- subscription = aItem.attributes.subscription;
- }
- if (subscription == "remove") {
- this._forgetRosterItem(jid);
- return "";
- }
-
- let buddy;
- if (this._buddies.has(jid)) {
- buddy = this._buddies.get(jid);
- let groups = aItem.getChildren("group");
- if (groups.length) {
- // If the server specified at least one group, ensure the group we use
- // as the account buddy's tag is still a group on the server...
- let tagName = buddy.tag.name;
- if (!groups.some(g => g.innerText == tagName)) {
- // ... otherwise we need to move our account buddy to a new group.
- tagName = groups[0].innerText;
- if (tagName) {
- // Should always be true, but check just in case...
- let oldTag = buddy.tag;
- buddy._tag = Services.tags.createTag(tagName);
- Services.contacts.accountBuddyMoved(buddy, oldTag, buddy._tag);
- }
- }
- }
- } else {
- let tag;
- for (let group of aItem.getChildren("group")) {
- let name = group.innerText;
- if (name) {
- tag = Services.tags.createTag(name);
- break; // TODO we should create an accountBuddy per group,
- // but this._buddies would probably not like that...
- }
- }
- buddy = new this._accountBuddyConstructor(
- this,
- null,
- tag || Services.tags.defaultTag,
- jid
- );
- }
-
- // We request the vCard only if we haven't received it yet and are
- // subscribed to presence for that contact.
- if (
- (subscription == "both" || subscription == "to") &&
- !buddy._vCardReceived
- ) {
- this._addVCardRequest(jid);
- }
-
- let alias = "name" in aItem.attributes ? aItem.attributes.name : "";
- if (alias) {
- if (aNotifyOfUpdates && this._buddies.has(jid)) {
- buddy.rosterAlias = alias;
- } else {
- buddy._rosterAlias = alias;
- }
- } else if (buddy._rosterAlias) {
- buddy.rosterAlias = "";
- }
-
- if (subscription) {
- buddy.subscription = subscription;
- }
- if (!this._buddies.has(jid)) {
- this._buddies.set(jid, buddy);
- Services.contacts.accountBuddyAdded(buddy);
- } else if (aNotifyOfUpdates) {
- buddy._notifyObservers("status-detail-changed");
- }
-
- // Keep the xml nodes of the item so that we don't have to
- // recreate them when changing something (eg. the alias) in it.
- buddy._rosterItem = aItem;
-
- return jid;
- },
- _forgetRosterItem(aJID) {
- Services.contacts.accountBuddyRemoved(this._buddies.get(aJID));
- this._buddies.delete(aJID);
- },
-
- /* When the roster is received */
- onRoster(aStanza) {
- // For the first element that is a roster stanza.
- for (let qe of aStanza.getChildren("query")) {
- if (qe.uri != Stanza.NS.roster) {
- continue;
- }
-
- // Find all the roster items in the new message.
- let newRoster = new Set();
- for (let item of qe.getChildren("item")) {
- let jid = this._onRosterItem(item);
- if (jid) {
- newRoster.add(jid);
- }
- }
- // If an item was in the old roster, but not in the new, forget it.
- for (let jid of this._buddies.keys()) {
- if (!newRoster.has(jid)) {
- this._forgetRosterItem(jid);
- }
- }
- break;
- }
-
- this._sendPresence();
- this._buddies.forEach(b => {
- if (b.subscription == "both" || b.subscription == "to") {
- b.setStatus(Ci.imIStatusInfo.STATUS_OFFLINE, "");
- }
- });
- this.reportConnected();
- this._sendVCard();
- },
-
- /* Public methods */
-
- sendStanza(aStanza, aCallback, aThis, aLogString) {
- return this._connection.sendStanza(aStanza, aCallback, aThis, aLogString);
- },
-
- // Variations of the XMPP protocol can change these default constructors:
- _conversationConstructor: XMPPConversation,
- _MUCConversationConstructor: XMPPMUCConversation,
- _accountBuddyConstructor: XMPPAccountBuddy,
-
- /* Create a new conversation */
- createConversation(aName) {
- let convName = this.normalize(aName);
-
- // Checks if conversation is with a participant of a MUC we are in. We do
- // not want to strip the resource as it is of the form room@domain/nick.
- let isMucParticipant = this._mucs.has(convName);
- if (isMucParticipant) {
- convName = this.normalizeFullJid(aName);
- }
-
- // Checking that the aName can be parsed and is not broken.
- let jid = this._parseJID(convName);
- if (
- !jid ||
- !jid.domain ||
- (isMucParticipant && (!jid.node || !jid.resource))
- ) {
- this.ERROR("Could not create conversation as jid is broken: " + convName);
- throw new Error("Invalid JID");
- }
-
- if (!this._conv.has(convName)) {
- this._conv.set(
- convName,
- new this._conversationConstructor(this, convName, isMucParticipant)
- );
- }
-
- return this._conv.get(convName);
- },
-
- /* Remove an existing conversation */
- removeConversation(aNormalizedName) {
- if (this._conv.has(aNormalizedName)) {
- this._conv.delete(aNormalizedName);
- } else if (this._mucs.has(aNormalizedName)) {
- this._mucs.delete(aNormalizedName);
- }
- },
-
- /* Private methods */
-
- /* Disconnect from the server */
- /* The aError and aErrorMessage parameters are passed to reportDisconnecting
- * and used by the account manager.
- * The aQuiet parameter is to avoid sending status change notifications
- * during the uninitialization of the account. */
- _disconnect(
- aError = Ci.prplIAccount.NO_ERROR,
- aErrorMessage = "",
- aQuiet = false
- ) {
- if (!this._connection) {
- return;
- }
-
- this.reportDisconnecting(aError, aErrorMessage);
-
- this._buddies.forEach(b => {
- if (!aQuiet) {
- b.setStatus(Ci.imIStatusInfo.STATUS_UNKNOWN, "");
- }
- b.onAccountDisconnected();
- });
-
- this._mucs.forEach(muc => {
- muc.joining = false; // In case we never finished joining.
- muc.left = true;
- });
-
- this._connection.disconnect();
- delete this._connection;
-
- // We won't receive "user-icon-changed" notifications while the
- // account isn't connected, so clear the cache to avoid keeping an
- // obsolete icon.
- delete this._cachedUserIcon;
- // Also clear the cached user vCard, as we will want to redownload it
- // after reconnecting.
- delete this._userVCard;
-
- // Clear vCard requests.
- this._pendingVCardRequests = [];
-
- this.reportDisconnected();
- },
-
- /* Set the user status on the server */
- _sendPresence() {
- delete this._shouldSendPresenceForIdlenessChange;
-
- if (!this._connection) {
- return;
- }
-
- let si = this.imAccount.statusInfo;
- let statusType = si.statusType;
- let show = "";
- if (statusType == Ci.imIStatusInfo.STATUS_UNAVAILABLE) {
- show = "dnd";
- } else if (
- statusType == Ci.imIStatusInfo.STATUS_AWAY ||
- statusType == Ci.imIStatusInfo.STATUS_IDLE
- ) {
- show = "away";
- }
- let children = [];
- if (show) {
- children.push(Stanza.node("show", null, null, show));
- }
- let statusText = si.statusText;
- if (statusText) {
- children.push(Stanza.node("status", null, null, statusText));
- }
- if (this._idleSince) {
- let time = Math.floor(Date.now() / 1000) - this._idleSince;
- children.push(Stanza.node("query", Stanza.NS.last, { seconds: time }));
- }
- if (this.prefs.prefHasUserValue("priority")) {
- let priority = Math.max(-128, Math.min(127, this.getInt("priority")));
- if (priority) {
- children.push(Stanza.node("priority", null, null, priority.toString()));
- }
- }
- this.sendStanza(
- Stanza.presence({ "xml:lang": "en" }, children),
- aStanza => {
- // As we are implicitly subscribed to our own presence (rfc6121#4), we
- // will receive the presence stanza mirrored back to us. We don't need
- // to do anything with this response.
- return true;
- }
- );
- },
-
- _downloadingUserVCard: false,
- _downloadUserVCard() {
- // If a download is already in progress, don't start another one.
- if (this._downloadingUserVCard) {
- return;
- }
- this._downloadingUserVCard = true;
- let s = Stanza.iq("get", null, null, Stanza.node("vCard", Stanza.NS.vcard));
- this.sendStanza(s, this.onUserVCard, this);
- },
-
- onUserVCard(aStanza) {
- delete this._downloadingUserVCard;
- let userVCard = aStanza.getElement(["vCard"]) || null;
- if (userVCard) {
- // Strip any server-specific namespace off the incoming vcard
- // before storing it.
- this._userVCard = Stanza.node(
- "vCard",
- Stanza.NS.vcard,
- null,
- userVCard.children
- );
- }
-
- // If a user icon exists in the vCard we received from the server,
- // we need to ensure the line breaks in its binval are exactly the
- // same as those we would include if we sent the icon, and that
- // there isn't any other whitespace.
- if (this._userVCard) {
- let binval = this._userVCard.getElement(["PHOTO", "BINVAL"]);
- if (binval && binval.children.length) {
- binval = binval.children[0];
- binval.text = binval.text
- .replace(/[^A-Za-z0-9\+\/\=]/g, "")
- .replace(/.{74}/g, "$&\n");
- }
- } else {
- // Downloading the vCard failed.
- if (
- this.handleErrors({
- itemNotFound: () => false, // OK, no vCard exists yet.
- default: () => true,
- })(aStanza)
- ) {
- this.WARN(
- "Unexpected error retrieving the user's vcard, " +
- "so we won't attempt to set it either."
- );
- return;
- }
- // Set this so that we don't get into an infinite loop trying to download
- // the vcard again. The check in sendVCard is for hasOwnProperty.
- this._userVCard = null;
- }
- this._sendVCard();
- },
-
- _cachingUserIcon: false,
- _cacheUserIcon() {
- if (this._cachingUserIcon) {
- return;
- }
-
- let userIcon = this.imAccount.statusInfo.getUserIcon();
- if (!userIcon) {
- this._cachedUserIcon = null;
- this._sendVCard();
- return;
- }
-
- this._cachingUserIcon = true;
- let channel = NetUtil.newChannel({
- uri: userIcon,
- loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
- securityFlags: Ci.nsILoadInfo.SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS,
- contentPolicyType: Ci.nsIContentPolicy.TYPE_IMAGE,
- });
- NetUtil.asyncFetch(channel, (inputStream, resultCode) => {
- if (!Components.isSuccessCode(resultCode)) {
- return;
- }
- try {
- let type = channel.contentType;
- let buffer = NetUtil.readInputStreamToString(
- inputStream,
- inputStream.available()
- );
- let readImage = imgTools.decodeImageFromBuffer(
- buffer,
- buffer.length,
- type
- );
- let scaledImage;
- if (readImage.width <= 96 && readImage.height <= 96) {
- scaledImage = imgTools.encodeImage(readImage, type);
- } else {
- if (type != "image/jpeg") {
- type = "image/png";
- }
- scaledImage = imgTools.encodeScaledImage(readImage, type, 64, 64);
- }
-
- let bstream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
- Ci.nsIBinaryInputStream
- );
- bstream.setInputStream(scaledImage);
-
- let data = bstream.readBytes(bstream.available());
- this._cachedUserIcon = {
- type,
- binval: btoa(data).replace(/.{74}/g, "$&\n"),
- };
- } catch (e) {
- Cu.reportError(e);
- this._cachedUserIcon = null;
- }
- delete this._cachingUserIcon;
- this._sendVCard();
- });
- },
- _sendVCard() {
- if (!this._connection) {
- return;
- }
-
- // We have to download the user's existing vCard before updating it.
- // This lets us preserve the fields that we don't change or don't know.
- // Some servers may reject a new vCard if we don't do this first.
- if (!this.hasOwnProperty("_userVCard")) {
- // The download of the vCard is asynchronous and will call _sendVCard back
- // when the user's vCard has been received.
- this._downloadUserVCard();
- return;
- }
-
- // Read the local user icon asynchronously from the disk.
- // _cacheUserIcon will call _sendVCard back once the icon is ready.
- if (!this.hasOwnProperty("_cachedUserIcon")) {
- this._cacheUserIcon();
- return;
- }
-
- // If the user currently doesn't have any vCard on the server or
- // the download failed, an empty new one.
- if (!this._userVCard) {
- this._userVCard = Stanza.node("vCard", Stanza.NS.vcard);
- }
-
- // Keep a serialized copy of the existing user vCard so that we
- // can avoid resending identical data to the server.
- let existingVCard = this._userVCard.getXML();
-
- let fn = this._userVCard.getElement(["FN"]);
- let displayName = this.imAccount.statusInfo.displayName;
- if (displayName) {
- // If a display name is set locally, update or add an FN field to the vCard.
- if (!fn) {
- this._userVCard.addChild(
- Stanza.node("FN", Stanza.NS.vcard, null, displayName)
- );
- } else if (fn.children.length) {
- fn.children[0].text = displayName;
- } else {
- fn.addText(displayName);
- }
- } else if ("_forceUserDisplayNameUpdate" in this) {
- // We remove a display name stored on the server without replacing
- // it with a new value only if this _sendVCard call is the result of
- // a user action. This is to avoid removing data from the server each
- // time the user connects from a new profile.
- this._userVCard.children = this._userVCard.children.filter(
- n => n.qName != "FN"
- );
- }
- delete this._forceUserDisplayNameUpdate;
-
- if (this._cachedUserIcon) {
- // If we have a local user icon, update or add it in the PHOTO field.
- let photoChildren = [
- Stanza.node("TYPE", Stanza.NS.vcard, null, this._cachedUserIcon.type),
- Stanza.node(
- "BINVAL",
- Stanza.NS.vcard,
- null,
- this._cachedUserIcon.binval
- ),
- ];
- let photo = this._userVCard.getElement(["PHOTO"]);
- if (photo) {
- photo.children = photoChildren;
- } else {
- this._userVCard.addChild(
- Stanza.node("PHOTO", Stanza.NS.vcard, null, photoChildren)
- );
- }
- } else if ("_forceUserIconUpdate" in this) {
- // Like for the display name, we remove a photo without
- // replacing it only if the call is caused by a user action.
- this._userVCard.children = this._userVCard.children.filter(
- n => n.qName != "PHOTO"
- );
- }
- delete this._forceUserIconUpdate;
-
- // Send the vCard only if it has really changed.
- // We handle the result response from the server (it does not require
- // any further action).
- if (this._userVCard.getXML() != existingVCard) {
- this.sendStanza(
- Stanza.iq("set", null, null, this._userVCard),
- this._handleResult()
- );
- } else {
- this.LOG(
- "Not sending the vCard because the server stored vCard is identical."
- );
- }
- },
};
-function XMPPAccount(aProtocol, aImAccount) {
- this._pendingVCardRequests = [];
- this._init(aProtocol, aImAccount);
-}
-XMPPAccount.prototype = XMPPAccountPrototype;
deleted file mode 100644
--- a/chat/protocols/xmpp/xmpp.manifest
+++ /dev/null
@@ -1,3 +0,0 @@
-component {dde786d1-6f59-43d0-9bc8-b505a757fb30} xmpp.js
-contract @mozilla.org/chat/xmpp;1 {dde786d1-6f59-43d0-9bc8-b505a757fb30}
-category im-protocol-plugin prpl-jabber @mozilla.org/chat/xmpp;1
new file mode 100644
--- /dev/null
+++ b/chat/protocols/yahoo/components.conf
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ 'cid': '{50ea817e-5d79-4657-91ae-aa0a52bdb98c}',
+ 'contract_ids': ['@mozilla.org/chat/yahoo;1'],
+ 'jsm': 'resource:///modules/yahoo.jsm',
+ 'constructor': 'YahooProtocol',
+ 'categories': {'im-protocol-plugin': 'prpl-yahoo'},
+ },
+]
--- a/chat/protocols/yahoo/moz.build
+++ b/chat/protocols/yahoo/moz.build
@@ -1,11 +1,14 @@
# vim: set filetype=python:
# 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/.
-EXTRA_COMPONENTS += [
- 'yahoo.js',
- 'yahoo.manifest',
+JAR_MANIFESTS += ['jar.mn']
+
+EXTRA_JS_MODULES += [
+ 'yahoo.jsm',
]
-JAR_MANIFESTS += ['jar.mn']
+XPCOM_MANIFESTS += [
+ 'components.conf',
+]
rename from chat/protocols/yahoo/yahoo.js
rename to chat/protocols/yahoo/yahoo.jsm
--- a/chat/protocols/yahoo/yahoo.js
+++ b/chat/protocols/yahoo/yahoo.jsm
@@ -1,12 +1,14 @@
/* 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/. */
+var EXPORTED_SYMBOLS = ["YahooProtocol"];
+
var { XPCOMUtils, l10nHelper } = ChromeUtils.import(
"resource:///modules/imXPCOMUtils.jsm"
);
var { GenericAccountPrototype, GenericProtocolPrototype } = ChromeUtils.import(
"resource:///modules/jsProtoHelper.jsm"
);
XPCOMUtils.defineLazyGetter(this, "_", () =>
@@ -46,12 +48,9 @@ YahooProtocol.prototype = {
return "Yahoo";
},
get iconBaseURI() {
return "chrome://prpl-yahoo/skin/";
},
getAccount(aImAccount) {
return new YahooAccount(this, aImAccount);
},
- classID: Components.ID("{50ea817e-5d79-4657-91ae-aa0a52bdb98c}"),
};
-
-var NSGetFactory = XPCOMUtils.generateNSGetFactory([YahooProtocol]);
deleted file mode 100644
--- a/chat/protocols/yahoo/yahoo.manifest
+++ /dev/null
@@ -1,3 +0,0 @@
-component {50ea817e-5d79-4657-91ae-aa0a52bdb98c} yahoo.js
-contract @mozilla.org/chat/yahoo;1 {50ea817e-5d79-4657-91ae-aa0a52bdb98c}
-category im-protocol-plugin prpl-yahoo @mozilla.org/chat/yahoo;1
--- a/mail/installer/package-manifest.in
+++ b/mail/installer/package-manifest.in
@@ -215,48 +215,16 @@
#endif
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; instant messaging
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@RESPATH@/@PREF_DIR@/chat-prefs.js
@RESPATH@/chrome/chat@JAREXT@
@RESPATH@/chrome/chat.manifest
-@RESPATH@/components/imAccounts.js
-@RESPATH@/components/imAccounts.manifest
-@RESPATH@/components/imCommands.js
-@RESPATH@/components/imCommands.manifest
-@RESPATH@/components/imContacts.js
-@RESPATH@/components/imContacts.manifest
-@RESPATH@/components/imConversations.js
-@RESPATH@/components/imConversations.manifest
-@RESPATH@/components/imCore.js
-@RESPATH@/components/imCore.manifest
-@RESPATH@/components/facebook.js
-@RESPATH@/components/facebook.manifest
-@RESPATH@/components/gtalk.js
-@RESPATH@/components/gtalk.manifest
-@RESPATH@/components/irc.js
-@RESPATH@/components/irc.manifest
-@RESPATH@/components/matrix.js
-@RESPATH@/components/matrix.manifest
-@RESPATH@/components/odnoklassniki.js
-@RESPATH@/components/odnoklassniki.manifest
-@RESPATH@/components/skype.js
-@RESPATH@/components/skype.manifest
-@RESPATH@/components/twitter.js
-@RESPATH@/components/twitter.manifest
-@RESPATH@/components/xmpp.js
-@RESPATH@/components/xmpp.manifest
-@RESPATH@/components/yahoo.js
-@RESPATH@/components/yahoo.manifest
-@RESPATH@/components/smileProtocolHandler.js
-@RESPATH@/components/smileProtocolHandler.manifest
-@RESPATH@/components/logger.js
-@RESPATH@/components/logger.manifest
; Thunderbird specific
@RESPATH@/@PREF_DIR@/all-im.js
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Chrome Files
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;