Selaa lähdekoodia

Use the settings API for setting and getting config settings

This is an initial step towards no longer storing and accessing settings
directly via the `_converse` object
JC Brand 5 vuotta sitten
vanhempi
commit
631b9bb438

+ 4 - 0
CHANGES.md

@@ -2,6 +2,10 @@
 
 ## 7.0.0 (Unreleased)
 
+*Note for plugin authors:*
+configuration settings should now be accessed via `_converse.api.settings.get` and not directly on the `_converse` object.
+Soon we'll deprecate the latter, so prepare now.
+
 - #1313: Stylistic improvements to the send button
 - #1490: Busy-loop when fetching registration form fails
 - #1535: Add option to destroy a MUC

+ 7 - 12
spec/converse.js

@@ -41,8 +41,8 @@
                 _converse.idle_seconds = 0; // Usually initialized by registerIntervalHandler
                 _converse.disco_entities.get(_converse.domain).features['urn:xmpp:csi:0'] = true; // Mock that the server supports CSI
 
-                _converse.csi_waiting_time = 3; // The relevant config option
-                while (i <= _converse.csi_waiting_time) {
+                _converse.api.settings.set('csi_waiting_time', 3);
+                while (i <= _converse.api.settings.get("csi_waiting_time")) {
                     expect(_converse.sendCSI).not.toHaveBeenCalled();
                     _converse.onEverySecond();
                     i++;
@@ -52,9 +52,6 @@
                 _converse.onUserActivity();
                 expect(_converse.sendCSI).toHaveBeenCalledWith('active');
                 expect(sent_stanza.toLocaleString()).toBe('<active xmlns="urn:xmpp:csi:0"/>');
-                // Reset values
-                _converse.csi_waiting_time = 0;
-                _converse.disco_entities.get(_converse.domain).features['urn:xmpp:csi:0'] = false;
                 done();
             }));
         });
@@ -66,13 +63,11 @@
                 // Usually initialized by registerIntervalHandler
                 _converse.idle_seconds = 0;
                 _converse.auto_changed_status = false;
-
-                // The relevant config options
-                _converse.auto_away = 3;
-                _converse.auto_xa = 6;
+                _converse.api.settings.set('auto_away', 3);
+                _converse.api.settings.set('auto_xa', 6);
 
                 expect(_converse.api.user.status.get()).toBe('online');
