diff --git a/.eslintrc.json b/.eslintrc.json index 176e0ca7b..a4ef9ed5f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -9,6 +9,7 @@ "plugins": ["lodash"], "extends": ["eslint:recommended", "plugin:lodash/canonical"], "globals": { + "Promise": true, "converse": true, "window": true, "sinon": true, diff --git a/CHANGES.md b/CHANGES.md index 158a287c9..45315b401 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,6 +19,7 @@ - Add a checkbox to indicate whether a trusted device is being used or not. If the device is not trusted, sessionStorage is used and all user data is deleted from the browser cache upon logout. If the device is trusted, localStorage is used and user data is cached indefinitely. +- Initial support for XEP-0357 Push Notifications, specifically registering an "App Server". ### Bugfixes diff --git a/dist/converse.js b/dist/converse.js index a4c6c1c04..41586eb54 100644 --- a/dist/converse.js +++ b/dist/converse.js @@ -64922,11 +64922,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); })(void 0, function (sizzle, Promise, _, f, polyfill, i18n, u, moment, Strophe, pluggable, Backbone) { - /* Cannot use this due to Safari bug. - * See https://github.com/jcbrand/converse.js/issues/196 - */ - // "use strict"; - // Strophe globals + "use strict"; // Strophe globals + const _Strophe = Strophe, $build = _Strophe.$build, $iq = _Strophe.$iq, @@ -64974,7 +64971,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ _.extend(_converse, Backbone.Events); // Core plugins are whitelisted automatically - _converse.core_plugins = ['converse-bookmarks', 'converse-chatboxes', 'converse-chatview', 'converse-caps', 'converse-controlbox', 'converse-core', 'converse-disco', 'converse-dragresize', 'converse-embedded', 'converse-fullscreen', 'converse-headline', 'converse-mam', 'converse-message-view', 'converse-minimize', 'converse-modal', 'converse-muc', 'converse-muc-views', 'converse-notification', 'converse-otr', 'converse-ping', 'converse-profile', 'converse-register', 'converse-roomslist', 'converse-roster', 'converse-rosterview', 'converse-singleton', 'converse-spoilers', 'converse-vcard']; // Make converse pluggable + _converse.core_plugins = ['converse-bookmarks', 'converse-caps', 'converse-chatboxes', 'converse-chatview', 'converse-controlbox', 'converse-core', 'converse-disco', 'converse-dragresize', 'converse-embedded', 'converse-fullscreen', 'converse-headline', 'converse-mam', 'converse-message-view', 'converse-minimize', 'converse-modal', 'converse-muc', 'converse-muc-views', 'converse-notification', 'converse-ping', 'converse-profile', 'converse-push', 'converse-register', 'converse-roomslist', 'converse-roster', 'converse-rosterview', 'converse-singleton', 'converse-spoilers', 'converse-vcard']; // Make converse pluggable pluggable.enable(_converse, '_converse', 'pluggable'); // Module-level constants @@ -65004,6 +65001,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ _converse.LOGOUT = "logout"; _converse.OPENED = 'opened'; _converse.PREBIND = "prebind"; + _converse.IQ_TIMEOUT = 30000; _converse.CONNECTION_STATUS = { 0: 'ERROR', 1: 'CONNECTING', @@ -65173,8 +65171,6 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ _converse.router = new Backbone.Router(); _converse.initialize = function (settings, callback) { - "use strict"; - settings = !_.isUndefined(settings) ? settings : {}; const init_promise = u.getResolveablePromise(); @@ -65564,7 +65560,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ const id = b64_sha1('converse.bosh-session'); _converse.session.id = id; // Appears to be necessary for backbone.browserStorage - _converse.session.browserStorage = new Backbone.BrowserStorage[_converse.storage](id); + _converse.session.browserStorage = new Backbone.BrowserStorage.session(id); _converse.session.fetch(); @@ -65682,7 +65678,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ _converse.log('An error occured while trying to enable message carbons.', Strophe.LogLevel.ERROR); } else { this.session.save({ - carbons_enabled: true + 'carbons_enabled': true }); _converse.log('Message carbons have been enabled.'); @@ -66272,6 +66268,12 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ 'send'(stanza) { _converse.connection.send(stanza); + }, + + 'sendIQ'(stanza) { + return new Promise((resolve, reject) => { + _converse.connection.sendIQ(stanza, resolve, reject, _converse.IQ_TIMEOUT); + }); } }; // The public API @@ -73287,6 +73289,94 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /***/ }), +/***/ "./src/converse-push.js": +/*!******************************!*\ + !*** ./src/converse-push.js ***! + \******************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__; + +// Converse.js +// https://conversejs.org +// +// Copyright (c) 2013-2018, the Converse.js developers +// Licensed under the Mozilla Public License (MPLv2) + +/* This is a Converse.js plugin which add support for registering + * an "App Server" as defined in XEP-0357 + */ +(function (root, factory) { + !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! converse-core */ "./src/converse-core.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), + __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? + (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), + __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); +})(void 0, function (converse) { + "use strict"; + + const _converse$env = converse.env, + Strophe = _converse$env.Strophe, + $iq = _converse$env.$iq; + Strophe.addNamespace('PUSH', 'urn:xmpp:push:0'); + converse.plugins.add('converse-push', { + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const _converse = this._converse, + __ = _converse.__; + + _converse.api.settings.update({ + 'push_service': undefined, + 'push_service_node': undefined, + 'push_service_secret': undefined + }); + + function enablePush() { + if (_converse.session.get('push_enabled')) { + return; + } + + if (_converse.push_service && _converse.push_service_node) { + Promise.all([_converse.api.disco.getIdentity('pubsub', 'push', _converse.push_service), _converse.api.disco.supports(Strophe.NS.PUSH, _converse.push_service)]).then(() => _converse.api.disco.supports(Strophe.NS.PUSH, _converse.bare_jid)).then(() => { + const stanza = $iq({ + 'type': 'set' + }).c('enable', { + 'xmlns': Strophe.NS.PUSH, + 'jid': _converse.push_service, + 'node': _converse.push_service_node + }); + + if (_converse.push_service_secret) { + stanza.c('x', { + 'xmlns': Strophe.NS.XFORM, + 'type': 'submit' + }).c('field', { + 'var': 'FORM_TYPE' + }).c('value').t(`${Strophe.NS.PUBSUB}#publish-options`).up().up().c('field', { + 'var': 'secret' + }).c('value').t(_converse.push_service_secret); + } + + _converse.api.sendIQ(stanza).then(() => _converse.session.set('push_enabled', true)).catch(e => { + _converse.log(`Could not enable push service for ${_converse.push_service}`, Strophe.LogLevel.ERROR); + + _converse.log(e, Strophe.LogLevel.ERROR); + }); + }); + } + } + + _converse.api.listen.on('statusInitialized', enablePush); + } + + }); +}); + +/***/ }), + /***/ "./src/converse-register.js": /*!**********************************!*\ !*** ./src/converse-register.js ***! @@ -76844,7 +76934,8 @@ if (true) { __webpack_require__(/*! converse-chatview */ "./src/converse-chatview.js"), // Renders standalone chat boxes for single user chat __webpack_require__(/*! converse-controlbox */ "./src/converse-controlbox.js"), // The control box __webpack_require__(/*! converse-dragresize */ "./src/converse-dragresize.js"), // Allows chat boxes to be resized by dragging them - __webpack_require__(/*! converse-embedded */ "./src/converse-embedded.js"), __webpack_require__(/*! converse-fullscreen */ "./src/converse-fullscreen.js"), __webpack_require__(/*! converse-headline */ "./src/converse-headline.js"), // Support for headline messages + __webpack_require__(/*! converse-embedded */ "./src/converse-embedded.js"), __webpack_require__(/*! converse-fullscreen */ "./src/converse-fullscreen.js"), __webpack_require__(/*! converse-push */ "./src/converse-push.js"), // XEP-0357 Push Notifications + __webpack_require__(/*! converse-headline */ "./src/converse-headline.js"), // Support for headline messages __webpack_require__(/*! converse-mam */ "./src/converse-mam.js"), // XEP-0313 Message Archive Management __webpack_require__(/*! converse-minimize */ "./src/converse-minimize.js"), // Allows chat boxes to be minimized __webpack_require__(/*! converse-muc */ "./src/converse-muc.js"), // XEP-0045 Multi-user chat diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst index 69af38318..283708184 100644 --- a/docs/source/configuration.rst +++ b/docs/source/configuration.rst @@ -7,10 +7,10 @@ Configuration ============= The included minified JavaScript and CSS files can be used for demoing or testing, but -you'll want to configure *Converse.js* to suit your needs before you deploy it +you'll want to configure *Converse* to suit your needs before you deploy it on your website. -*Converse.js* is passed its configuration settings when you call its *initialize* method. +*Converse* is passed its configuration settings when you call its *initialize* method. You'll most likely want to call the *initialize* method in your HTML page. For an example of how this is done, please see the bottom of the *./index.html* page. @@ -18,7 +18,7 @@ an example of how this is done, please see the bottom of the *./index.html* page Please refer to the `Configuration settings`_ section below for info on all the available configuration settings. -After you have configured *Converse.js*, you'll have to regenerate the minified +After you have configured *Converse*, you'll have to regenerate the minified JavaScript file so that it will include the new settings. Please refer to the :ref:`minification` section for more info on how to do this. @@ -83,7 +83,7 @@ requiring them to log in manually. When a BOSH session is initially created, you'll receive three tokens. A JID (jabber ID), SID (session ID) and RID (Request ID). -Converse.js needs these tokens in order to attach to that same session. +Converse needs these tokens in order to attach to that same session. There are two complementary configuration settings to ``prebind``. They are :ref:`keepalive` and `prebind_url`_. @@ -200,7 +200,7 @@ allow_public_bookmarks Some XMPP servers don't support private PEP/PubSub nodes, as required for private bookmarks and outlined in `XEP-0223 `_. -Even though Converse.js asks for the bookmarks to be kept private (via the +Even though Converse asks for the bookmarks to be kept private (via the `` XML node), the server simply ignores the privacy settings and publishes the node contents under the default privacy setting, which makes the information available to all roster contacts. @@ -382,7 +382,7 @@ A list of plugin names that are blacklisted and will therefore not be initialized once ``converse.initialize`` is called, even if the same plugin is whitelisted. -From Converse.js 3.0 onwards most of the API is available only to plugins and +From Converse 3.0 onwards most of the API is available only to plugins and all plugins need to be whitelisted first. The usecase for blacklisting is generally to disable removed core plugins @@ -495,7 +495,7 @@ connection_options * Default: ``{}`` * Type: Object -Converse.js relies on `Strophe.js `_ to establish and +Converse relies on `Strophe.js `_ to establish and maintain a connection to the XMPP server. This option allows you to pass a map of configuration options to be passed into @@ -725,7 +725,7 @@ The translations for that locale must be available in JSON format at the If an explicit locale is specified via the ``i18n`` setting and the translations for that locale are not found at the `locales_url``, then -then Converse.js will fall back to trying to determine the browser's language +then Converse will fall back to trying to determine the browser's language and fetching those translations, or if that fails the default English texts will be used. @@ -747,7 +747,7 @@ keepalive * Default: ``true`` -Determines whether Converse.js will maintain the chat session across page +Determines whether Converse will maintain the chat session across page loads. This setting should also be used in conjunction with ``authentication`` set to `prebind`_. @@ -785,7 +785,7 @@ locales 'ru', 'uk', 'zh' ] -This setting restricts the locales that are supported by Converse.js and +This setting restricts the locales that are supported by Converse and therefore what may be given as value for the :ref:`i18n` option. Any other locales will be ignored. @@ -800,7 +800,7 @@ locales_url * Default: ``/locale/{{{locale}}}/LC_MESSAGES/converse.json``, -The URL from where Converse.js should fetch translation JSON. +The URL from where Converse should fetch translation JSON. The three curly braces ``{{{ }}}`` are `Mustache `_-style @@ -812,7 +812,7 @@ The variable being interpolated via the curly braces is ``locale``, which is the value passed in to the `i18n`_ setting, or the browser's locale or the default local or `en` (resolved in that order). -From version 3.3.0, Converse.js no longer bundles all translations into its +From version 3.3.0, Converse no longer bundles all translations into its final build file. Instead, only the relevant translations are fetched at runtime. @@ -953,7 +953,7 @@ muc_show_join_leave * Default; ``true`` -Determines whether Converse.js will show info messages inside a chatroom +Determines whether Converse will show info messages inside a chatroom whenever a user joins or leaves it. nickname @@ -1007,7 +1007,7 @@ play_sounds Plays a notification sound when you receive a personal message or when your nickname is mentioned in a chatroom. -Inside the ``./sounds`` directory of the Converse.js repo you'll see MP3 and Ogg +Inside the ``./sounds`` directory of the Converse repo you'll see MP3 and Ogg formatted sound files. We need both, because neither format is supported by all browsers. You can set the URL where the sound files are hosted with the `sounds_path`_ @@ -1047,12 +1047,12 @@ priority * Type: Number Determines the priority used for presence stanzas sent out from this resource -(i.e. this instance of Converse.js). +(i.e. this instance of Converse). The priority of a given XMPP chat client determines the importance of its presence stanzas in relation to stanzas received from other clients of the same user. -In Converse.js, the indicated chat status of a roster contact will be taken from the +In Converse, the indicated chat status of a roster contact will be taken from the presence stanza (and associated resource) with the highest priority. If multiple resources have the same top priority, then the chat status will be @@ -1068,6 +1068,39 @@ providers_link The hyperlink on the registration form which points to a directory of public XMPP servers. +push_service +------------ + +* Default: ``undefined`` + +This option allows you to specify a URI for the push notifications service +(called an "App Server" by `XEP-0357 `_). + +If provided, together with a `push_service_node`_, then Converse will instruct +the user's XMPP server to send push notificatiosn to that URI. + +push_service_node +----------------- + +* Default: ``undefined`` + +This is the PubSub node of the push notifications service (aka "App Server") specified with the +`push_service`_ setting. + +Push notifications will be sent to this node. If this value is not set, then +push notifications won't be sent out. + +push_service_secret +------------------- + +* Default: ``undefined`` + +Some push notification services (aka "App Servers") require a secret token to +be used when sending out notifications. + +This setting enables you to provide such a secret to Converse which will +forward it to your XMPP server to be included in push notifications. + root ---- @@ -1108,7 +1141,7 @@ configured. .. note:: It's currently not possible to use converse.js to assign contacts to groups. - Converse.js can only show users and groups that were previously configured + Converse can only show users and groups that were previously configured elsewhere. show_chatstate_notifications @@ -1262,10 +1295,10 @@ loaded), then an error will be raised. Otherwise a message will simply be logged and the override instruction ignored. -The Converse.js plugins architecture can have an :ref:`dependencies` +The Converse plugins architecture can have an :ref:`dependencies` plugin attribute. This enables you to specify an array of other plugins which this one depends on. -Converse.js (more specifically, `pluggable.js `_) +Converse (more specifically, `pluggable.js `_) will first load these dependencies before executing the plugin's overrides and calling its ``initialize`` method. @@ -1302,7 +1335,7 @@ This setting determines whether the default value of the "This is a trusted devi When the current device is not trusted, then localStorage and sessionStorage will be cleared when the user logs out, thereby removing all cached data. -Clearing the cache in this way makes Converse.js much slower when the user logs +Clearing the cache in this way makes Converse much slower when the user logs in again, because all data needs to be fetch anew. See also `storage`_. @@ -1323,7 +1356,7 @@ use_otr_by_default * Default: ``false`` -If set to ``true``, Converse.js will automatically try to initiate an OTR (off-the-record) +If set to ``true``, Converse will automatically try to initiate an OTR (off-the-record) encrypted chat session every time you open a chatbox. visible_toolbar_buttons @@ -1382,7 +1415,7 @@ support. configuration setting). .. note:: - Converse.js does not yet support "keepalive" with websockets. + Converse does not yet support "keepalive" with websockets. .. _`view_mode`: @@ -1448,7 +1481,7 @@ whitelisted_plugins A list of plugin names that are whitelisted and will therefore be initialized once ``converse.initialize`` is called. -From Converse.js 3.0 onwards most of the API is available only to plugins and +From Converse 3.0 onwards most of the API is available only to plugins and all plugins need to be whitelisted first. This is done to prevent malicious scripts from using the API to trick users or diff --git a/spec/push.js b/spec/push.js new file mode 100644 index 000000000..384150854 --- /dev/null +++ b/spec/push.js @@ -0,0 +1,119 @@ +(function (root, factory) { + define(["jasmine", "mock", "test-utils"], factory); +} (this, function (jasmine, mock, test_utils) { + "use strict"; + var $iq = converse.env.$iq; + var Strophe = converse.env.Strophe; + var _ = converse.env._; + + describe("XEP-0357 Push Notifications", function () { + + it("can be enabled by specifying a push_service and push_service_node", + mock.initConverseWithPromises(null, + ['rosterGroupsFetched'], { + 'push_service': 'push-5@client.example', + 'push_service_node': 'yxs32uqsflafdk3iuqo' + }, function (done, _converse) { + + const IQ_stanzas = _converse.connection.IQ_stanzas; + let stanza; + + expect(_converse.push_service).toBe('push-5@client.example'); + expect(_converse.push_service_node).toBe('yxs32uqsflafdk3iuqo'); + expect(_converse.session.get('push_enabled')).toBeFalsy(); + + test_utils.waitUntilDiscoConfirmed( + _converse, _converse.push_service, + [{'category': 'pubsub', 'type':'push'}], + ['urn:xmpp:push:0'], [], 'info') + .then(() => test_utils.waitUntilDiscoConfirmed( + _converse, + _converse.bare_jid, + [{'category': 'account', 'type':'registered'}], + ['urn:xmpp:push:0'], [], 'info')) + .then(() => { + return test_utils.waitUntil(() => { + const node = _.filter(IQ_stanzas, function (iq) { + return iq.nodeTree.querySelector('iq[type="set"] enable[xmlns="urn:xmpp:push:0"]'); + }).pop(); + if (node) { + stanza = node.nodeTree; + return true; + } + }) + }).then(() => { + expect(stanza.outerHTML).toEqual( + ``+ + ''+ + '' + ) + _converse.connection._dataRecv(test_utils.createRequest($iq({ + 'to': _converse.connection.jid, + 'type': 'result', + 'id': stanza.getAttribute('id') + }))); + return test_utils.waitUntil(() => _converse.session.get('push_enabled')) + }).then(() => { + done(); + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + })); + + + it("can require a secret token to be included", + mock.initConverseWithPromises(null, + ['rosterGroupsFetched'], { + 'push_service': 'push-5@client.example', + 'push_service_node': 'yxs32uqsflafdk3iuqo', + 'push_service_secret': 'eruio234vzxc2kla-91' + }, function (done, _converse) { + + const IQ_stanzas = _converse.connection.IQ_stanzas; + let stanza; + + expect(_converse.push_service).toBe('push-5@client.example'); + expect(_converse.push_service_node).toBe('yxs32uqsflafdk3iuqo'); + expect(_converse.push_service_secret).toBe('eruio234vzxc2kla-91'); + expect(_converse.session.get('push_enabled')).toBeFalsy(); + + test_utils.waitUntilDiscoConfirmed( + _converse, _converse.push_service, + [{'category': 'pubsub', 'type':'push'}], + ['urn:xmpp:push:0'], [], 'info') + .then(() => test_utils.waitUntilDiscoConfirmed( + _converse, + _converse.bare_jid, + [{'category': 'account', 'type':'registered'}], + ['urn:xmpp:push:0'], [], 'info')) + .then(() => { + return test_utils.waitUntil(() => { + const node = _.filter(IQ_stanzas, function (iq) { + return iq.nodeTree.querySelector('iq[type="set"] enable[xmlns="urn:xmpp:push:0"]'); + }).pop(); + if (node) { + stanza = node.nodeTree; + return true; + } + }) + }).then(() => { + expect(stanza.outerHTML).toEqual( + ``+ + ''+ + ''+ + 'http://jabber.org/protocol/pubsub#publish-options'+ + 'eruio234vzxc2kla-91'+ + ''+ + ''+ + '' + ) + _converse.connection._dataRecv(test_utils.createRequest($iq({ + 'to': _converse.connection.jid, + 'type': 'result', + 'id': stanza.getAttribute('id') + }))); + return test_utils.waitUntil(() => _converse.session.get('push_enabled')) + }).then(() => { + done(); + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + })); + }); +})); diff --git a/src/converse-core.js b/src/converse-core.js index caa6e2751..7483b188e 100644 --- a/src/converse-core.js +++ b/src/converse-core.js @@ -20,11 +20,7 @@ "backbone.browserStorage" ], factory); }(this, function (sizzle, Promise, _, f, polyfill, i18n, u, moment, Strophe, pluggable, Backbone) { - - /* Cannot use this due to Safari bug. - * See https://github.com/jcbrand/converse.js/issues/196 - */ - // "use strict"; + "use strict"; // Strophe globals const { $build, $iq, $msg, $pres } = Strophe; @@ -72,9 +68,9 @@ // Core plugins are whitelisted automatically _converse.core_plugins = [ 'converse-bookmarks', + 'converse-caps', 'converse-chatboxes', 'converse-chatview', - 'converse-caps', 'converse-controlbox', 'converse-core', 'converse-disco', @@ -89,9 +85,9 @@ 'converse-muc', 'converse-muc-views', 'converse-notification', - 'converse-otr', 'converse-ping', 'converse-profile', + 'converse-push', 'converse-register', 'converse-roomslist', 'converse-roster', @@ -131,6 +127,8 @@ _converse.OPENED = 'opened'; _converse.PREBIND = "prebind"; + _converse.IQ_TIMEOUT = 30000; + _converse.CONNECTION_STATUS = { 0: 'ERROR', 1: 'CONNECTING', @@ -298,7 +296,6 @@ _converse.initialize = function (settings, callback) { - "use strict"; settings = !_.isUndefined(settings) ? settings : {}; const init_promise = u.getResolveablePromise(); @@ -638,7 +635,7 @@ _converse.session = new Backbone.Model(); const id = b64_sha1('converse.bosh-session'); _converse.session.id = id; // Appears to be necessary for backbone.browserStorage - _converse.session.browserStorage = new Backbone.BrowserStorage[_converse.storage](id); + _converse.session.browserStorage = new Backbone.BrowserStorage.session(id); _converse.session.fetch(); _converse.emit('sessionInitialized'); }; @@ -739,7 +736,7 @@ 'An error occured while trying to enable message carbons.', Strophe.LogLevel.ERROR); } else { - this.session.save({carbons_enabled: true}); + this.session.save({'carbons_enabled': true}); _converse.log('Message carbons have been enabled.'); } }, null, "iq", null, "enablecarbons"); @@ -1289,6 +1286,11 @@ 'send' (stanza) { _converse.connection.send(stanza); }, + 'sendIQ' (stanza) { + return new Promise((resolve, reject) => { + _converse.connection.sendIQ(stanza, resolve, reject, _converse.IQ_TIMEOUT); + }); + } }; // The public API diff --git a/src/converse-push.js b/src/converse-push.js new file mode 100644 index 000000000..331d530e5 --- /dev/null +++ b/src/converse-push.js @@ -0,0 +1,70 @@ +// Converse.js +// https://conversejs.org +// +// Copyright (c) 2013-2018, the Converse.js developers +// Licensed under the Mozilla Public License (MPLv2) + +/* This is a Converse.js plugin which add support for registering + * an "App Server" as defined in XEP-0357 + */ +(function (root, factory) { + define(["converse-core"], factory); +}(this, function (converse) { + "use strict"; + const { Strophe, $iq } = converse.env; + + + Strophe.addNamespace('PUSH', 'urn:xmpp:push:0'); + + + converse.plugins.add('converse-push', { + + initialize () { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const { _converse } = this, + { __ } = _converse; + + _converse.api.settings.update({ + 'push_service': undefined, + 'push_service_node': undefined, + 'push_service_secret': undefined + }); + + function enablePush() { + if (_converse.session.get('push_enabled')) { + return; + } + if (_converse.push_service && _converse.push_service_node) { + Promise.all([ + _converse.api.disco.getIdentity('pubsub', 'push', _converse.push_service), + _converse.api.disco.supports(Strophe.NS.PUSH, _converse.push_service) + ]).then(() => _converse.api.disco.supports(Strophe.NS.PUSH, _converse.bare_jid)) + .then(() => { + const stanza = $iq({'type': 'set'}) + .c('enable', { + 'xmlns': Strophe.NS.PUSH, + 'jid': _converse.push_service, + 'node': _converse.push_service_node + }); + if (_converse.push_service_secret) { + stanza.c('x', {'xmlns': Strophe.NS.XFORM, 'type': 'submit'}) + .c('field', {'var': 'FORM_TYPE'}) + .c('value').t(`${Strophe.NS.PUBSUB}#publish-options`).up().up() + .c('field', {'var': 'secret'}) + .c('value').t(_converse.push_service_secret); + } + _converse.api.sendIQ(stanza) + .then(() => _converse.session.set('push_enabled', true)) + .catch((e) => { + _converse.log(`Could not enable push service for ${_converse.push_service}`, Strophe.LogLevel.ERROR); + _converse.log(e, Strophe.LogLevel.ERROR); + }); + }); + } + } + _converse.api.listen.on('statusInitialized', enablePush); + } + }); +})); diff --git a/src/converse.js b/src/converse.js index f2f0c2777..eef4c9a54 100644 --- a/src/converse.js +++ b/src/converse.js @@ -14,6 +14,7 @@ if (typeof define !== 'undefined') { "converse-dragresize", // Allows chat boxes to be resized by dragging them "converse-embedded", "converse-fullscreen", + "converse-push", // XEP-0357 Push Notifications "converse-headline", // Support for headline messages "converse-mam", // XEP-0313 Message Archive Management "converse-minimize", // Allows chat boxes to be minimized diff --git a/tests/runner.js b/tests/runner.js index c75a520be..137856433 100644 --- a/tests/runner.js +++ b/tests/runner.js @@ -191,6 +191,7 @@ var specs = [ "spec/presence", "spec/eventemitter", "spec/ping", + "spec/push", "spec/xmppstatus", "spec/mam", // "spec/otr",