-                while (i <= _converse.auto_away) {
+                while (i <= _converse.api.settings.get("auto_away")) {
                     _converse.onEverySecond(); i++;
                 }
                 expect(_converse.auto_changed_status).toBe(true);
@@ -92,7 +87,7 @@
                 // Check that it also works for the chat feature
                 _converse.api.user.status.set('chat')
                 i = 0;
-                while (i <= _converse.auto_away) {
+                while (i <= _converse.api.settings.get("auto_away")) {
                     _converse.onEverySecond();
                     i++;
                 }
@@ -112,7 +107,7 @@
                 // Check that it doesn't work for 'dnd'
                 _converse.api.user.status.set('dnd');
                 i = 0;
-                while (i <= _converse.auto_away) {
+                while (i <= _converse.api.settings.get("auto_away")) {
                     _converse.onEverySecond();
                     i++;
                 }

+ 1 - 1
src/converse-bookmark-views.js

@@ -203,7 +203,7 @@ converse.plugins.add('converse-bookmark-views', {
             },
 
             toHTML () {
-                const is_hidden = b => !!(_converse.hide_open_bookmarks && _converse.chatboxes.get(b.get('jid')));
+                const is_hidden = b => !!(_converse.api.settings.get('hide_open_bookmarks') && _converse.chatboxes.get(b.get('jid')));
                 return tpl_bookmarks_list({
                     '_converse': _converse,
                     'bookmarks': this.model,

+ 4 - 4
src/converse-chatboxviews.js

@@ -74,7 +74,7 @@ converse.plugins.add('converse-chatboxviews', {
                     if (el === null) {
                         el = document.createElement('div');
                         el.setAttribute('id', 'conversejs');
-                        u.addClass(`theme-${_converse.theme}`, el);
+                        u.addClass(`theme-${_converse.api.settings.get('theme')}`, el);
                         const body = _converse.root.querySelector('body');
                         if (body) {
                             body.appendChild(el);
@@ -97,9 +97,9 @@ converse.plugins.add('converse-chatboxviews', {
                     bg.innerHTML = tpl_background_logo();
                 }
                 const body = document.querySelector('body');
-                body.classList.add(`converse-${_converse.view_mode}`);
-                this.el.classList.add(`converse-${_converse.view_mode}`);
-                if (_converse.singleton) {
+                body.classList.add(`converse-${_converse.api.settings.get("view_mode")}`);
+                this.el.classList.add(`converse-${_converse.api.settings.get("view_mode")}`);
+                if (_converse.api.settings.get("singleton")) {
                     this.el.classList.add(`converse-singleton`);
                 }
                 this.render();

+ 18 - 20
src/converse-chatview.js

@@ -64,7 +64,6 @@ converse.plugins.add('converse-chatview', {
             'show_send_button': false,
             'show_retraction_warning': true,
             'show_toolbar': true,
-            'time_format': 'HH:mm',
             'visible_toolbar_buttons': {
                 'call': false,
                 'clear': true,
@@ -270,7 +269,7 @@ converse.plugins.add('converse-chatview', {
             },
 
             renderToolbar () {
-                if (!_converse.show_toolbar) {
+                if (!_converse.api.settings.get('show_toolbar')) {
                     return this;
                 }
                 const options = Object.assign(
@@ -295,13 +294,13 @@ converse.plugins.add('converse-chatview', {
                 form_container.innerHTML = tpl_chatbox_message_form(
                     Object.assign(this.model.toJSON(), {
                         '__': __,
-                        'message_limit': _converse.message_limit,
+                        'message_limit': _converse.api.settings.get('message_limit'),
                         'hint_value': this.el.querySelector('.spoiler-hint')?.value,
                         'label_message': this.model.get('composing_spoiler') ? __('Hidden message') : __('Message'),
                         'label_spoiler_hint': __('Optional hint'),
                         'message_value': this.el.querySelector('.chat-textarea')?.value,
-                        'show_send_button': _converse.show_send_button,
-                        'show_toolbar': _converse.show_toolbar,
+                        'show_send_button': _converse.api.settings.get('show_send_button'),
+                        'show_toolbar': _converse.api.settings.get('show_toolbar'),
                         'unread_msgs': __('You have unread messages')
                     }));
                 this.el.addEventListener('focusin', ev => this.emitFocused(ev));
@@ -430,9 +429,9 @@ converse.plugins.add('converse-chatview', {
                     'i18n_title': __('See more information about this person'),
                     'icon_class': 'fa-id-card',
                     'name': 'details',
-                    'standalone': _converse.view_mode === 'overlayed',
+                    'standalone': _converse.api.settings.get("view_mode") === 'overlayed',
                 }];
-                if (!_converse.singleton) {
+                if (!_converse.api.settings.get("singleton")) {
                     buttons.push({
                         'a_class': 'close-chatbox-button',
                         'handler': ev => this.close(ev),
@@ -440,7 +439,7 @@ converse.plugins.add('converse-chatview', {
                         'i18n_title': __('Close and end this conversation'),
                         'icon_class': 'fa-times',
                         'name': 'close',
-                        'standalone': _converse.view_mode === 'overlayed',
+                        'standalone': _converse.api.settings.get("view_mode") === 'overlayed',
                     });
                 }
                 return buttons;
@@ -457,9 +456,9 @@ converse.plugins.add('converse-chatview', {
                     'label_clear': __('Clear all messages'),
                     'label_message_limit': __('Message characters remaining'),
                     'label_toggle_spoiler': label_toggle_spoiler,
-                    'message_limit': _converse.message_limit,
-                    'show_call_button': _converse.visible_toolbar_buttons.call,
-                    'show_spoiler_button': _converse.visible_toolbar_buttons.spoiler,
+                    'message_limit': _converse.api.settings.get('message_limit'),
+                    'show_call_button': _converse.api.settings.get('visible_toolbar_buttons').call,
+                    'show_spoiler_button': _converse.api.settings.get('visible_toolbar_buttons').spoiler,
                     'tooltip_start_call': __('Start a call')
                 }
             },
@@ -825,7 +824,7 @@ converse.plugins.add('converse-chatview', {
                 ev.preventDefault();
                 const textarea = this.el.querySelector('.chat-textarea');
                 const message_text = textarea.value.trim();
-                if (_converse.message_limit && message_text.length > _converse.message_limit ||
+                if (_converse.api.settings.get('message_limit') && message_text.length > _converse.api.settings.get('message_limit') ||
                         !message_text.replace(/\s/g, '').length) {
                     return;
                 }
@@ -863,7 +862,7 @@ converse.plugins.add('converse-chatview', {
                      */
                     _converse.api.trigger('messageSend', message);
                 }
-                if (_converse.view_mode === 'overlayed') {
+                if (_converse.api.settings.get("view_mode") === 'overlayed') {
                     // XXX: Chrome flexbug workaround. The .chat-content area
                     // doesn't resize when the textarea is resized to its original size.
                     this.msgs_container.parentElement.style.display = 'none';
@@ -871,11 +870,10 @@ converse.plugins.add('converse-chatview', {
                 textarea.removeAttribute('disabled');
                 u.removeClass('disabled', textarea);
 
-                if (_converse.view_mode === 'overlayed') {
+                if (_converse.api.settings.get("view_mode") === 'overlayed') {
                     // XXX: Chrome flexbug workaround.
                     this.msgs_container.parentElement.style.display = '';
                 }
-
                 // Suppress events, otherwise superfluous CSN gets set
                 // immediately after the message, causing rate-limiting issues.
                 this.model.setChatState(_converse.ACTIVE, {'silent': true});
@@ -883,9 +881,9 @@ converse.plugins.add('converse-chatview', {
             },
 
             updateCharCounter (chars) {
-                if (_converse.message_limit) {
+                if (_converse.api.settings.get('message_limit')) {
                     const message_limit = this.el.querySelector('.message-limit');
-                    const counter = _converse.message_limit - chars.length;
+                    const counter = _converse.api.settings.get('message_limit') - chars.length;
                     message_limit.textContent = counter;
                     if (counter < 1) {
                         u.addClass('error', message_limit);
@@ -994,7 +992,7 @@ converse.plugins.add('converse-chatview', {
                         "be removed everywhere.");
 
                 const messages = [__('Are you sure you want to retract this message?')];
-                if (_converse.show_retraction_warning) {
+                if (_converse.api.settings.get('show_retraction_warning')) {
                     messages[1] = retraction_warning;
                 }
                 const result = await _converse.api.confirm(__('Confirm'), messages);
@@ -1264,7 +1262,7 @@ converse.plugins.add('converse-chatview', {
             },
 
             maybeFocus () {
-                _converse.auto_focus && this.focus();
+                _converse.api.settings.get('auto_focus') && this.focus();
             },
 
             hide () {
@@ -1293,7 +1291,7 @@ converse.plugins.add('converse-chatview', {
                  */
                 _converse.api.trigger('beforeShowingChatView', this);
 
-                if (_converse.animate) {
+                if (_converse.api.settings.get('animate')) {
                     u.fadeIn(this.el, () => this.afterShown());
                 } else {
                     u.showElement(this.el);

+ 19 - 19
src/converse-controlbox.js

@@ -70,7 +70,7 @@ converse.plugins.add('converse-controlbox', {
 
 
     enabled (_converse) {
-        return !_converse.singleton;
+        return !_converse.api.settings.get("singleton");
     },
 
 
@@ -119,7 +119,7 @@ converse.plugins.add('converse-controlbox', {
                     'bookmarked': false,
                     'box_id': 'controlbox',
                     'chat_state': undefined,
-                    'closed': !_converse.show_controlbox_by_default,
+                    'closed': !_converse.api.settings.get('show_controlbox_by_default'),
                     'num_unread': 0,
                     'time_opened': this.get('time_opened') || (new Date()).getTime(),
                     'type': _converse.CONTROLBOX_TYPE,
@@ -137,7 +137,7 @@ converse.plugins.add('converse-controlbox', {
 
             validate (attrs) {
                 if (attrs.type === _converse.CONTROLBOX_TYPE) {
-                    if (_converse.view_mode === 'embedded' && _converse.singleton)  {
+                    if (_converse.api.settings.get("view_mode") === 'embedded' && _converse.api.settings.get("singleton"))  {
                         return 'Controlbox not relevant in embedded view mode';
                     }
                     return;
@@ -197,7 +197,7 @@ converse.plugins.add('converse-controlbox', {
             render () {
                 if (this.model.get('connected')) {
                     if (this.model.get('closed') === undefined) {
-                        this.model.set('closed', !_converse.show_controlbox_by_default);
+                        this.model.set('closed', !_converse.api.settings.get('show_controlbox_by_default'));
                     }
                 }
                 this.el.innerHTML = tpl_controlbox(Object.assign(this.model.toJSON()));
@@ -225,7 +225,7 @@ converse.plugins.add('converse-controlbox', {
 
              createBrandHeadingHTML () {
                 return tpl_brand_heading({
-                    'sticky_controlbox': _converse.sticky_controlbox
+                    'sticky_controlbox': _converse.api.settings.get('sticky_controlbox')
                 });
             },
 
@@ -285,10 +285,10 @@ converse.plugins.add('converse-controlbox', {
                 if (ev && ev.preventDefault) { ev.preventDefault(); }
                 if (ev?.name === 'closeAllChatBoxes' &&
                         (_converse.disconnection_cause !== _converse.LOGOUT ||
-                         _converse.show_controlbox_by_default)) {
+                         _converse.api.settings.get('show_controlbox_by_default'))) {
                     return;
                 }
-                if (_converse.sticky_controlbox) {
+                if (_converse.api.settings.get('sticky_controlbox')) {
                     return;
                 }
                 const connection = _converse?.connection || {};
@@ -315,7 +315,7 @@ converse.plugins.add('converse-controlbox', {
             },
 
             hide (callback) {
-                if (_converse.sticky_controlbox) {
+                if (_converse.api.settings.get('sticky_controlbox')) {
                     return;
                 }
                 u.addClass('hidden', this.el);
@@ -387,12 +387,12 @@ converse.plugins.add('converse-controlbox', {
                         'LOGIN': _converse.LOGIN,
                         'PREBIND': _converse.PREBIND,
                         'auto_login': _converse.auto_login,
-                        'authentication': _converse.authentication,
+                        'authentication': _converse.api.settings.get("authentication"),
                         'connection_status': connection_status,
                         'conn_feedback_class': feedback_class,
                         'conn_feedback_subject': pretty_status,
                         'conn_feedback_message': _converse.connfeedback.get('message'),
-                        'placeholder_username': (_converse.locked_domain || _converse.default_domain) &&
+                        'placeholder_username': (_converse.api.settings.get('locked_domain') || _converse.api.settings.get('default_domain')) &&
                                                 __('Username') || __('user@domain'),
                         'show_trust_checkbox': _converse.trusted !== 'on' && _converse.trusted !== 'off'
                     })
@@ -402,8 +402,8 @@ converse.plugins.add('converse-controlbox', {
             initPopovers () {
                 Array.from(this.el.querySelectorAll('[data-title]')).forEach(el => {
                     new bootstrap.Popover(el, {
-                        'trigger': _converse.view_mode === 'mobile' && 'click' || 'hover',
-                        'dismissible': _converse.view_mode === 'mobile' && true || false,
+                        'trigger': _converse.api.settings.get("view_mode") === 'mobile' && 'click' || 'hover',
+                        'dismissible': _converse.api.settings.get("view_mode") === 'mobile' && true || false,
                         'container': this.el.parentElement.parentElement.parentElement
                     })
                 });
@@ -413,8 +413,8 @@ converse.plugins.add('converse-controlbox', {
                 const form = this.el.querySelector('form');
                 const jid_element = form.querySelector('input[name=jid]');
                 if (jid_element.value &&
-                        !_converse.locked_domain &&
-                        !_converse.default_domain &&
+                        !_converse.api.settings.get('locked_domain') &&
+                        !_converse.api.settings.get('default_domain') &&
                         !u.isValidJID(jid_element.value)) {
                     jid_element.setCustomValidity(__('Please enter a valid XMPP address'));
                     return false;
@@ -427,7 +427,7 @@ converse.plugins.add('converse-controlbox', {
                 /* Authenticate the user based on a form submission event.
                  */
                 if (ev && ev.preventDefault) { ev.preventDefault(); }
-                if (_converse.authentication === _converse.ANONYMOUS) {
+                if (_converse.api.settings.get("authentication") === _converse.ANONYMOUS) {
                     return this.connect(_converse.jid, null);
                 }
                 if (!this.validate()) { return; }
@@ -447,14 +447,14 @@ converse.plugins.add('converse-controlbox', {
                 }
 
                 let jid = form_data.get('jid');
-                if (_converse.locked_domain) {
-                    const last_part = '@' + _converse.locked_domain;
+                if (_converse.api.settings.get('locked_domain')) {
+                    const last_part = '@' + _converse.api.settings.get('locked_domain');
                     if (jid.endsWith(last_part)) {
                         jid = jid.substr(0, jid.length - last_part.length);
                     }
                     jid = Strophe.escapeNode(jid) + last_part;
-                } else if (_converse.default_domain && !jid.includes('@')) {
-                    jid = jid + '@' + _converse.default_domain;
+                } else if (_converse.api.settings.get('default_domain') && !jid.includes('@')) {
+                    jid = jid + '@' + _converse.api.settings.get('default_domain');
                 }
                this.connect(jid, form_data.get('password'));
             },

+ 13 - 5
src/converse-dragresize.js

@@ -36,7 +36,7 @@ converse.plugins.add('converse-dragresize', {
     dependencies: ["converse-chatview", "converse-headlines-view", "converse-muc-views"],
 
     enabled (_converse) {
-        return _converse.view_mode == 'overlayed';
+        return _converse.api.settings.get("view_mode") == 'overlayed';
     },
 
     overrides: {
@@ -252,7 +252,9 @@ converse.plugins.add('converse-dragresize', {
             },
 
             onStartVerticalResize (ev, trigger=true) {
-                if (!_converse.allow_dragresize) { return true; }
+                if (!_converse.api.settings.get('allow_dragresize')) {
+                    return true;
+                }
                 // Record element attributes for mouseMove().
                 const flyout = this.el.querySelector('.box-flyout'),
                       style = window.getComputedStyle(flyout);
@@ -273,7 +275,9 @@ converse.plugins.add('converse-dragresize', {
             },
 
             onStartHorizontalResize (ev, trigger=true) {
-                if (!_converse.allow_dragresize) { return true; }
+                if (!_converse.api.settings.get('allow_dragresize')) {
+                    return true;
+                }
                 const flyout = this.el.querySelector('.box-flyout'),
                       style = window.getComputedStyle(flyout);
                 this.width = parseInt(style.width.replace(/px$/, ''), 10);
@@ -328,14 +332,18 @@ converse.plugins.add('converse-dragresize', {
 
 
         function onMouseMove (ev) {
-            if (!_converse.resizing || !_converse.allow_dragresize) { return true; }
+            if (!_converse.resizing || !_converse.api.settings.get('allow_dragresize')) {
+                return true;
+            }
             ev.preventDefault();
             _converse.resizing.chatbox.resizeChatBox(ev);
         }
 
 
         function onMouseUp (ev) {
-            if (!_converse.resizing || !_converse.allow_dragresize) { return true; }
+            if (!_converse.resizing || !_converse.api.settings.get('allow_dragresize')) {
+                return true;
+            }
             ev.preventDefault();
             const height = u.applyDragResistance(
                     _converse.resizing.chatbox.height,

+ 2 - 2
src/converse-emoji-views.js

@@ -151,7 +151,7 @@ converse.plugins.add('converse-emoji-views', {
                     Object.assign(
                         this.model.toJSON(), {
                             '_converse': _converse,
-                            'emoji_categories': _converse.emoji_categories,
+                            'emoji_categories': _converse.api.settings.get('emoji_categories'),
                             'emojis_by_category': _converse.emojis.json,
                             'onSkintonePicked': ev => this.chooseSkinTone(ev),
                             'onEmojiPicked': ev => this.insertEmoji(ev),
@@ -418,7 +418,7 @@ converse.plugins.add('converse-emoji-views', {
         _converse.api.listen.on('chatBoxClosed', view => view.emoji_picker_view && view.emoji_picker_view.remove());
 
         _converse.api.listen.on('renderToolbar', view => {
-            if (_converse.visible_toolbar_buttons.emoji) {
+            if (_converse.api.settings.get('visible_toolbar_buttons').emoji) {
                 const html = tpl_emoji_button({'tooltip_insert_smiley': __('Insert emojis')});
                 view.el.querySelector('.chat-toolbar').insertAdjacentHTML('afterBegin', html);
             }

+ 5 - 5
src/converse-message-view.js

@@ -67,7 +67,7 @@ converse.plugins.add('converse-message-view', {
 
         _converse.api.settings.update({
             'show_images_inline': true,
-            'allow_message_retraction': 'all'
+            'time_format': 'HH:mm',
         });
 
         _converse.MessageVersionsModal = BootstrapModal.extend({
@@ -153,7 +153,7 @@ converse.plugins.add('converse-message-view', {
             },
 
             fadeOut () {
-                if (_converse.animate) {
+                if (_converse.api.settings.get('animate')) {
                     setTimeout(() => this.remove(), 600);
                     u.addClass('fade-out', this.el);
                 } else {
@@ -207,7 +207,7 @@ converse.plugins.add('converse-message-view', {
                 await _converse.api.trigger('beforeMessageBodyTransformed', this, text, {'Synchronous': true});
                 text = this.model.isMeCommand() ? text.substring(4) : text;
                 text = xss.filterXSS(text, {'whiteList': {}, 'onTag': onTagFoundDuringXSSFilter});
-                text = u.geoUriToHttp(text, _converse.geouri_replacement);
+                text = u.geoUriToHttp(text, _converse.api.settings.get("geouri_replacement"));
                 text = u.addMentionsMarkup(text, this.model.get('references'), this.model.collection.chatbox);
                 text = u.addHyperlinks(text);
                 text = u.renderNewLines(text);
@@ -243,7 +243,7 @@ converse.plugins.add('converse-message-view', {
                         'is_me_message': this.model.isMeCommand(),
                         'label_show': __('Show more'),
                         'occupant': this.model.occupant,
-                        'pretty_time': time.format(_converse.time_format),
+                        'pretty_time': time.format(_converse.api.settings.get('time_format')),
                         'retraction_text': is_retracted ? this.getRetractionText() : null,
                         'roles': roles,
                         'time': time.toISOString(),
@@ -259,7 +259,7 @@ converse.plugins.add('converse-message-view', {
                     const msg_content = msg.querySelector('.chat-msg__text');
                     if (text && text !== url) {
                         msg_content.innerHTML = await this.transformBodyText(text);
-                        if (_converse.show_images_inline) {
+                        if (_converse.api.settings.get('show_images_inline')) {
                             u.renderImageURLs(_converse, msg_content).then(() => this.triggerRendered());
                         }
                     }

+ 5 - 5
src/converse-minimize.js

@@ -40,7 +40,7 @@ converse.plugins.add('converse-minimize', {
     ],
 
     enabled (_converse) {
-        return _converse.view_mode === 'overlayed';
+        return _converse.api.settings.get("view_mode") === 'overlayed';
     },
 
     overrides: {
@@ -81,7 +81,7 @@ converse.plugins.add('converse-minimize', {
 
             show () {
                 const { _converse } = this.__super__;
-                if (_converse.view_mode === 'overlayed' && this.model.get('minimized')) {
+                if (_converse.api.settings.get("view_mode") === 'overlayed' && this.model.get('minimized')) {
                     this.model.minimize();
                     return this;
                 } else {
@@ -121,7 +121,7 @@ converse.plugins.add('converse-minimize', {
                     'i18n_title': __('Minimize this chat'),
                     'icon_class': "fa-minus",
                     'name': 'minimize',
-                    'standalone': _converse.view_mode === 'overlayed'
+                    'standalone': _converse.api.settings.get("view_mode") === 'overlayed'
                 }
                 const names = buttons.map(t => t.name);
                 const idx = names.indexOf('close');
@@ -149,7 +149,7 @@ converse.plugins.add('converse-minimize', {
                     'i18n_title': __('Minimize this groupchat'),
                     'icon_class': "fa-minus",
                     'name': 'minimize',
-                    'standalone': _converse.view_mode === 'overlayed'
+                    'standalone': _converse.api.settings.get("view_mode") === 'overlayed'
                 }
                 const names = buttons.map(t => t.name);
                 const idx = names.indexOf('signout');
@@ -314,7 +314,7 @@ converse.plugins.add('converse-minimize', {
              * @param { _converse.ChatBoxView|_converse.ChatRoomView|_converse.ControlBoxView|_converse.HeadlinesBoxView } [newchat]
              */
             async trimChats (newchat) {
-                if (_converse.no_trimming || !_converse.api.connection.connected() || _converse.view_mode !== 'overlayed') {
+                if (_converse.api.settings.get('no_trimming') || !_converse.api.connection.connected() || _converse.api.settings.get("view_mode") !== 'overlayed') {
                     return;
                 }
                 const shown_chats = this.getShownChats();

+ 2 - 2
src/converse-muc-views.js

@@ -1225,7 +1225,7 @@ converse.plugins.add('converse-muc-views', {
                     });
                 }
 
-                if (!_converse.singleton) {
+                if (!_converse.api.settings.get("singleton")) {
                     buttons.push({
                         'i18n_text': __('Leave'),
                         'i18n_title': __('Leave and close this groupchat'),
@@ -1235,7 +1235,7 @@ converse.plugins.add('converse-muc-views', {
                             result && this.close(ev);
                         },
                         'a_class': 'close-chatbox-button',
-                        'standalone': _converse.view_mode === 'overlayed',
+                        'standalone': _converse.api.settings.get("view_mode") === 'overlayed',
                         'icon_class': 'fa-sign-out-alt',
                         'name': 'signout'
                     });

+ 3 - 3
src/converse-notification.js

@@ -30,7 +30,7 @@ converse.plugins.add('converse-notification', {
             chatstate_notification_blacklist: [],
             // ^ a list of JIDs to ignore concerning chat state notifications
             play_sounds: true,
-            sounds_path: _converse.assets_path+'/sounds/',
+            sounds_path: _converse.api.settings.get("assets_path")+'/sounds/',
             notification_icon: 'logo/conversejs-filled.svg',
             notification_delay: 5000
         });
@@ -135,7 +135,7 @@ converse.plugins.add('converse-notification', {
             const full_from_jid = message.getAttribute('from'),
                   from_jid = Strophe.getBareJidFromJid(full_from_jid);
             if (message.getAttribute('type') === 'headline') {
-                if (!from_jid.includes('@') || _converse.allow_non_roster_messaging) {
+                if (!from_jid.includes('@') || _converse.api.settings.get("allow_non_roster_messaging")) {
                     title = __("Notification from %1$s", from_jid);
                 } else {
                     return;
@@ -154,7 +154,7 @@ converse.plugins.add('converse-notification', {
                 if (roster_item !== undefined) {
                     title = __("%1$s says", roster_item.getDisplayName());
                 } else {
-                    if (_converse.allow_non_roster_messaging) {
+                    if (_converse.api.settings.get("allow_non_roster_messaging")) {
                         title = __("%1$s says", from_jid);
                     } else {
                         return;

+ 2 - 2
src/converse-oauth.js

@@ -56,7 +56,7 @@ converse.plugins.add("converse-oauth", {
             render () {
                 const { _converse } = this.__super__;
                 const result = this.__super__.render.apply(this, arguments);
-                if (_converse.oauth_providers && !_converse.auto_login) {
+                if (_converse.oauth_providers && !_converse.api.settings.get("auto_login")) {
                     this.insertOAuthProviders();
                 }
                 return result;
@@ -79,7 +79,7 @@ converse.plugins.add("converse-oauth", {
             'sync': function sync () {},
 
             initialize () {
-                _converse.user_settings.oauth_providers.forEach(provider => {
+                _converse.api.settings.get('oauth_providers').forEach(provider => {
                     const item = new Model(Object.assign(provider, {
                         'login_text': __('Log in with %1$s', provider.name)
                     }));

+ 3 - 3
src/converse-omemo.js

@@ -66,7 +66,7 @@ function parseBundle (bundle_el) {
 converse.plugins.add('converse-omemo', {
 
     enabled (_converse) {
-        return window.libsignal && !_converse.blacklisted_plugins.includes('converse-omemo') && _converse.config.get('trusted');
+        return window.libsignal && !_converse.api.settings.get("blacklisted_plugins").includes('converse-omemo') && _converse.config.get('trusted');
     },
 
     dependencies: ["converse-chatview", "converse-pubsub", "converse-profile"],
@@ -306,7 +306,7 @@ converse.plugins.add('converse-omemo', {
             },
 
             reportDecryptionError (e) {
-                if (_converse.loglevel === 'debug') {
+                if (_converse.api.settings.get("loglevel") === 'debug') {
                     const { __ } = _converse;
                     this.createMessage({
                         'message': __("Sorry, could not decrypt a received OMEMO message due to an error.") + ` ${e.name} ${e.message}`,
@@ -1222,7 +1222,7 @@ converse.plugins.add('converse-omemo', {
                 supported = await _converse.contactHasOMEMOSupport(chatbox.get('jid'));
             }
             chatbox.set('omemo_supported', supported);
-            if (supported && _converse.omemo_default) {
+            if (supported && _converse.api.settings.get('omemo_default')) {
                 chatbox.set('omemo_active', true);
             }
         }

+ 3 - 3
src/converse-push.js

@@ -97,8 +97,8 @@ converse.plugins.add('converse-push', {
             if (push_enabled.includes(domain)) {
                 return;
             }
-            const enabled_services = reject(_converse.push_app_servers, 'disable');
-            const disabled_services = filter(_converse.push_app_servers, 'disable');
+            const enabled_services = reject(_converse.api.settings.get('push_app_servers'), 'disable');
+            const disabled_services = filter(_converse.api.settings.get('push_app_servers'), 'disable');
             const enabled = enabled_services.map(s => enablePushAppServer(domain, s));
             const disabled = disabled_services.map(s => disablePushAppServer(domain, s));
             try {
@@ -118,7 +118,7 @@ converse.plugins.add('converse-push', {
                 enablePush(Strophe.getDomainFromJid(model.get('jid')));
             }
         }
-        if (_converse.enable_muc_push) {
+        if (_converse.api.settings.get('enable_muc_push')) {
             _converse.api.listen.on('chatBoxesInitialized',  () => _converse.chatboxes.on('add', onChatBoxAdded));
         }
     }

+ 10 - 10
src/converse-register.js

@@ -95,7 +95,7 @@ converse.plugins.add('converse-register', {
             },
 
             renderRegistrationPanel () {
-                if (_converse.allow_registration) {
+                if (_converse.api.settings.get('allow_registration')) {
                     this.registerpanel = new _converse.RegisterPanel({
                         'model': this.model
                     });
@@ -145,15 +145,15 @@ converse.plugins.add('converse-register', {
                 this.model.set('registration_form_rendered', false);
                 this.el.innerHTML = tpl_register_panel({
                     '__': __,
-                    'default_domain': _converse.registration_domain,
+                    'default_domain': _converse.api.settings.get('registration_domain'),
                     'label_register': __('Fetch registration form'),
                     'help_providers': __('Tip: A list of public XMPP providers is available'),
                     'help_providers_link': __('here'),
-                    'href_providers': _converse.providers_link,
-                    'domain_placeholder': _converse.domain_placeholder
+                    'href_providers': _converse.api.settings.get('providers_link'),
+                    'domain_placeholder': _converse.api.settings.get('domain_placeholder')
                 });
-                if (_converse.registration_domain) {
-                    this.fetchRegistrationForm(_converse.registration_domain);
+                if (_converse.api.settings.get('registration_domain')) {
+                    this.fetchRegistrationForm(_converse.api.settings.get('registration_domain'));
                 }
                 return this;
             },
@@ -324,7 +324,7 @@ converse.plugins.add('converse-register', {
                     'beforeend',
                     tpl_registration_request({
                         '__': _converse.__,
-                        'cancel': _converse.registration_domain,
+                        'cancel': _converse.api.settings.get('registration_domain'),
                     })
                 );
             },
@@ -451,7 +451,7 @@ converse.plugins.add('converse-register', {
                     'domain': this.domain,
                     'title': this.title,
                     'instructions': this.instructions,
-                    'registration_domain': _converse.registration_domain
+                    'registration_domain': _converse.api.settings.get('registration_domain')
                 });
 
                 const buttons = form.querySelector('fieldset.buttons');
@@ -522,9 +522,9 @@ converse.plugins.add('converse-register', {
                 _converse.connection._proto._abortAllRequests();
                 _converse.connection.reset();
                 if (this.model.get('registration_form_rendered')) {
-                    if (_converse.registration_domain && this.model.get('registration_form_rendered')) {
+                    if (_converse.api.settings.get('registration_domain') && this.model.get('registration_form_rendered')) {
                         this.fetchRegistrationForm(
-                            _converse.registration_domain
+                            _converse.api.settings.get('registration_domain')
                         );
                     }
                 } else {

+ 15 - 16
src/converse-rosterview.js

@@ -42,7 +42,6 @@ converse.plugins.add('converse-rosterview', {
             'allow_contact_removal': true,
             'hide_offline_users': false,
             'roster_groups': true,
-            'show_toolbar': true,
             'xhr_user_search_url': null,
         });
         _converse.api.promises.add('rosterViewInitialized');
@@ -69,7 +68,7 @@ converse.plugins.add('converse-rosterview', {
             },
 
             toHTML () {
-                const label_nickname = _converse.xhr_user_search_url ? __('Contact name') : __('Optional nickname');
+                const label_nickname = _converse.api.settings.get('xhr_user_search_url') ? __('Contact name') : __('Optional nickname');
                 return tpl_add_contact_modal(Object.assign(this.model.toJSON(), {
                     '_converse': _converse,
                     'label_nickname': label_nickname,
@@ -77,7 +76,7 @@ converse.plugins.add('converse-rosterview', {
             },
 
             afterRender () {
-                if (_converse.xhr_user_search_url && isString(_converse.xhr_user_search_url)) {
+                if (_converse.api.settings.get('xhr_user_search_url') && isString(_converse.api.settings.get('xhr_user_search_url'))) {
                     this.initXHRAutoComplete();
                 } else {
                     this.initJIDAutoComplete();
@@ -87,7 +86,7 @@ converse.plugins.add('converse-rosterview', {
             },
 
             initJIDAutoComplete () {
-                if (!_converse.autocomplete_add_contact) {
+                if (!_converse.api.settings.get('autocomplete_add_contact')) {
                     return;
                 }
                 const el = this.el.querySelector('.suggestion-box__jid').parentElement;
@@ -99,7 +98,7 @@ converse.plugins.add('converse-rosterview', {
             },
 
             initXHRAutoComplete () {
-                if (!_converse.autocomplete_add_contact) {
+                if (!_converse.api.settings.get('autocomplete_add_contact')) {
                     return this.initXHRFetch();
                 }
                 const el = this.el.querySelector('.suggestion-box__name').parentElement;
@@ -120,7 +119,7 @@ converse.plugins.add('converse-rosterview', {
                 };
                 const input_el = this.el.querySelector('input[name="name"]');
                 input_el.addEventListener('input', debounce(() => {
-                    xhr.open("GET", `${_converse.xhr_user_search_url}q=${encodeURIComponent(input_el.value)}`, true);
+                    xhr.open("GET", `${_converse.api.settings.get('xhr_user_search_url')}q=${encodeURIComponent(input_el.value)}`, true);
                     xhr.send()
                 } , 300));
                 this.name_auto_complete.on('suggestion-box-selectcomplete', ev => {
@@ -177,9 +176,9 @@ converse.plugins.add('converse-rosterview', {
                 const data = new FormData(ev.target),
                       jid = (data.get('jid') || '').trim();
 
-                if (!jid && _converse.xhr_user_search_url && isString(_converse.xhr_user_search_url)) {
+                if (!jid && _converse.api.settings.get('xhr_user_search_url') && isString(_converse.api.settings.get('xhr_user_search_url'))) {
                     const input_el = this.el.querySelector('input[name="name"]');
-                    this.xhr.open("GET", `${_converse.xhr_user_search_url}q=${encodeURIComponent(input_el.value)}`, true);
+                    this.xhr.open("GET", `${_converse.api.settings.get('xhr_user_search_url')}q=${encodeURIComponent(input_el.value)}`, true);
                     this.xhr.send()
                     return;
                 }
@@ -358,7 +357,7 @@ converse.plugins.add('converse-rosterview', {
                         Object.assign(this.model.toJSON(), {
                             display_name,
                             'desc_remove': __('Click to remove %1$s as a contact', display_name),
-                            'allow_chat_pending_contacts': _converse.allow_chat_pending_contacts
+                            'allow_chat_pending_contacts': _converse.api.settings.get('allow_chat_pending_contacts')
                         })
                     );
                 } else if (requesting === true) {
@@ -369,7 +368,7 @@ converse.plugins.add('converse-rosterview', {
                             display_name,
                             'desc_accept': __("Click to accept the contact request from %1$s", display_name),
                             'desc_decline': __("Click to decline the contact request from %1$s", display_name),
-                            'allow_chat_pending_contacts': _converse.allow_chat_pending_contacts
+                            'allow_chat_pending_contacts': _converse.api.settings.get('allow_chat_pending_contacts')
                         })
                     );
                 } else if (subscription === 'both' || subscription === 'to' || _converse.rosterview.isSelf(jid)) {
@@ -420,7 +419,7 @@ converse.plugins.add('converse-rosterview', {
                         'desc_status': STATUSES[show],
                         'desc_chat': __('Click to chat with %1$s (JID: %2$s)', display_name, item.get('jid')),
                         'desc_remove': __('Click to remove %1$s as a contact', display_name),
-                        'allow_contact_removal': _converse.allow_contact_removal,
+                        'allow_contact_removal': _converse.api.settings.get('allow_contact_removal'),
                         'num_unread': item.get('num_unread') || 0,
                         classes: ''
                     })
@@ -439,7 +438,7 @@ converse.plugins.add('converse-rosterview', {
              */
             mayBeShown () {
                 const chatStatus = this.model.presence.get('show');
-                if (_converse.hide_offline_users && chatStatus === 'offline') {
+                if (_converse.api.settings.get('hide_offline_users') && chatStatus === 'offline') {
                     // If pending or requesting, show
                     if ((this.model.get('ask') === 'subscribe') ||
                             (this.model.get('subscription') === 'from') ||
@@ -459,7 +458,7 @@ converse.plugins.add('converse-rosterview', {
 
             async removeContact (ev) {
                 if (ev && ev.preventDefault) { ev.preventDefault(); }
-                if (!_converse.allow_contact_removal) { return; }
+                if (!_converse.api.settings.get('allow_contact_removal')) { return; }
                 if (!confirm(__("Are you sure you want to remove this contact?"))) { return; }
 
                 try {
@@ -893,7 +892,7 @@ converse.plugins.add('converse-rosterview', {
 
             addExistingContact (contact, options) {
                 let groups;
-                if (_converse.roster_groups) {
+                if (_converse.api.settings.get('roster_groups')) {
                     groups = contact.get('groups');
                     groups = (groups.length === 0) ? [_converse.HEADER_UNGROUPED] : groups;
                 } else {
@@ -946,7 +945,7 @@ converse.plugins.add('converse-rosterview', {
 
         _converse.api.listen.on('controlBoxInitialized', (view) => {
             function insertRoster () {
-                if (!view.model.get('connected') || _converse.authentication === _converse.ANONYMOUS) {
+                if (!view.model.get('connected') || _converse.api.settings.get("authentication") === _converse.ANONYMOUS) {
                     return;
                 }
                 /* Place the rosterview inside the "Contacts" panel. */
@@ -963,7 +962,7 @@ converse.plugins.add('converse-rosterview', {
             /* Create an instance of RosterView once the RosterGroups
              * collection has been created (in @converse/headless/converse-core.js)
              */
-            if (_converse.authentication === _converse.ANONYMOUS) {
+            if (_converse.api.settings.get("authentication") === _converse.ANONYMOUS) {
                 return;
             }
             _converse.rosterview = new _converse.RosterView({

+ 1 - 1
src/converse-singleton.js

@@ -10,7 +10,7 @@ import converse from "@converse/headless/converse-core";
 converse.plugins.add('converse-singleton', {
 
     enabled (_converse) {
-        return _converse.singleton;
+        return _converse.api.settings.get("singleton");
     },
 
     initialize () {

+ 5 - 4
src/headless/converse-bookmarks.js

@@ -69,7 +69,8 @@ converse.plugins.add('converse-bookmarks', {
         // configuration settings.
         _converse.api.settings.update({
             allow_bookmarks: true,
-            allow_public_bookmarks: false
+            allow_public_bookmarks: false,
+            muc_respect_autojoin: true
         });
 
         _converse.api.promises.add('bookmarksInitialized');
@@ -81,7 +82,7 @@ converse.plugins.add('converse-bookmarks', {
           * @method _converse#getNicknameFromBookmark
           */
         _converse.getNicknameFromBookmark = function (jid) {
-            if (!_converse.bookmarks || !_converse.allow_bookmarks) {
+            if (!_converse.bookmarks || !_converse.api.settings.get('allow_bookmarks')) {
                 return null;
             }
             const bookmark = _converse.bookmarks.findWhere({'jid': jid});
@@ -115,7 +116,7 @@ converse.plugins.add('converse-bookmarks', {
             },
 
             async openBookmarkedRoom (bookmark) {
-                if ( _converse.muc_respect_autojoin && bookmark.get('autojoin')) {
+                if ( _converse.api.settings.get('muc_respect_autojoin') && bookmark.get('autojoin')) {
                     const groupchat = await _converse.api.rooms.create(bookmark.get('jid'), bookmark.get('nick'));
                     groupchat.maybeShow();
                 }
@@ -271,7 +272,7 @@ converse.plugins.add('converse-bookmarks', {
         }
 
         const initBookmarks = async function () {
-            if (!_converse.allow_bookmarks) {
+            if (!_converse.api.settings.get('allow_bookmarks')) {
                 return;
             }
             if (await _converse.checkBookmarksSupport()) {

+ 2 - 2
src/headless/converse-bosh.js

@@ -51,11 +51,11 @@ converse.plugins.add('converse-bosh', {
 
 
         _converse.startNewPreboundBOSHSession = function () {
-            if (!_converse.prebind_url) {
+            if (!_converse.api.settings.get('prebind_url')) {
                 throw new Error("startNewPreboundBOSHSession: If you use prebind then you MUST supply a prebind_url");
             }
             const xhr = new XMLHttpRequest();
-            xhr.open('GET', _converse.prebind_url, true);
+            xhr.open('GET', _converse.api.settings.get('prebind_url'), true);
             xhr.setRequestHeader('Accept', 'application/json, text/javascript');
             xhr.onload = async function () {
                 if (xhr.status >= 200 && xhr.status < 400) {

+ 16 - 15
src/headless/converse-chat.js

@@ -42,10 +42,11 @@ converse.plugins.add('converse-chat', {
         // Refer to docs/source/configuration.rst for explanations of these
         // configuration settings.
         _converse.api.settings.update({
+            'allow_message_corrections': 'all',
+            'allow_message_retraction': 'all',
             'auto_join_private_chats': [],
             'clear_messages_on_reconnection': false,
             'filter_by_resource': false,
-            'allow_message_corrections': 'all',
             'send_chat_state_notifications': true
         });
 
@@ -150,7 +151,7 @@ converse.plugins.add('converse-chat', {
              */
             mayBeRetracted () {
                 const is_own_message = this.get('sender') === 'me';
-                return is_own_message && ['all', 'own'].includes(_converse.allow_message_retraction);
+                return is_own_message && ['all', 'own'].includes(_converse.api.settings.get('allow_message_retraction'));
             },
 
             safeDestroy () {
@@ -305,7 +306,7 @@ converse.plugins.add('converse-chat', {
                 return {
                     'bookmarked': false,
                     'chat_state': undefined,
-                    'hidden': ['mobile', 'fullscreen'].includes(_converse.view_mode),
+                    'hidden': ['mobile', 'fullscreen'].includes(_converse.api.settings.get("view_mode")),
                     'message_type': 'chat',
                     'nickname': undefined,
                     'num_unread': 0,
@@ -452,7 +453,7 @@ converse.plugins.add('converse-chat', {
                 } catch (e) {
                     log.error(e);
                 } finally {
-                    if (_converse.clear_messages_on_reconnection) {
+                    if (_converse.api.settings.get('clear_messages_on_reconnection')) {
                         await this.clearMessages();
                     }
                 }
@@ -469,7 +470,7 @@ converse.plugins.add('converse-chat', {
             },
 
             async onReconnection () {
-                if (_converse.clear_messages_on_reconnection) {
+                if (_converse.api.settings.get('clear_messages_on_reconnection')) {
                     await this.clearMessages();
                 }
                 this.announceReconnection();
@@ -480,8 +481,8 @@ converse.plugins.add('converse-chat', {
                     return 'Ignored ChatBox without JID';
                 }
                 const room_jids = _converse.auto_join_rooms.map(s => isObject(s) ? s.jid : s);
-                const auto_join = _converse.auto_join_private_chats.concat(room_jids);
-                if (_converse.singleton && !auto_join.includes(attrs.jid) && !_converse.auto_join_on_invite) {
+                const auto_join = _converse.api.settings.get('auto_join_private_chats').concat(room_jids);
+                if (_converse.api.settings.get("singleton") && !auto_join.includes(attrs.jid) && !_converse.auto_join_on_invite) {
                     const msg = `${attrs.jid} is not allowed because singleton is true and it's not being auto_joined`;
                     log.warn(msg);
                     return msg;
@@ -935,7 +936,7 @@ converse.plugins.add('converse-chat', {
 
             /**
              * Responsible for setting the editable attribute of messages.
-             * If _converse.allow_message_corrections is "last", then only the last
+             * If _converse.api.settings.get('allow_message_corrections') is "last", then only the last
              * message sent from me will be editable. If set to "all" all messages
              * will be editable. Otherwise no messages will be editable.
              * @method _converse.ChatBox#setEditable
@@ -950,9 +951,9 @@ converse.plugins.add('converse-chat', {
                 if (u.isEmptyMessage(attrs) || attrs.sender !== 'me') {
                     return;
                 }
-                if (_converse.allow_message_corrections === 'all') {
+                if (_converse.api.settings.get('allow_message_corrections') === 'all') {
                     attrs.editable = !(attrs.file || attrs.retracted || 'oob_url' in attrs);
-                } else if ((_converse.allow_message_corrections === 'last') && (send_time > this.get('time_sent'))) {
+                } else if ((_converse.api.settings.get('allow_message_corrections') === 'last') && (send_time > this.get('time_sent'))) {
                     this.set({'time_sent': send_time});
                     const msg = this.messages.findWhere({'editable': true});
                     if (msg) {
@@ -1014,8 +1015,8 @@ converse.plugins.add('converse-chat', {
              * @method _converse.ChatBox#sendChatState
              */
             sendChatState () {
-                if (_converse.send_chat_state_notifications && this.get('chat_state')) {
-                    const allowed = _converse.send_chat_state_notifications;
+                if (_converse.api.settings.get('send_chat_state_notifications') && this.get('chat_state')) {
+                    const allowed = _converse.api.settings.get('send_chat_state_notifications');
                     if (Array.isArray(allowed) && !allowed.includes(this.get('chat_state'))) {
                         return;
                     }
@@ -1183,7 +1184,7 @@ converse.plugins.add('converse-chat', {
             let to_jid = stanza.getAttribute('to');
             const to_resource = Strophe.getResourceFromJid(to_jid);
 
-            if (_converse.filter_by_resource && (to_resource && to_resource !== _converse.resource)) {
+            if (_converse.api.settings.get('filter_by_resource') && (to_resource && to_resource !== _converse.resource)) {
                 return log.info(`handleMessageStanza: Ignoring incoming message intended for a different resource: ${to_jid}`);
             } else if (utils.isHeadlineMessage(_converse, stanza)) {
                 // XXX: Prosody sends headline messages with the
@@ -1229,7 +1230,7 @@ converse.plugins.add('converse-chat', {
             }
             const contact_jid = is_me ? Strophe.getBareJidFromJid(to_jid) : from_bare_jid;
             const contact = await _converse.api.contacts.get(contact_jid);
-            if (contact === undefined && !_converse.allow_non_roster_messaging) {
+            if (contact === undefined && !_converse.api.settings.get("allow_non_roster_messaging")) {
                 log.error(`Blocking messaging with a JID not in our roster because allow_non_roster_messaging is false.`);
                 return log.error(stanza);
             }
@@ -1286,7 +1287,7 @@ converse.plugins.add('converse-chat', {
         function autoJoinChats () {
             // Automatically join private chats, based on the
             // "auto_join_private_chats" configuration setting.
-            _converse.auto_join_private_chats.forEach(jid => {
+            _converse.api.settings.get('auto_join_private_chats').forEach(jid => {
                 if (_converse.chatboxes.where({'jid': jid}).length) {
                     return;
                 }

+ 71 - 69
src/headless/converse-core.js

@@ -199,19 +199,15 @@ _converse.default_connection_options = {'explicitResourceBinding': true};
 
 // Default configuration values
 // ----------------------------
-_converse.default_settings = {
+const DEFAULT_SETTINGS = {
     allow_non_roster_messaging: false,
     assets_path: '/dist',
     authentication: 'login', // Available values are "login", "prebind", "anonymous" and "external".
-    auto_away: 0, // Seconds after which user status is set to 'away'
     auto_login: false, // Currently only used in connection with anonymous login
     auto_reconnect: true,
-    auto_xa: 0, // Seconds after which user status is set to 'xa'
     blacklisted_plugins: [],
     connection_options: {},
     credentials_url: null, // URL from where login credentials can be fetched
-    csi_waiting_time: 0, // Support for XEP-0352. Seconds before client is considered idle and CSI is sent out.
-    default_state: 'online',
     discover_connection_methods: false,
     geouri_regex: /https\:\/\/www.openstreetmap.org\/.*#map=[0-9]+\/([\-0-9.]+)\/([\-0-9.]+)\S*/g,
     geouri_replacement: 'https://www.openstreetmap.org/?mlat=$1&mlon=$2#map=18/$1/$2',
@@ -228,7 +224,6 @@ _converse.default_settings = {
     nickname: undefined,
     password: undefined,
     persistent_store: 'localStorage',
-    priority: 0,
     rid: undefined,
     root: window.document,
     sid: undefined,
@@ -317,7 +312,7 @@ _converse.isUniView = function () {
      * UniView means that only one chat is visible, even though there might be multiple ongoing chats.
      * MultiView means that multiple chats may be visible simultaneously.
      */
-    return ['mobile', 'fullscreen', 'embedded'].includes(_converse.view_mode);
+    return ['mobile', 'fullscreen', 'embedded'].includes(_converse.api.settings.get("view_mode"));
 };
 
 
@@ -341,10 +336,10 @@ function initPersistentStorage () {
         'name': _converse.isTestEnv() ? 'converse-test-persistent' : 'converse-persistent',
         'storeName': _converse.bare_jid
     }
-    if (_converse.persistent_store === 'localStorage') {
+    if (_converse.api.settings.get("persistent_store") === 'localStorage') {
         config['description'] = 'localStorage instance';
         config['driver'] = [Storage.localForage.LOCALSTORAGE];
-    } else if (_converse.persistent_store === 'IndexedDB') {
+    } else if (_converse.api.settings.get("persistent_store") === 'IndexedDB') {
         config['description'] = 'indexedDB instance';
         config['driver'] = [Storage.localForage.INDEXEDDB];
     }
@@ -366,21 +361,21 @@ function initPlugins () {
     // If initialize is called for the first time, then this array is empty
     // in any case.
     _converse.pluggable.initialized_plugins = [];
-    const whitelist = CORE_PLUGINS.concat(_converse.whitelisted_plugins);
+    const whitelist = CORE_PLUGINS.concat(_converse.api.settings.get("whitelisted_plugins"));
 
-    if (_converse.singleton) {
+    if (_converse.api.settings.get("singleton")) {
         [
             'converse-bookmarks',
             'converse-controlbox',
             'converse-headline',
             'converse-register'
-        ].forEach(name => _converse.blacklisted_plugins.push(name));
+        ].forEach(name => _converse.api.settings.get("blacklisted_plugins").push(name));
     }
 
     _converse.pluggable.initializePlugins(
         { '_converse': _converse },
         whitelist,
-        _converse.blacklisted_plugins
+        _converse.api.settings.get("blacklisted_plugins")
     );
 
     /**
@@ -410,8 +405,8 @@ function initClientConfig () {
     const id = 'converse.client-config';
     _converse.config = new Model({
         'id': id,
-        'trusted': _converse.trusted && true || false,
-        'storage': _converse.trusted ? 'persistent' : 'session'
+        'trusted': _converse.api.settings.get("trusted") && true || false,
+        'storage': _converse.api.settings.get("trusted") ? 'persistent' : 'session'
     });
     _converse.config.browserStorage = _converse.createStore(id, "session");
     _converse.config.fetch();
@@ -442,34 +437,34 @@ async function tearDown () {
 
 
 async function attemptNonPreboundSession (credentials, automatic) {
-    if (_converse.authentication === _converse.LOGIN) {
+    if (_converse.api.settings.get("authentication") === _converse.LOGIN) {
         // XXX: If EITHER ``keepalive`` or ``auto_login`` is ``true`` and
         // ``authentication`` is set to ``login``, then Converse will try to log the user in,
         // since we don't have a way to distinguish between wether we're
         // restoring a previous session (``keepalive``) or whether we're
         // automatically setting up a new session (``auto_login``).
-        // So we can't do the check (!automatic || _converse.auto_login) here.
+        // So we can't do the check (!automatic || _converse.api.settings.get("auto_login")) here.
         if (credentials) {
             connect(credentials);
-        } else if (_converse.credentials_url) {
+        } else if (_converse.api.settings.get("credentials_url")) {
             // We give credentials_url preference, because
             // _converse.connection.pass might be an expired token.
             connect(await getLoginCredentials());
-        } else if (_converse.jid && (_converse.password || _converse.connection.pass)) {
+        } else if (_converse.jid && (_converse.api.settings.get("password") || _converse.connection.pass)) {
             connect();
         } else if (!_converse.isTestEnv() && 'credentials' in navigator) {
             connect(await getLoginCredentialsFromBrowser());
         } else {
             log.warn("attemptNonPreboundSession: Could not find any credentials to log in with");
         }
-    } else if ([_converse.ANONYMOUS, _converse.EXTERNAL].includes(_converse.authentication) && (!automatic || _converse.auto_login)) {
+    } else if ([_converse.ANONYMOUS, _converse.EXTERNAL].includes(_converse.api.settings.get("authentication")) && (!automatic || _converse.api.settings.get("auto_login"))) {
         connect();
     }
 }
 
 
 function connect (credentials) {
-    if ([_converse.ANONYMOUS, _converse.EXTERNAL].includes(_converse.authentication)) {
+    if ([_converse.ANONYMOUS, _converse.EXTERNAL].includes(_converse.api.settings.get("authentication"))) {
         if (!_converse.jid) {
             throw new Error("Config Error: when using anonymous login " +
                 "you need to provide the server's domain via the 'jid' option. " +
@@ -485,10 +480,10 @@ function connect (credentials) {
             _converse.onConnectStatusChanged,
             BOSH_WAIT
         );
-    } else if (_converse.authentication === _converse.LOGIN) {
-        const password = credentials ? credentials.password : (_converse.connection?.pass || _converse.password);
+    } else if (_converse.api.settings.get("authentication") === _converse.LOGIN) {
+        const password = credentials ? credentials.password : (_converse.connection?.pass || _converse.api.settings.get("password"));
         if (!password) {
-            if (_converse.auto_login) {
+            if (_converse.api.settings.get("auto_login")) {
                 throw new Error("autoLogin: If you use auto_login and "+
                     "authentication='login' then you also need to provide a password.");
             }
@@ -554,8 +549,8 @@ async function onDomainDiscovered (response) {
     const bosh_methods = bosh_links.map(el => el.getAttribute('href'));
     const ws_methods = ws_links.map(el => el.getAttribute('href'));
     // TODO: support multiple endpoints
-    _converse.websocket_url = ws_methods.pop();
-    _converse.bosh_service_url = bosh_methods.pop();
+    _converse.api.settings.set("websocket_url", ws_methods.pop());
+    _converse.api.settings.set('bosh_service_url', bosh_methods.pop());
     if (bosh_methods.length === 0 && ws_methods.length === 0) {
         log.warn(
             "onDomainDiscovered: neither BOSH nor WebSocket connection methods have been specified with XEP-0156."
@@ -591,30 +586,30 @@ async function discoverConnectionMethods (domain) {
 
 
 _converse.initConnection = async function (domain) {
-    if (_converse.discover_connection_methods) {
+    if (_converse.api.settings.get("discover_connection_methods")) {
         await discoverConnectionMethods(domain);
     }
-    if (! _converse.bosh_service_url) {
-        if (_converse.authentication === _converse.PREBIND) {
+    if (! _converse.api.settings.get('bosh_service_url')) {
+        if (_converse.api.settings.get("authentication") === _converse.PREBIND) {
             throw new Error("authentication is set to 'prebind' but we don't have a BOSH connection");
         }
-        if (! _converse.websocket_url) {
+        if (! _converse.api.settings.get("websocket_url")) {
             throw new Error("initConnection: you must supply a value for either the bosh_service_url or websocket_url or both.");
         }
     }
 
-    if (('WebSocket' in window || 'MozWebSocket' in window) && _converse.websocket_url) {
+    if (('WebSocket' in window || 'MozWebSocket' in window) && _converse.api.settings.get("websocket_url")) {
         _converse.connection = new Strophe.Connection(
-            _converse.websocket_url,
-            Object.assign(_converse.default_connection_options, _converse.connection_options)
+            _converse.api.settings.get("websocket_url"),
+            Object.assign(_converse.default_connection_options, _converse.api.settings.get("connection_options"))
         );
-    } else if (_converse.bosh_service_url) {
+    } else if (_converse.api.settings.get('bosh_service_url')) {
         _converse.connection = new Strophe.Connection(
-            _converse.bosh_service_url,
+            _converse.api.settings.get('bosh_service_url'),
             Object.assign(
                 _converse.default_connection_options,
-                _converse.connection_options,
-                {'keepalive': _converse.keepalive}
+                _converse.api.settings.get("connection_options"),
+                {'keepalive': _converse.api.settings.get("keepalive")}
             )
         );
     } else {
@@ -660,7 +655,7 @@ async function initSession (jid) {
 
 function saveJIDtoSession (jid) {
     jid = _converse.session.get('jid') || jid;
-    if (_converse.authentication !== _converse.ANONYMOUS && !Strophe.getResourceFromJid(jid)) {
+    if (_converse.api.settings.get("authentication") !== _converse.ANONYMOUS && !Strophe.getResourceFromJid(jid)) {
         jid = jid.toLowerCase() + _converse.generateResource();
     }
     _converse.jid = jid;
@@ -712,7 +707,7 @@ function enableCarbons () {
     /* Ask the XMPP server to enable Message Carbons
      * See XEP-0280 https://xmpp.org/extensions/xep-0280.html#enabling
      */
-    if (!_converse.message_carbons || !_converse.session || _converse.session.get('carbons_enabled')) {
+    if (!_converse.api.settings.get("message_carbons") || !_converse.session || _converse.session.get('carbons_enabled')) {
         return;
     }
     const carbons_iq = new Strophe.Builder('iq', {
@@ -801,13 +796,13 @@ async function finishInitialization () {
     if (!History.started) {
         _converse.router.history.start();
     }
-    if (_converse.idle_presence_timeout > 0) {
+    if (_converse.api.settings.get("idle_presence_timeout") > 0) {
         _converse.api.listen.on('addClientFeatures', () => {
             _converse.api.disco.own.features.add(Strophe.NS.IDLE);
         });
     }
-    if (_converse.auto_login ||
-            _converse.keepalive && invoke(_converse.pluggable.plugins['converse-bosh'], 'enabled')) {
+    if (_converse.api.settings.get("auto_login") ||
+            _converse.api.settings.get("keepalive") && invoke(_converse.pluggable.plugins['converse-bosh'], 'enabled')) {
         await _converse.api.user.login(null, null, true);
     }
 }
@@ -840,7 +835,7 @@ function fetchLoginCredentials (wait=0) {
     return new Promise(
         debounce((resolve, reject) => {
             const xhr = new XMLHttpRequest();
-            xhr.open('GET', _converse.credentials_url, true);
+            xhr.open('GET', _converse.api.settings.get("credentials_url"), true);
             xhr.setRequestHeader('Accept', 'application/json, text/javascript');
             xhr.onload = () => {
                 if (xhr.status >= 200 && xhr.status < 400) {
@@ -978,17 +973,21 @@ _converse.initialize = async function (settings, callback) {
         _converse.unloadevent = 'unload';
     }
 
-    assignIn(this, this.default_settings);
-    // Allow only whitelisted configuration attributes to be overwritten
-    assignIn(this, pick(settings, Object.keys(this.default_settings)));
     this.settings = {};
-    assignIn(this.settings, pick(settings, Object.keys(this.default_settings)));
+    assignIn(this.settings, DEFAULT_SETTINGS);
+    // Allow only whitelisted configuration attributes to be overwritten
+    assignIn(this.settings, pick(settings, Object.keys(DEFAULT_SETTINGS)));
+    assignIn(this, this.settings);
+    this.user_settings = settings; // XXX: See whether this can be removed
 
-    log.setLogLevel(_converse.loglevel);
+    // Needed by pluggable.js
+    this.strict_plugin_dependencies = settings.strict_plugin_dependencies;
+
+    log.setLogLevel(_converse.api.settings.get("loglevel"));
     _converse.log = log.log;
 
-    if (this.authentication === _converse.ANONYMOUS) {
-        if (this.auto_login && !this.jid) {
+    if (_converse.api.settings.get("authentication") === _converse.ANONYMOUS) {
+        if (_converse.api.settings.get("auto_login") && !this.jid) {
             throw new Error("Config Error: you need to provide the server's " +
                   "domain via the 'jid' option when using anonymous " +
                   "authentication with auto_login.");
@@ -1005,7 +1004,7 @@ _converse.initialize = async function (settings, callback) {
         _converse.locale = 'en';
     } else {
         try {
-            _converse.locale = i18n.getLocale(settings.i18n, _converse.locales);
+            _converse.locale = i18n.getLocale(settings.i18n, _converse.api.settings.get("locales"));
             await i18n.fetchTranslations(_converse);
         } catch (e) {
             log.fatal(e.message);
@@ -1024,7 +1023,6 @@ _converse.initialize = async function (settings, callback) {
      * https://github.com/jcbrand/converse.js/issues/521
      */
     this.send_initial_presence = true;
-    this.user_settings = settings; // Save the user settings so that they can be used by plugins
 
     // Module-level functions
     // ----------------------
@@ -1049,7 +1047,8 @@ _converse.initialize = async function (settings, callback) {
     this.onDisconnected = function () {
         const reason = _converse.disconnection_reason;
         if (_converse.disconnection_cause === Strophe.Status.AUTHFAIL) {
-            if (_converse.auto_reconnect && (_converse.credentials_url || _converse.authentication === _converse.ANONYMOUS)) {
+            if (_converse.api.settings.get("auto_reconnect") &&
+                (_converse.api.settings.get("credentials_url") || _converse.api.settings.get("authentication") === _converse.ANONYMOUS)) {
                 /**
                  * If `credentials_url` is set, we reconnect, because we might
                  * be receiving expirable tokens from the credentials_url.
@@ -1066,7 +1065,7 @@ _converse.initialize = async function (settings, callback) {
                 (reason !== undefined && reason === Strophe?.ErrorCondition.NO_AUTH_MECH) ||
                 reason === "host-unknown" ||
                 reason === "remote-connection-failed" ||
-                !_converse.auto_reconnect) {
+                !_converse.api.settings.get("auto_reconnect")) {
             return finishDisconnection();
         }
         _converse.api.connection.reconnect();
@@ -1233,7 +1232,7 @@ _converse.api = {
         async reconnect () {
             const conn_status = _converse.connfeedback.get('connection_status');
 
-            if (_converse.authentication === _converse.ANONYMOUS) {
+            if (_converse.api.settings.get("authentication") === _converse.ANONYMOUS) {
                 await tearDown();
                 await clearSession();
             }
@@ -1244,30 +1243,30 @@ _converse.api = {
                 //
                 // We also call `_proto._doDisconnect` so that connection event handlers
                 // for the old transport are removed.
-                if (_converse.api.connection.isType('websocket') && _converse.bosh_service_url) {
+                if (_converse.api.connection.isType('websocket') && _converse.api.settings.get('bosh_service_url')) {
                     await _converse.setUserJID(_converse.bare_jid);
                     _converse.connection._proto._doDisconnect();
                     _converse.connection._proto = new Strophe.Bosh(_converse.connection);
-                    _converse.connection.service = _converse.bosh_service_url;
-                } else if (_converse.api.connection.isType('bosh') && _converse.websocket_url) {
-                    if (_converse.authentication === _converse.ANONYMOUS) {
+                    _converse.connection.service = _converse.api.settings.get('bosh_service_url');
+                } else if (_converse.api.connection.isType('bosh') && _converse.api.settings.get("websocket_url")) {
+                    if (_converse.api.settings.get("authentication") === _converse.ANONYMOUS) {
                         // When reconnecting anonymously, we need to connect with only
                         // the domain, not the full JID that we had in our previous
                         // (now failed) session.
-                        await _converse.setUserJID(_converse.settings.jid);
+                        await _converse.setUserJID(_converse.api.settings.get("jid"));
                     } else {
                         await _converse.setUserJID(_converse.bare_jid);
                     }
                     _converse.connection._proto._doDisconnect();
                     _converse.connection._proto = new Strophe.Websocket(_converse.connection);
-                    _converse.connection.service = _converse.websocket_url;
+                    _converse.connection.service = _converse.api.settings.get("websocket_url");
                 }
             }
-            if (conn_status === Strophe.Status.AUTHFAIL && _converse.authentication === _converse.ANONYMOUS) {
+            if (conn_status === Strophe.Status.AUTHFAIL && _converse.api.settings.get("authentication") === _converse.ANONYMOUS) {
                 // When reconnecting anonymously, we need to connect with only
                 // the domain, not the full JID that we had in our previous
                 // (now failed) session.
-                await _converse.setUserJID(_converse.settings.jid);
+                await _converse.setUserJID(_converse.api.settings.get("jid"));
             }
 
             if (_converse.connection.authenticated) {
@@ -1369,12 +1368,12 @@ _converse.api = {
             if (bosh_plugin && bosh_plugin.enabled()) {
                 if (await _converse.restoreBOSHSession()) {
                     return;
-                } else if (_converse.authentication === _converse.PREBIND && (!automatic || _converse.auto_login)) {
+                } else if (_converse.api.settings.get("authentication") === _converse.PREBIND && (!automatic || _converse.api.settings.get("auto_login"))) {
                     return _converse.startNewPreboundBOSHSession();
                 }
             }
 
-            password = password || _converse.password;
+            password = password || _converse.api.settings.get("password");
             const credentials = (jid && password) ? { jid, password } : null;
             attemptNonPreboundSession(credentials, automatic);
         },
@@ -1436,17 +1435,18 @@ _converse.api = {
          * });
          */
         update (settings) {
-            u.merge(_converse.default_settings, settings);
+            u.merge(DEFAULT_SETTINGS, settings);
             u.merge(_converse, settings);
             u.applyUserSettings(_converse, settings, _converse.user_settings);
         },
+
         /**
          * @method _converse.api.settings.get
          * @returns {*} Value of the particular configuration setting.
          * @example _converse.api.settings.get("play_sounds");
          */
         get (key) {
-            if (Object.keys(_converse.default_settings).includes(key)) {
+            if (Object.keys(DEFAULT_SETTINGS).includes(key)) {
                 return _converse[key];
             }
         },
@@ -1471,10 +1471,12 @@ _converse.api = {
         set (key, val) {
             const o = {};
             if (isObject(key)) {
-                assignIn(_converse, pick(key, Object.keys(_converse.default_settings)));
+                assignIn(_converse, pick(key, Object.keys(DEFAULT_SETTINGS)));
+                assignIn(_converse.settings, pick(key, Object.keys(DEFAULT_SETTINGS)));
             } else if (isString('string')) {
                 o[key] = val;
-                assignIn(_converse, pick(o, Object.keys(_converse.default_settings)));
+                assignIn(_converse, pick(o, Object.keys(DEFAULT_SETTINGS)));
+                assignIn(_converse.settings, pick(o, Object.keys(DEFAULT_SETTINGS)));
             }
         }
     },

+ 1 - 1
src/headless/converse-disco.js

@@ -251,7 +251,7 @@ converse.plugins.add('converse-disco', {
             _converse.api.disco.own.features.add(Strophe.NS.CHATSTATES);
             _converse.api.disco.own.features.add(Strophe.NS.DISCO_INFO);
             _converse.api.disco.own.features.add(Strophe.NS.ROSTERX); // Limited support
-            if (_converse.message_carbons) {
+            if (_converse.api.settings.get("message_carbons")) {
                 _converse.api.disco.own.features.add(Strophe.NS.CARBONS);
             }
             /**

+ 2 - 2
src/headless/converse-emoji.js

@@ -99,7 +99,7 @@ converse.plugins.add('converse-emoji', {
 
         _converse.emojis = {};
         _converse.api.promises.add('emojisInitialized', false);
-        twemoji.default.base = _converse.emoji_image_path;
+        twemoji.default.base = _converse.api.settings.get('emoji_image_path');
 
 
         /**
@@ -162,7 +162,7 @@ converse.plugins.add('converse-emoji', {
                     }
                 };
                 const transform = u.shortnamesToEmojis;
-                return _converse.use_system_emojis ? transform : text => twemoji.default.parse(transform(text), how);
+                return _converse.api.settings.get('use_system_emojis') ? transform : text => twemoji.default.parse(transform(text), how);
             },
 
             /**

+ 2 - 2
src/headless/converse-headlines.js

@@ -58,7 +58,7 @@ converse.plugins.add('converse-headlines', {
             defaults () {
                 return {
                     'bookmarked': false,
-                    'hidden': ['mobile', 'fullscreen'].includes(_converse.view_mode),
+                    'hidden': ['mobile', 'fullscreen'].includes(_converse.api.settings.get("view_mode")),
                     'message_type': 'headline',
                     'num_unread': 0,
                     'time_opened': this.get('time_opened') || (new Date()).getTime(),
@@ -85,7 +85,7 @@ converse.plugins.add('converse-headlines', {
                 const from_jid = message.getAttribute('from');
                 if (from_jid.includes('@') &&
                         !_converse.roster.get(from_jid) &&
-                        !_converse.allow_non_roster_messaging) {
+                        !_converse.api.settings.get("allow_non_roster_messaging")) {
                     return;
                 }
                 if (message.querySelector('body') === null) {

+ 10 - 10
src/headless/converse-mam.js

@@ -92,7 +92,7 @@ converse.plugins.add('converse-mam', {
 
                 const query = Object.assign({
                         'groupchat': is_groupchat,
-                        'max': _converse.archived_messages_page_size,
+                        'max': _converse.api.settings.get('archived_messages_page_size'),
                         'with': this.get('jid'),
                     }, options);
 
@@ -113,9 +113,9 @@ converse.plugins.add('converse-mam', {
 
                 if (page && result.rsm) {
                     if (page === 'forwards') {
-                        options = result.rsm.next(_converse.archived_messages_page_size, options.before);
+                        options = result.rsm.next(_converse.api.settings.get('archived_messages_page_size'), options.before);
                     } else if (page === 'backwards') {
-                        options = result.rsm.previous(_converse.archived_messages_page_size, options.after);
+                        options = result.rsm.previous(_converse.api.settings.get('archived_messages_page_size'), options.after);
                     }
                     return this.fetchArchivedMessages(options, page);
                 } else {
@@ -150,11 +150,11 @@ converse.plugins.add('converse-mam', {
              */
             const preference = sizzle(`prefs[xmlns="${Strophe.NS.MAM}"]`, iq).pop();
             const default_pref = preference.getAttribute('default');
-            if (default_pref !== _converse.message_archiving) {
+            if (default_pref !== _converse.api.settings.get('message_archiving')) {
                 const stanza = $iq({'type': 'set'})
                     .c('prefs', {
                         'xmlns':Strophe.NS.MAM,
-                        'default':_converse.message_archiving
+                        'default':_converse.api.settings.get('message_archiving')
                     });
                 Array.from(preference.children).forEach(child => stanza.cnode(child).up());
 
@@ -162,19 +162,19 @@ converse.plugins.add('converse-mam', {
                 // (see example 18: https://xmpp.org/extensions/xep-0313.html#config)
                 // but Prosody doesn't do this, so we don't rely on it.
                 _converse.api.sendIQ(stanza)
-                    .then(() => feature.save({'preferences': {'default':_converse.message_archiving}}))
+                    .then(() => feature.save({'preferences': {'default':_converse.api.settings.get('message_archiving')}}))
                     .catch(_converse.onMAMError);
             } else {
-                feature.save({'preferences': {'default':_converse.message_archiving}});
+                feature.save({'preferences': {'default':_converse.api.settings.get('message_archiving')}});
             }
         };
 
         function getMAMPrefsFromFeature (feature) {
             const prefs = feature.get('preferences') || {};
-            if (feature.get('var') !== Strophe.NS.MAM || _converse.message_archiving === undefined) {
+            if (feature.get('var') !== Strophe.NS.MAM || _converse.api.settings.get('message_archiving') === undefined) {
                 return;
             }
-            if (prefs['default'] !== _converse.message_archiving) {
+            if (prefs['default'] !== _converse.api.settings.get('message_archiving')) {
                 _converse.api.sendIQ($iq({'type': 'get'}).c('prefs', {'xmlns': Strophe.NS.MAM}))
                     .then(iq => _converse.onMAMPreferences(iq, feature))
                     .catch(_converse.onMAMError);
@@ -479,7 +479,7 @@ converse.plugins.add('converse-mam', {
                     }, Strophe.NS.MAM);
 
                     let error;
-                    const iq_result = await _converse.api.sendIQ(stanza, _converse.message_archiving_timeout, false)
+                    const iq_result = await _converse.api.sendIQ(stanza, _converse.api.settings.get('message_archiving_timeout'), false)
                     if (iq_result === null) {
                         const err_msg = "Timeout while trying to fetch archived messages.";
                         log.error(err_msg);

+ 20 - 20
src/headless/converse-muc.js

@@ -124,7 +124,7 @@ converse.plugins.add('converse-muc', {
         });
         _converse.api.promises.add(['roomsAutoJoined']);
 
-        if (_converse.locked_muc_domain && !isString(_converse.muc_domain)) {
+        if (_converse.api.settings.get('locked_muc_domain') && !isString(_converse.api.settings.get('muc_domain'))) {
             throw new Error("Config Error: it makes no sense to set locked_muc_domain "+
                             "to true when muc_domain is not set");
         }
@@ -207,7 +207,7 @@ converse.plugins.add('converse-muc', {
             const nick = _converse.xmppstatus.getNickname();
             if (nick) {
                 return nick;
-            } else if (_converse.muc_nickname_from_jid) {
+            } else if (_converse.api.settings.get('muc_nickname_from_jid')) {
                 return Strophe.unescapeNode(Strophe.getNodeFromJid(_converse.bare_jid));
             }
         }
@@ -259,7 +259,7 @@ converse.plugins.add('converse-muc', {
              * @returns { Boolean }
              */
             mayBeModerated () {
-                return ['all', 'moderator'].includes(_converse.allow_message_retraction) &&
+                return ['all', 'moderator'].includes(_converse.api.settings.get('allow_message_retraction')) &&
                     this.collection.chatbox.canModerateMessages();
             },
 
@@ -351,7 +351,7 @@ converse.plugins.add('converse-muc', {
                     'num_unread_general': 0,
                     'bookmarked': false,
                     'chat_state': undefined,
-                    'hidden': ['mobile', 'fullscreen'].includes(_converse.view_mode),
+                    'hidden': ['mobile', 'fullscreen'].includes(_converse.api.settings.get("view_mode")),
                     'message_type': 'groupchat',
                     'name': '',
                     'num_unread': 0,
@@ -427,7 +427,7 @@ converse.plugins.add('converse-muc', {
                 nick = await this.getAndPersistNickname(nick);
                 if (!nick) {
                     u.safeSave(this.session, {'connection_status': converse.ROOMSTATUS.NICKNAME_REQUIRED});
-                    if (_converse.muc_show_logs_before_join) {
+                    if (_converse.api.settings.get('muc_show_logs_before_join')) {
                         await this.fetchMessages();
                     }
                     return this;
@@ -436,7 +436,7 @@ converse.plugins.add('converse-muc', {
                     'from': _converse.connection.jid,
                     'to': this.getRoomJIDAndNick()
                 }).c("x", {'xmlns': Strophe.NS.MUC})
-                  .c("history", {'maxstanzas': this.features.get('mam_enabled') ? 0 : _converse.muc_history_max_stanzas}).up();
+                  .c("history", {'maxstanzas': this.features.get('mam_enabled') ? 0 : _converse.api.settings.get('muc_history_max_stanzas')}).up();
 
                 if (password) {
                     stanza.cnode(Strophe.xmlElement("password", [], password));
@@ -493,7 +493,7 @@ converse.plugins.add('converse-muc', {
                      */
                     _converse.api.trigger('enteredNewRoom', this);
 
-                    if (_converse.auto_register_muc_nickname &&
+                    if (_converse.api.settings.get('auto_register_muc_nickname') &&
                             await _converse.api.disco.supports(Strophe.NS.MUC_REGISTER, this.get('jid'))) {
                         this.registerNickname()
                     }
@@ -618,7 +618,7 @@ converse.plugins.add('converse-muc', {
             },
 
             invitesAllowed () {
-                return _converse.allow_muc_invitations &&
+                return _converse.api.settings.get('allow_muc_invitations') &&
                     (this.features.get('open') ||
                         this.getOwnAffiliation() === "owner"
                     );
@@ -628,7 +628,7 @@ converse.plugins.add('converse-muc', {
                 const name = this.get('name');
                 if (name) {
                     return name;
-                } else if (_converse.locked_muc_domain === 'hidden') {
+                } else if (_converse.api.settings.get('locked_muc_domain') === 'hidden') {
                     return Strophe.getNodeFromJid(this.get('jid'));
                 } else {
                     return this.get('jid');
@@ -2089,7 +2089,7 @@ converse.plugins.add('converse-muc', {
 
 
             onNicknameClash (presence) {
-                if (_converse.muc_nickname_from_jid) {
+                if (_converse.api.settings.get('muc_nickname_from_jid')) {
                     const nick = presence.getAttribute('from').split('/')[1];
                     if (nick === _converse.getDefaultMUCNickname()) {
                         this.join(nick + '-2');
@@ -2216,7 +2216,7 @@ converse.plugins.add('converse-muc', {
                     if (locked_room) {
                         if (this.get('auto_configure')) {
                             this.autoConfigureChatRoom().then(() => this.refreshDiscoInfo());
-                        } else if (_converse.muc_instant_rooms) {
+                        } else if (_converse.api.settings.get('muc_instant_rooms')) {
                             // Accept default configuration
                             this.sendConfiguration().then(() => this.refreshDiscoInfo());
                         } else {
@@ -2359,7 +2359,7 @@ converse.plugins.add('converse-muc', {
             },
 
             getAutoFetchedAffiliationLists () {
-                const affs = _converse.muc_fetch_members;
+                const affs = _converse.api.settings.get('muc_fetch_members');
                 return Array.isArray(affs) ? affs :  (affs ? ['member', 'admin', 'owner'] : []);
             },
 
@@ -2426,13 +2426,13 @@ converse.plugins.add('converse-muc', {
         _converse.RoomsPanelModel = Model.extend({
             defaults: function () {
                 return {
-                    'muc_domain': _converse.muc_domain,
+                    'muc_domain': _converse.api.settings.get('muc_domain'),
                     'nick': _converse.getDefaultMUCNickname()
                 }
             },
 
             setDomain (jid) {
-                if (!_converse.locked_muc_domain) {
+                if (!_converse.api.settings.get('locked_muc_domain')) {
                     this.save('muc_domain', Strophe.getDomainFromJid(jid));
                 }
             }
@@ -2453,7 +2453,7 @@ converse.plugins.add('converse-muc', {
                 reason = x_el.getAttribute('reason');
 
             let result;
-            if (_converse.auto_join_on_invite) {
+            if (_converse.api.settings.get('auto_join_on_invite')) {
                 result = true;
             } else {
                 // Invite request might come from someone not your roster list
@@ -2478,7 +2478,7 @@ converse.plugins.add('converse-muc', {
             }
         };
 
-        if (_converse.allow_muc_invitations) {
+        if (_converse.api.settings.get('allow_muc_invitations')) {
             const registerDirectInvitationHandler = function () {
                 _converse.connection.addHandler(
                     (message) =>  {
@@ -2504,7 +2504,7 @@ converse.plugins.add('converse-muc', {
          * settings).
          */
         function autoJoinRooms () {
-            _converse.auto_join_rooms.forEach(groupchat => {
+            _converse.api.settings.get('auto_join_rooms').forEach(groupchat => {
                 if (isString(groupchat)) {
                     if (_converse.chatboxes.where({'jid': groupchat}).length) {
                         return;
@@ -2542,10 +2542,10 @@ converse.plugins.add('converse-muc', {
         _converse.api.listen.on('windowStateChanged', onWindowStateChanged);
 
         _converse.api.listen.on('addClientFeatures', () => {
-            if (_converse.allow_muc) {
+            if (_converse.api.settings.get('allow_muc')) {
                 _converse.api.disco.own.features.add(Strophe.NS.MUC);
             }
-            if (_converse.allow_muc_invitations) {
+            if (_converse.api.settings.get('allow_muc_invitations')) {
                 _converse.api.disco.own.features.add('jabber:x:conference'); // Invites
             }
         });
@@ -2624,7 +2624,7 @@ converse.plugins.add('converse-muc', {
                  */
                 create (jids, attrs={}) {
                     attrs = isString(attrs) ? {'nick': attrs} : (attrs || {});
-                    if (!attrs.nick && _converse.muc_nickname_from_jid) {
+                    if (!attrs.nick && _converse.api.settings.get('muc_nickname_from_jid')) {
                         attrs.nick = Strophe.getNodeFromJid(_converse.bare_jid);
                     }
                     if (jids === undefined) {

+ 3 - 3
src/headless/converse-ping.js

@@ -46,7 +46,7 @@ converse.plugins.add('converse-ping', {
 
         function registerPingHandler () {
             _converse.connection.addHandler(() => {
-                if (_converse.ping_interval > 0) {
+                if (_converse.api.settings.get('ping_interval') > 0) {
                     // Handler on each stanza, saves the received date
                     // in order to ping only when needed.
                     lastStanzaDate = new Date();
@@ -56,12 +56,12 @@ converse.plugins.add('converse-ping', {
         }
 
         setTimeout(() => {
-            if (_converse.ping_interval > 0) {
+            if (_converse.api.settings.get('ping_interval') > 0) {
                 const now = new Date();
                 if (!lastStanzaDate) {
                     lastStanzaDate = now;
                 }
-                if ((now - lastStanzaDate)/1000 > _converse.ping_interval) {
+                if ((now - lastStanzaDate)/1000 > _converse.api.settings.get('ping_interval')) {
                     return _converse.api.ping();
                 }
                 return true;

+ 4 - 4
src/headless/converse-roster.js

@@ -710,13 +710,13 @@ converse.plugins.add('converse-roster', {
                     bare_jid = Strophe.getBareJidFromJid(jid),
                     contact = this.get(bare_jid);
 
-                if (!_converse.allow_contact_requests) {
+                if (!_converse.api.settings.get('allow_contact_requests')) {
                     _converse.rejectPresenceSubscription(
                         jid,
                         __("This client does not allow presence subscriptions")
                     );
                 }
-                if (_converse.auto_subscribe) {
+                if (_converse.api.settings.get('auto_subscribe')) {
                     if ((!contact) || (contact.get('subscription') !== 'to')) {
                         this.subscribeBack(bare_jid, presence);
                     } else {
@@ -742,8 +742,8 @@ converse.plugins.add('converse-roster', {
 
                 if ((_converse.connection.jid !== jid) &&
                         (presence_type !== 'unavailable') &&
-                        (_converse.synchronize_availability === true ||
-                        _converse.synchronize_availability === resource)) {
+                        (_converse.api.settings.get('synchronize_availability') === true ||
+                        _converse.api.settings.get('synchronize_availability') === resource)) {
                     // Another resource has changed its status and
                     // synchronize_availability option set to update,
                     // we'll update ours as well.

+ 3 - 3
src/headless/converse-smacks.js

@@ -185,7 +185,7 @@ converse.plugins.add('converse-smacks', {
         }
 
         async function sendEnableStanza () {
-            if (!_converse.enable_smacks || _converse.session.get('smacks_enabled')) {
+            if (!_converse.api.settings.get('enable_smacks') || _converse.session.get('smacks_enabled')) {
                 return;
             }
             if (await isStreamManagementSupported()) {
@@ -202,7 +202,7 @@ converse.plugins.add('converse-smacks', {
         }
 
         async function enableStreamManagement () {
-            if (!_converse.enable_smacks) {
+            if (!_converse.api.settings.get('enable_smacks')) {
                 return;
             }
             if (!(await isStreamManagementSupported())) {
@@ -235,7 +235,7 @@ converse.plugins.add('converse-smacks', {
                     'unacked_stanzas',
                     (_converse.session.get('unacked_stanzas') || []).concat([stanza_string])
                 );
-                const max_unacked = _converse.smacks_max_unacked_stanzas;
+                const max_unacked = _converse.api.settings.get('smacks_max_unacked_stanzas');
                 if (max_unacked > 0) {
                     const num = _converse.session.get('num_stanzas_since_last_ack') + 1;
                     if (num % max_unacked === 0) {

+ 26 - 16
src/headless/converse-status.js

@@ -15,9 +15,17 @@ converse.plugins.add('converse-status', {
     initialize () {
         const { _converse } = this;
 
+        _converse.api.settings.update({
+            auto_away: 0, // Seconds after which user status is set to 'away'
+            auto_xa: 0, // Seconds after which user status is set to 'xa'
+            csi_waiting_time: 0, // Support for XEP-0352. Seconds before client is considered idle and CSI is sent out.
+            default_state: 'online',
+            priority: 0,
+        });
+
         _converse.XMPPStatus = Model.extend({
             defaults () {
-                return {"status":  _converse.default_state}
+                return {"status":  _converse.api.settings.get("default_state")}
             },
 
             initialize () {
@@ -42,7 +50,7 @@ converse.plugins.add('converse-status', {
 
             constructPresence (type, status_message) {
                 let presence;
-                type = isString(type) ? type : (this.get('status') || _converse.default_state);
+                type = isString(type) ? type : (this.get('status') || _converse.api.settings.get("default_state"));
                 status_message = isString(status_message) ? status_message : this.get('status_message');
                 // Most of these presence types are actually not explicitly sent,
                 // but I add all of them here for reference and future proofing.
@@ -64,7 +72,9 @@ converse.plugins.add('converse-status', {
                 if (status_message) {
                     presence.c('status').t(status_message).up();
                 }
-                presence.c('priority').t(isNaN(Number(_converse.priority)) ? 0 : _converse.priority).up();
+
+                const priority = _converse.api.settings.get("priority");
+                presence.c('priority').t(isNaN(Number(priority)) ? 0 : priority).up();
                 if (_converse.idle) {
                     const idle_since = new Date();
                     idle_since.setSeconds(idle_since.getSeconds() - _converse.idle_seconds);
@@ -113,7 +123,7 @@ converse.plugins.add('converse-status', {
                 _converse.auto_changed_status = false;
                 // XXX: we should really remember the original state here, and
                 // then set it back to that...
-                _converse.xmppstatus.set('status', _converse.default_state);
+                _converse.xmppstatus.set('status', _converse.api.settings.get("default_state"));
             }
         };
 
@@ -128,24 +138,24 @@ converse.plugins.add('converse-status', {
             }
             const stat = _converse.xmppstatus.get('status');
             _converse.idle_seconds++;
-            if (_converse.csi_waiting_time > 0 &&
-                    _converse.idle_seconds > _converse.csi_waiting_time &&
+            if (_converse.api.settings.get("csi_waiting_time") > 0 &&
+                    _converse.idle_seconds > _converse.api.settings.get("csi_waiting_time") &&
                     !_converse.inactive) {
                 _converse.sendCSI(_converse.INACTIVE);
             }
-            if (_converse.idle_presence_timeout > 0 &&
-                    _converse.idle_seconds > _converse.idle_presence_timeout &&
+            if (_converse.api.settings.get("idle_presence_timeout") > 0 &&
+                    _converse.idle_seconds > _converse.api.settings.get("idle_presence_timeout") &&
                     !_converse.idle) {
                 _converse.idle = true;
                 _converse.xmppstatus.sendPresence();
             }
-            if (_converse.auto_away > 0 &&
-                    _converse.idle_seconds > _converse.auto_away &&
+            if (_converse.api.settings.get("auto_away") > 0 &&
+                    _converse.idle_seconds > _converse.api.settings.get("auto_away") &&
                     stat !== 'away' && stat !== 'xa' && stat !== 'dnd') {
                 _converse.auto_changed_status = true;
                 _converse.xmppstatus.set('status', 'away');
-            } else if (_converse.auto_xa > 0 &&
-                    _converse.idle_seconds > _converse.auto_xa &&
+            } else if (_converse.api.settings.get("auto_xa") > 0 &&
+                    _converse.idle_seconds > _converse.api.settings.get("auto_xa") &&
                     stat !== 'xa' && stat !== 'dnd') {
                 _converse.auto_changed_status = true;
                 _converse.xmppstatus.set('status', 'xa');
@@ -157,10 +167,10 @@ converse.plugins.add('converse-status', {
              * Required for the auto_away, auto_xa and csi_waiting_time features.
              */
             if (
-                _converse.auto_away < 1 &&
-                _converse.auto_xa < 1 &&
-                _converse.csi_waiting_time < 1 &&
-                _converse.idle_presence_timeout < 1
+                _converse.api.settings.get("auto_away") < 1 &&
+                _converse.api.settings.get("auto_xa") < 1 &&
+                _converse.api.settings.get("csi_waiting_time") < 1 &&
+                _converse.api.settings.get("idle_presence_timeout") < 1
             ) {
                 // Waiting time of less then one second means features aren't used.
                 return;

+ 1 - 1
src/headless/i18n.js

@@ -94,7 +94,7 @@ export const i18n = {
      */
     async fetchTranslations (_converse) {
         const locale = _converse.locale;
-        if (!isConverseLocale(locale, _converse.locales) || locale === 'en') {
+        if (!isConverseLocale(locale, _converse.api.settings.get("locales")) || locale === 'en') {
             return;
         }
         const { default: data } = await import(/*webpackChunkName: "locales/[request]" */ `../../locale/${locale}/LC_MESSAGES/converse.po`);

+ 9 - 4
src/headless/utils/core.js

@@ -218,10 +218,15 @@ u.isServiceUnavailableError = function (stanza) {
     return sizzle(`error[type="cancel"] service-unavailable[xmlns="${Strophe.NS.STANZAS}"]`, stanza).length > 0;
 }
 
+/**
+ * Merge the second object into the first one.
+ * @private
+ * @method u#stringToNode
+ * @param { Object } first
+ * @param { Object } second
+ */
 u.merge = function merge (first, second) {
-    /* Merge the second object into the first one.
-     */
-    for (var k in second) {
+    for (const k in second) {
         if (isObject(first[k])) {
             merge(first[k], second[k]);
         } else {
@@ -493,7 +498,7 @@ u.geoUriToHttp = function(text, geouri_replacement) {
 
 u.httpToGeoUri = function(text, _converse) {
     const replacement = 'geo:$1,$2';
-    return text.replace(_converse.geouri_regex, replacement);
+    return text.replace(_converse.api.settings.get("geouri_regex"), replacement);
 };
 
 u.getSelectValues = function (select) {

+ 1 - 1
src/templates/chatbox_head.js

@@ -19,7 +19,7 @@ export default (o) => {
     return html`
         <div class="chatbox-title ${ o.status ? '' :  "chatbox-title--no-desc"}">
             <div class="chatbox-title--row">
-                ${ (!o._converse.singleton) ? html`<div class="chatbox-navback"><i class="fa fa-arrow-left"></i></div>` : '' }
+                ${ (!o._converse.api.settings.get("singleton")) ? html`<div class="chatbox-navback"><i class="fa fa-arrow-left"></i></div>` : '' }
                 ${ (o.type !== o._converse.HEADLINES_TYPE) ? avatar(Object.assign({}, o, avatar_data)) : '' }
                 <div class="chatbox-title__text" title="${o.jid}">
                     ${ o.url ? html`<a href="${o.url}" target="_blank" rel="noopener" class="user">${o.display_name}</a>` : o.display_name}

+ 3 - 3
src/templates/emoji_picker.js

@@ -33,7 +33,7 @@ const emoji_picker_header = (o) => html`
 
 const emoji_item = (o) => {
     let emoji;
-    if (o._converse.use_system_emojis) {
+    if (o._converse.api.settings.get('use_system_emojis')) {
         emoji = unsafeHTML(xss.filterXSS(o.transform(o.emoji.sn), {'whitelist': {'img': []}}));
     }
     return html`
@@ -53,7 +53,7 @@ const search_results = (o) => html`
 `;
 
 const emojis_for_category = (o) => html`
-    <a id="emoji-picker-${o.category}" class="emoji-category__heading" data-category="${o.category}">${ __(o._converse.emoji_category_labels[o.category]) }</a>
+    <a id="emoji-picker-${o.category}" class="emoji-category__heading" data-category="${o.category}">${ __(o._converse.api.settings.get('emoji_category_labels')[o.category]) }</a>
     <ul class="emoji-picker" data-category="${o.category}">
         ${ Object.values(o.emojis_by_category[o.category]).map(emoji => emoji_item(Object.assign({emoji}, o))) }
     </ul>
@@ -63,7 +63,7 @@ const emojis_for_category = (o) => html`
 const skintone_emoji = (o) => {
     const shortname = ':'+o.skintone+':';
     let emoji;
-    if (o._converse.use_system_emojis) {
+    if (o._converse.api.settings.get('use_system_emojis')) {
         emoji = unsafeHTML(xss.filterXSS(o.transform(shortname), {'whitelist': {'img': []}}));
     }
     return html`

+ 2 - 2
src/templates/login_panel.js

@@ -34,7 +34,7 @@ const password_input = () => html`
     </div>
 `;
 
-const register_link = (o) => html`
+const register_link = () => html`
     <fieldset class="switch-form">
         <p>${i18n_hint_no_account}</p>
         <p><a class="register-account toggle-register-login" href="#converse/register">${i18n_create_account}</a></p>
@@ -44,7 +44,7 @@ const register_link = (o) => html`
 const show_register_link = (o) => {
     const _converse = o._converse;
     return _converse.allow_registration &&
-        !_converse.auto_login &&
+        !_converse.api.settings.get("auto_login") &&
         _converse.pluggable.plugins['converse-register'].enabled(_converse);
 }
 

+ 2 - 2
src/templates/profile.js

@@ -14,8 +14,8 @@ export default (o) => html`
                 <canvas class="avatar align-self-center" height="40" width="40"></canvas>
             </a>
             <span class="username w-100 align-self-center">${o.fullname}</span>
-            ${o._converse.show_client_info && html`<a class="controlbox-heading__btn show-client-info fa fa-info-circle align-self-center" title="${i18n_details}"></a>`}
-            ${o._converse.allow_logout && html`<a class="controlbox-heading__btn logout fa fa-sign-out-alt align-self-center" title="${i18n_logout}"></a>`}
+            ${o._converse.api.settings.get('show_client_info') && html`<a class="controlbox-heading__btn show-client-info fa fa-info-circle align-self-center" title="${i18n_details}"></a>`}
+            ${o._converse.api.settings.get('allow_logout') && html`<a class="controlbox-heading__btn logout fa fa-sign-out-alt align-self-center" title="${i18n_logout}"></a>`}
         </div>
         <div class="d-flex xmpp-status">
             <a class="change-status" title="${i18n_change_status}" data-toggle="modal" data-target="#changeStatusModal">

+ 2 - 2
src/utils/html.js

@@ -94,7 +94,7 @@ function renderAudioURL (_converse, uri) {
 }
 
 function renderImageURL (_converse, uri) {
-    if (!_converse.show_images_inline) {
+    if (!_converse.api.settings.get('show_images_inline')) {
         return u.convertToHyperlink(uri);
     }
     const { __ } = _converse;
@@ -207,7 +207,7 @@ async function renderImage (img_url, link_url, el, callback) {
  * @returns { Promise }
  */
 u.renderImageURLs = function (_converse, el) {
-    if (!_converse.show_images_inline) {
+    if (!_converse.api.settings.get('show_images_inline')) {
         return Promise.resolve();
     }
     const list = el.textContent.match(URL_REGEX) || [];