Bladeren bron

Work on removing circular dependencies

Updates #3348

- Update to latest Skeletor
- Updatee Webpack
JC Brand 1 jaar geleden
bovenliggende
commit
017a054e3f
100 gewijzigde bestanden met toevoegingen van 1457 en 731 verwijderingen
  1. 690 47
      package-lock.json
  2. 6 4
      package.json
  3. 5 5
      src/headless/index.js
  4. 1 1
      src/headless/package.json
  5. 2 1
      src/headless/plugins/adhoc/api.js
  6. 1 1
      src/headless/plugins/adhoc/index.js
  7. 1 1
      src/headless/plugins/adhoc/utils.js
  8. 2 1
      src/headless/plugins/bookmarks/collection.js
  9. 2 1
      src/headless/plugins/bookmarks/index.js
  10. 1 1
      src/headless/plugins/bookmarks/model.js
  11. 2 1
      src/headless/plugins/bookmarks/utils.js
  12. 2 1
      src/headless/plugins/bosh/index.js
  13. 2 1
      src/headless/plugins/caps/index.js
  14. 1 1
      src/headless/plugins/caps/utils.js
  15. 2 1
      src/headless/plugins/chat/message.js
  16. 5 4
      src/headless/plugins/chat/model.js
  17. 2 1
      src/headless/plugins/chat/parsers.js
  18. 2 1
      src/headless/plugins/chat/plugin.js
  19. 3 2
      src/headless/plugins/chat/utils.js
  20. 5 4
      src/headless/plugins/chatboxes/api.js
  21. 2 1
      src/headless/plugins/chatboxes/index.js
  22. 1 3
      src/headless/plugins/chatboxes/utils.js
  23. 2 1
      src/headless/plugins/disco/api.js
  24. 2 1
      src/headless/plugins/disco/entity.js
  25. 2 1
      src/headless/plugins/disco/index.js
  26. 2 1
      src/headless/plugins/disco/utils.js
  27. 2 1
      src/headless/plugins/emoji/index.js
  28. 1 1
      src/headless/plugins/emoji/utils.js
  29. 2 1
      src/headless/plugins/headlines/index.js
  30. 2 1
      src/headless/plugins/mam/api.js
  31. 2 1
      src/headless/plugins/mam/index.js
  32. 2 1
      src/headless/plugins/mam/utils.js
  33. 2 1
      src/headless/plugins/muc/affiliations/utils.js
  34. 160 153
      src/headless/plugins/muc/api.js
  35. 2 1
      src/headless/plugins/muc/muc.js
  36. 2 1
      src/headless/plugins/muc/occupants.js
  37. 2 1
      src/headless/plugins/muc/parsers.js
  38. 2 1
      src/headless/plugins/muc/plugin.js
  39. 2 1
      src/headless/plugins/muc/utils.js
  40. 2 1
      src/headless/plugins/ping/api.js
  41. 2 1
      src/headless/plugins/ping/index.js
  42. 2 1
      src/headless/plugins/ping/utils.js
  43. 2 1
      src/headless/plugins/pubsub.js
  44. 2 1
      src/headless/plugins/roster/api.js
  45. 2 1
      src/headless/plugins/roster/contact.js
  46. 2 1
      src/headless/plugins/roster/contacts.js
  47. 2 1
      src/headless/plugins/roster/plugin.js
  48. 1 1
      src/headless/plugins/roster/presence.js
  49. 3 2
      src/headless/plugins/roster/utils.js
  50. 2 1
      src/headless/plugins/smacks/index.js
  51. 2 1
      src/headless/plugins/smacks/utils.js
  52. 3 2
      src/headless/plugins/status/plugin.js
  53. 2 1
      src/headless/plugins/status/status.js
  54. 2 1
      src/headless/plugins/status/utils.js
  55. 2 1
      src/headless/plugins/vcard/api.js
  56. 2 1
      src/headless/plugins/vcard/plugin.js
  57. 3 2
      src/headless/plugins/vcard/utils.js
  58. 1 1
      src/headless/plugins/vcard/vcards.js
  59. 1 1
      src/headless/shared/_converse.js
  60. 2 1
      src/headless/shared/actions.js
  61. 0 2
      src/headless/shared/api/index.js
  62. 21 11
      src/headless/shared/api/presence.js
  63. 10 4
      src/headless/shared/api/public.js
  64. 2 2
      src/headless/shared/api/user.js
  65. 2 1
      src/headless/shared/chat/utils.js
  66. 18 9
      src/headless/shared/connection/index.js
  67. 4 0
      src/headless/shared/connection/utils.js
  68. 1 1
      src/headless/shared/i18n.js
  69. 1 1
      src/headless/shared/rsm.js
  70. 5 71
      src/headless/shared/settings/api.js
  71. 73 0
      src/headless/shared/settings/user/api.js
  72. 44 0
      src/headless/shared/settings/user/utils.js
  73. 1 44
      src/headless/shared/settings/utils.js
  74. 1 1
      src/headless/types/index.d.ts
  75. 2 2
      src/headless/types/plugins/chatboxes/api.d.ts
  76. 113 112
      src/headless/types/plugins/muc/api.d.ts
  77. 1 1
      src/headless/types/shared/_converse.d.ts
  78. 0 1
      src/headless/types/shared/api/index.d.ts
  79. 4 5
      src/headless/types/shared/api/presence.d.ts
  80. 11 10
      src/headless/types/shared/api/public.d.ts
  81. 1 1
      src/headless/types/shared/api/user.d.ts
  82. 0 1
      src/headless/types/shared/connection/index.d.ts
  83. 1 0
      src/headless/types/shared/connection/utils.d.ts
  84. 1 1
      src/headless/types/shared/i18n.d.ts
  85. 4 41
      src/headless/types/shared/settings/api.d.ts
  86. 41 0
      src/headless/types/shared/settings/user/api.d.ts
  87. 4 0
      src/headless/types/shared/settings/user/utils.d.ts
  88. 0 3
      src/headless/types/shared/settings/utils.d.ts
  89. 19 47
      src/headless/types/utils/session.d.ts
  90. 4 4
      src/headless/utils/init.js
  91. 25 9
      src/headless/utils/session.js
  92. 4 2
      src/headless/utils/storage.js
  93. 7 5
      src/headless/utils/url.js
  94. 1 2
      src/plugins/chatboxviews/view.js
  95. 15 0
      src/plugins/omemo/api.js
  96. 1 2
      src/plugins/omemo/devicelist.js
  97. 1 1
      src/plugins/omemo/index.js
  98. 1 10
      src/plugins/omemo/utils.js
  99. 0 41
      src/plugins/roomslist/templates/groups.js
  100. 40 6
      src/plugins/roomslist/templates/roomslist.js

File diff suppressed because it is too large
+ 690 - 47
package-lock.json


+ 6 - 4
package.json

@@ -81,6 +81,7 @@
     "autoprefixer": "^10.4.5",
     "babel-loader": "^9.1.0",
     "bootstrap.native-loader": "2.0.0",
+    "circular-dependency-plugin": "^5.2.2",
     "clean-css-cli": "^5.6.2",
     "copy-webpack-plugin": "^12.0.2",
     "css-loader": "^6.7.1",
@@ -96,7 +97,7 @@
     "karma-cli": "^2.0.0",
     "karma-jasmine": "^5.0.0",
     "karma-jasmine-html-reporter": "^2.0.0",
-    "karma-webpack": "^5.0.0",
+    "karma-webpack": "^5.0.1",
     "mini-css-extract-plugin": "^2.6.0",
     "minimist": "^1.2.6",
     "po-loader": "0.7.0",
@@ -109,10 +110,10 @@
     "typescript": "^4.9.5",
     "typescript-eslint-parser": "^22.0.0",
     "uglify-js": "^3.17.4",
-    "webpack": "^5.86.0",
+    "webpack": "^5.90.3",
     "webpack-cli": "^5.1.4",
-    "webpack-dev-server": "^4.8.1",
-    "webpack-merge": "^5.8.0"
+    "webpack-dev-server": "^5.0.3",
+    "webpack-merge": "^5.10.0"
   },
   "dependencies": {
     "bootstrap": "^4.6.0",
@@ -120,6 +121,7 @@
     "client-compress": "^2.2.2",
     "dayjs": "^1.11.8",
     "dompurify": "^3.0.8",
+    "dpdm": "^3.14.0",
     "favico.js-slevomat": "^0.3.11",
     "gifuct-js": "^2.1.2",
     "jed": "1.1.1",

+ 5 - 5
src/headless/index.js

@@ -1,13 +1,13 @@
-import './shared/constants.js';
+import dayjs from 'dayjs';
 import advancedFormat from 'dayjs/plugin/advancedFormat';
+
+import './shared/constants.js';
+
 import api from './shared/api/index.js';
 import u from './utils/index.js';
 import _converse from './shared/_converse';
-_converse.api = api;
-
-import dayjs from 'dayjs';
 import i18n from './shared/i18n';
-import { converse } from './shared/api/public.js';
+import converse from './shared/api/public.js';
 
 dayjs.extend(advancedFormat);
 

+ 1 - 1
src/headless/package.json

@@ -34,7 +34,7 @@
   },
   "dependencies": {
     "@converse/openpromise": "^0.0.1",
-    "@converse/skeletor": "conversejs/skeletor#2d33edf584cb37b7a54c8b140b85210f080769b1 ",
+    "@converse/skeletor": "conversejs/skeletor#7e1cbeba0161bfe91d709a22deaa70e4daa0cd39",
     "dayjs": "^1.11.8",
     "dompurify": "^3.0.8",
     "filesize": "^10.0.7",

+ 2 - 1
src/headless/plugins/adhoc/api.js

@@ -1,6 +1,7 @@
 import log from '../../log.js';
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import { getCommandFields, parseForCommands } from './utils.js';
 
 const { Strophe, $iq, u, stx } = converse.env;

+ 1 - 1
src/headless/plugins/adhoc/index.js

@@ -1,5 +1,5 @@
 import adhoc_api from './api.js';
-import { converse } from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 
 const { Strophe } = converse.env;
 

+ 1 - 1
src/headless/plugins/adhoc/utils.js

@@ -1,5 +1,5 @@
 import sizzle from 'sizzle';
-import { converse } from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import { getAttributes } from '../../shared/parsers';
 
 const { Strophe, u } = converse.env;

+ 2 - 1
src/headless/plugins/bookmarks/collection.js

@@ -1,7 +1,8 @@
 import "../../plugins/muc/index.js";
 import Bookmark from './model.js';
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import log from "../../log.js";
 import { Collection } from "@converse/skeletor";
 import { getOpenPromise } from '@converse/openpromise';

+ 2 - 1
src/headless/plugins/bookmarks/index.js

@@ -8,7 +8,8 @@ import "../..//plugins/muc/index.js";
 import Bookmark from './model.js';
 import Bookmarks from './collection.js';
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import { initBookmarks, getNicknameFromBookmark, handleBookmarksPush } from './utils.js';
 
 const { Strophe } = converse.env;

+ 1 - 1
src/headless/plugins/bookmarks/model.js

@@ -1,4 +1,4 @@
-import { converse } from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import { Model } from '@converse/skeletor';
 
 const { Strophe } = converse.env;

+ 2 - 1
src/headless/plugins/bookmarks/utils.js

@@ -1,5 +1,6 @@
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import log from "../../log.js";
 
 const { Strophe, sizzle } = converse.env;

+ 2 - 1
src/headless/plugins/bosh/index.js

@@ -6,7 +6,8 @@
 import 'strophe.js/src/bosh';
 import { Strophe } from "strophe.js";
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import bosh_api from './api.js';
 import { attemptPrebind, clearSession, saveJIDToSession} from './utils.js';
 

+ 2 - 1
src/headless/plugins/caps/index.js

@@ -2,7 +2,8 @@
  * @copyright 2022, the Converse.js contributors
  * @license Mozilla Public License (MPLv2)
  */
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import { addCapsNode } from './utils.js';
 
 const { Strophe } = converse.env;

+ 1 - 1
src/headless/plugins/caps/utils.js

@@ -2,7 +2,7 @@
  * @typedef {import('strophe.js/src/builder.js').Builder} Strophe.Builder
  */
 import _converse from '../../shared/_converse.js';
-import { converse } from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import { arrayBufferToBase64, stringToArrayBuffer  } from '../../utils/arraybuffer.js';
 
 const { Strophe, $build } = converse.env;

+ 2 - 1
src/headless/plugins/chat/message.js

@@ -3,7 +3,8 @@
  */
 import ModelWithContact from './model-with-contact.js';
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import dayjs from 'dayjs';
 import log from '../../log.js';
 import { getOpenPromise } from '@converse/openpromise';

+ 5 - 4
src/headless/plugins/chat/model.js

@@ -5,14 +5,15 @@
  * @typedef {module:plugin-chat-parsers.MessageAttributes} MessageAttributes
  * @typedef {import('strophe.js/src/builder.js').Builder} Strophe.Builder
  */
+import pick from "lodash-es/pick";
+import { Model } from '@converse/skeletor';
+import { ACTIVE, PRIVATE_CHAT_TYPE, COMPOSING, INACTIVE, PAUSED, SUCCESS, GONE } from '../../shared/constants.js';
 import ModelWithContact from './model-with-contact.js';
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import isMatch from "lodash-es/isMatch";
 import log from '../../log.js';
-import pick from "lodash-es/pick";
-import { Model } from '@converse/skeletor';
-import { ACTIVE, PRIVATE_CHAT_TYPE, COMPOSING, INACTIVE, PAUSED, SUCCESS, GONE } from '@converse/headless/shared/constants.js';
 import { TimeoutError } from '../../shared/errors.js';
 import { debouncedPruneHistory, handleCorrection } from '../../shared/chat/utils.js';
 import { filesize } from "filesize";

+ 2 - 1
src/headless/plugins/chat/parsers.js

@@ -3,7 +3,8 @@
  * @typedef {module:plugin-chat-parsers.MessageAttributes} MessageAttributes
  */
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import dayjs from 'dayjs';
 import log from '../../log.js';
 import u from '../../utils/index.js';

+ 2 - 1
src/headless/plugins/chat/plugin.js

@@ -2,7 +2,8 @@ import ChatBox from './model.js';
 import Message from './message.js';
 import Messages from './messages.js';
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import chat_api from './api.js';
 import { PRIVATE_CHAT_TYPE } from '../../shared/constants.js';
 import {

+ 3 - 2
src/headless/plugins/chat/utils.js

@@ -7,7 +7,8 @@
 import sizzle from "sizzle";
 import { Model } from '@converse/skeletor';
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from "../../shared/api/public.js";
 import log from '../../log.js';
 import { isArchived, isHeadline, isServerMessage, } from '../../shared/parsers';
 import { parseMessage } from './parsers.js';
@@ -29,7 +30,7 @@ export function routeToChat (event) {
 }
 
 export async function onClearSession () {
-    if (shouldClearCache()) {
+    if (shouldClearCache(_converse)) {
         const { chatboxes } = _converse.state;
         await Promise.all(
             chatboxes.map(/** @param {ChatBox} c */(c) => c.messages?.clearStore({ 'silent': true }))

+ 5 - 4
src/headless/plugins/chatboxes/api.js

@@ -3,9 +3,10 @@
  * @typedef {import('../chat/model.js').default} ChatBox
  */
 import _converse from '../../shared/_converse.js';
-import api from '../../shared/api/index.js';
+import promise_api from '../../shared/api/promise.js';
 import { createChatBox } from './utils.js';
 
+const { waitUntil } = promise_api;
 const _chatBoxTypes = {};
 
 
@@ -23,7 +24,7 @@ export default {
      * @param {new (attrs: object, options: object) => ChatBox} model - The type of chatbox that should be created
      */
     async create (jids=[], attrs={}, model) {
-        await api.waitUntil('chatBoxesFetched');
+        await waitUntil('chatBoxesFetched');
         if (typeof jids === 'string') {
             return createChatBox(jids, attrs, model);
         } else {
@@ -33,10 +34,10 @@ export default {
 
     /**
      * @method api.chatboxes.get
-     * @param {string|string[]} jids - A JID or array of JIDs
+     * @param {string|string[]} [jids] - A JID or array of JIDs
      */
     async get (jids) {
-        await api.waitUntil('chatBoxesFetched');
+        await waitUntil('chatBoxesFetched');
         const { chatboxes } = _converse.state;
         if (jids === undefined) {
             return chatboxes.models;

+ 2 - 1
src/headless/plugins/chatboxes/index.js

@@ -5,7 +5,8 @@
 import "../emoji/index.js";
 import ChatBoxes from './chatboxes.js';
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from "../../shared/api/public.js";
 import chatboxes_api from './api.js';
 
 const { Strophe } = converse.env;

+ 1 - 3
src/headless/plugins/chatboxes/utils.js

@@ -2,10 +2,8 @@
  * @typedef {import('../chat/model.js').default} ChatBox
  */
 import _converse from '../../shared/_converse.js';
-import { converse } from '../../shared/api/index.js';
 import log from "../../log";
-
-const { Strophe } = converse.env;
+import { Strophe } from 'strophe.js';
 
 
 /**

+ 2 - 1
src/headless/plugins/disco/api.js

@@ -4,7 +4,8 @@
  * @typedef {import('@converse/skeletor').Collection} Collection
  */
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import log from "../../log.js";
 import { getOpenPromise } from '@converse/openpromise';
 

+ 2 - 1
src/headless/plugins/disco/entity.js

@@ -1,5 +1,6 @@
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import log from '../../log.js';
 import sizzle from 'sizzle';
 import { Collection, Model } from '@converse/skeletor';

+ 2 - 1
src/headless/plugins/disco/index.js

@@ -6,7 +6,8 @@
 import DiscoEntities from './entities.js';
 import DiscoEntity from './entity.js';
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import disco_api from './api.js';
 import {
     clearSession,

+ 2 - 1
src/headless/plugins/disco/utils.js

@@ -1,5 +1,6 @@
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import { Collection } from "@converse/skeletor";
 import { createStore } from '../../utils/storage.js';
 

+ 2 - 1
src/headless/plugins/emoji/index.js

@@ -5,7 +5,8 @@
  */
 import './utils.js';
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import { getOpenPromise } from '@converse/openpromise';
 import EmojiPicker from './picker.js';
 

+ 1 - 1
src/headless/plugins/emoji/utils.js

@@ -1,5 +1,5 @@
 import { ASCII_REPLACE_REGEX, CODEPOINTS_REGEX } from './regexes.js';
-import { converse } from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 
 const { u } = converse.env;
 

+ 2 - 1
src/headless/plugins/headlines/index.js

@@ -4,7 +4,8 @@
  */
 import HeadlinesFeed from './feed.js';
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import headlines_api from './api.js';
 import { onHeadlineMessage } from './utils.js';
 import { HEADLINES_TYPE } from '../../shared/constants.js';

+ 2 - 1
src/headless/plugins/mam/api.js

@@ -2,7 +2,8 @@
  * @typedef {module:converse-rsm.RSMQueryParameters} RSMQueryParameters
  */
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import dayjs from 'dayjs';
 import log from '../../log.js';
 import sizzle from "sizzle";

+ 2 - 1
src/headless/plugins/mam/index.js

@@ -6,7 +6,8 @@
 import '../disco/index.js';
 import MAMPlaceholderMessage from './placeholder.js';
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import mam_api from './api.js';
 import { PRIVATE_CHAT_TYPE } from '../..//shared/constants.js';
 import { Strophe } from 'strophe.js';

+ 2 - 1
src/headless/plugins/mam/utils.js

@@ -4,7 +4,8 @@
  */
 import MAMPlaceholderMessage from './placeholder.js';
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import log from '../../log.js';
 import sizzle from 'sizzle';
 import { Strophe, $iq } from 'strophe.js';

+ 2 - 1
src/headless/plugins/muc/affiliations/utils.js

@@ -7,7 +7,8 @@
  * @typedef {import('@converse/skeletor').Model} Model
  */
 import _converse from '../../../shared/_converse.js';
-import api, { converse } from '../../../shared/api/index.js';
+import api from '../../../shared/api/index.js';
+import converse from '../../../shared/api/public.js';
 import log from '../../../log.js';
 import { AFFILIATIONS } from '../constants.js';
 import { parseMemberListIQ } from '../parsers.js';

+ 160 - 153
src/headless/plugins/muc/api.js

@@ -2,170 +2,177 @@
  * @typedef {import('./muc.js').default} MUC
  */
 import _converse from '../../shared/_converse.js';
-import api from '../../shared/api/index.js';
+import chatboxes from '../../plugins/chatboxes/api.js';
 import log from '../../log';
+import promise_api from '../../shared/api/promise.js';
+import { CHATROOMS_TYPE } from '../../shared/constants.js';
 import { Strophe } from 'strophe.js';
 import { getJIDFromURI } from '../../utils/jid.js';
-import { CHATROOMS_TYPE } from '../../shared/constants.js';
+import { settings_api } from '../../shared/settings/api.js';
 
+const { waitUntil } = promise_api;
+const { settings } = settings_api;
 
-export default {
+/**
+ * The "rooms" namespace groups methods relevant to chatrooms
+ * (aka groupchats).
+ *
+ * @namespace api.rooms
+ * @memberOf api
+ */
+const rooms = {
     /**
-     * The "rooms" namespace groups methods relevant to chatrooms
-     * (aka groupchats).
+     * Creates a new MUC chatroom (aka groupchat)
      *
-     * @namespace api.rooms
-     * @memberOf api
+     * Similar to {@link api.rooms.open}, but creates
+     * the chatroom in the background (i.e. doesn't cause a view to open).
+     *
+     * @method api.rooms.create
+     * @param {(string[]|string)} jids The JID or array of
+     *     JIDs of the chatroom(s) to create
+     * @param {object} [attrs] attrs The room attributes
+     * @returns {Promise<MUC[]|MUC>} Promise which resolves with the Model representing the chat.
      */
-    rooms: {
-        /**
-         * Creates a new MUC chatroom (aka groupchat)
-         *
-         * Similar to {@link api.rooms.open}, but creates
-         * the chatroom in the background (i.e. doesn't cause a view to open).
-         *
-         * @method api.rooms.create
-         * @param {(string[]|string)} jids The JID or array of
-         *     JIDs of the chatroom(s) to create
-         * @param {object} [attrs] attrs The room attributes
-         * @returns {Promise[]} Promise which resolves with the Model representing the chat.
-         */
-        create (jids, attrs = {}) {
-            attrs = typeof attrs === 'string' ? { 'nick': attrs } : attrs || {};
-            if (!attrs.nick && api.settings.get('muc_nickname_from_jid')) {
-                const bare_jid = _converse.session.get('bare_jid');
-                attrs.nick = Strophe.getNodeFromJid(bare_jid);
-            }
-            if (jids === undefined) {
-                throw new TypeError('rooms.create: You need to provide at least one JID');
-            } else if (typeof jids === 'string') {
-                return api.rooms.get(getJIDFromURI(jids), attrs, true);
-            }
-            return jids.map(jid => api.rooms.get(getJIDFromURI(jid), attrs, true));
-        },
+    create (jids, attrs = {}) {
+        attrs = typeof attrs === 'string' ? { 'nick': attrs } : attrs || {};
+        if (!attrs.nick && settings.get('muc_nickname_from_jid')) {
+            const bare_jid = _converse.session.get('bare_jid');
+            attrs.nick = Strophe.getNodeFromJid(bare_jid);
+        }
+        if (jids === undefined) {
+            throw new TypeError('rooms.create: You need to provide at least one JID');
+        } else if (typeof jids === 'string') {
+            return rooms.get(getJIDFromURI(jids), attrs, true);
+        }
+        return Promise.all(jids.map((jid) => /** @type {Promise<MUC>} */(rooms.get(getJIDFromURI(jid), attrs, true))));
+    },
 
-        /**
-         * Opens a MUC chatroom (aka groupchat)
-         *
-         * Similar to {@link api.chats.open}, but for groupchats.
-         *
-         * @method api.rooms.open
-         * @param {string|string[]} jids The room JID or JIDs (if not specified, all
-         *     currently open rooms will be returned).
-         * @param {object} attrs A map  containing any extra room attributes.
-         * @param {string} [attrs.nick] The current user's nickname for the MUC
-         * @param {boolean} [attrs.hidden]
-         * @param {boolean} [attrs.auto_configure] A boolean, indicating
-         *     whether the room should be configured automatically or not.
-         *     If set to `true`, then it makes sense to pass in configuration settings.
-         * @param {object} [attrs.roomconfig] A map of configuration settings to be used when the room gets
-         *     configured automatically. Currently it doesn't make sense to specify
-         *     `roomconfig` values if `auto_configure` is set to `false`.
-         *     For a list of configuration values that can be passed in, refer to these values
-         *     in the [XEP-0045 MUC specification](https://xmpp.org/extensions/xep-0045.html#registrar-formtype-owner).
-         *     The values should be named without the `muc#roomconfig_` prefix.
-         * @param {boolean} [attrs.minimized] A boolean, indicating whether the room should be opened minimized or not.
-         * @param {boolean} [attrs.bring_to_foreground] A boolean indicating whether the room should be
-         *     brought to the foreground and therefore replace the currently shown chat.
-         *     If there is no chat currently open, then this option is ineffective.
-         * @param {boolean} [force=false] - By default, a minimized
-         *   room won't be maximized (in `overlayed` view mode) and in
-         *   `fullscreen` view mode a newly opened room won't replace
-         *   another chat already in the foreground.
-         *   Set `force` to `true` if you want to force the room to be
-         *   maximized or shown.
-         * @returns {Promise<MUC[]>} Promise which resolves with the Model representing the chat.
-         *
-         * @example
-         * api.rooms.open('group@muc.example.com')
-         *
-         * @example
-         * // To return an array of rooms, provide an array of room JIDs:
-         * api.rooms.open(['group1@muc.example.com', 'group2@muc.example.com'])
-         *
-         * @example
-         * // To setup a custom nickname when joining the room, provide the optional nick argument:
-         * api.rooms.open('group@muc.example.com', {'nick': 'mycustomnick'})
-         *
-         * @example
-         * // For example, opening a room with a specific default configuration:
-         * api.rooms.open(
-         *     'myroom@conference.example.org',
-         *     { 'nick': 'coolguy69',
-         *       'auto_configure': true,
-         *       'roomconfig': {
-         *           'changesubject': false,
-         *           'membersonly': true,
-         *           'persistentroom': true,
-         *           'publicroom': true,
-         *           'roomdesc': 'Comfy room for hanging out',
-         *           'whois': 'anyone'
-         *       }
-         *     }
-         * );
-         */
-        async open (jids, attrs = {}, force = false) {
-            await api.waitUntil('chatBoxesFetched');
-            if (jids === undefined) {
-                const err_msg = 'rooms.open: You need to provide at least one JID';
-                log.error(err_msg);
-                throw new TypeError(err_msg);
-            } else if (typeof jids === 'string') {
-                const room = await api.rooms.get(jids, attrs, true);
-                !attrs.hidden && room?.maybeShow(force);
-                return room;
-            } else {
-                const rooms = await Promise.all(jids.map(jid => api.rooms.get(jid, attrs, true)));
-                rooms.forEach(r => !attrs.hidden && r.maybeShow(force));
-                return rooms;
-            }
-        },
+    /**
+     * Opens a MUC chatroom (aka groupchat)
+     *
+     * Similar to {@link api.chats.open}, but for groupchats.
+     *
+     * @method api.rooms.open
+     * @param {string|string[]} jids The room JID or JIDs (if not specified, all
+     *     currently open rooms will be returned).
+     * @param {object} attrs A map  containing any extra room attributes.
+     * @param {string} [attrs.nick] The current user's nickname for the MUC
+     * @param {boolean} [attrs.hidden]
+     * @param {boolean} [attrs.auto_configure] A boolean, indicating
+     *     whether the room should be configured automatically or not.
+     *     If set to `true`, then it makes sense to pass in configuration settings.
+     * @param {object} [attrs.roomconfig] A map of configuration settings to be used when the room gets
+     *     configured automatically. Currently it doesn't make sense to specify
+     *     `roomconfig` values if `auto_configure` is set to `false`.
+     *     For a list of configuration values that can be passed in, refer to these values
+     *     in the [XEP-0045 MUC specification](https://xmpp.org/extensions/xep-0045.html#registrar-formtype-owner).
+     *     The values should be named without the `muc#roomconfig_` prefix.
+     * @param {boolean} [attrs.minimized] A boolean, indicating whether the room should be opened minimized or not.
+     * @param {boolean} [attrs.bring_to_foreground] A boolean indicating whether the room should be
+     *     brought to the foreground and therefore replace the currently shown chat.
+     *     If there is no chat currently open, then this option is ineffective.
+     * @param {boolean} [force=false] - By default, a minimized
+     *   room won't be maximized (in `overlayed` view mode) and in
+     *   `fullscreen` view mode a newly opened room won't replace
+     *   another chat already in the foreground.
+     *   Set `force` to `true` if you want to force the room to be
+     *   maximized or shown.
+     * @returns {Promise<MUC[]|MUC>} Promise which resolves with the Model representing the chat.
+     *
+     * @example
+     * api.rooms.open('group@muc.example.com')
+     *
+     * @example
+     * // To return an array of rooms, provide an array of room JIDs:
+     * api.rooms.open(['group1@muc.example.com', 'group2@muc.example.com'])
+     *
+     * @example
+     * // To setup a custom nickname when joining the room, provide the optional nick argument:
+     * api.rooms.open('group@muc.example.com', {'nick': 'mycustomnick'})
+     *
+     * @example
+     * // For example, opening a room with a specific default configuration:
+     * api.rooms.open(
+     *     'myroom@conference.example.org',
+     *     { 'nick': 'coolguy69',
+     *       'auto_configure': true,
+     *       'roomconfig': {
+     *           'changesubject': false,
+     *           'membersonly': true,
+     *           'persistentroom': true,
+     *           'publicroom': true,
+     *           'roomdesc': 'Comfy room for hanging out',
+     *           'whois': 'anyone'
+     *       }
+     *     }
+     * );
+     */
+    async open (jids, attrs = {}, force = false) {
+        await waitUntil('chatBoxesFetched');
+        if (jids === undefined) {
+            const err_msg = 'rooms.open: You need to provide at least one JID';
+            log.error(err_msg);
+            throw new TypeError(err_msg);
+        } else if (typeof jids === 'string') {
+            const room = /** @type {MUC} */(await rooms.get(jids, attrs, true));
+            !attrs.hidden && room?.maybeShow(force);
+            return room;
+        } else {
+            const rooms = await Promise.all(jids.map((jid) => rooms.get(jid, attrs, true)));
+            rooms.forEach((r) => !attrs.hidden && r.maybeShow(force));
+            return rooms;
+        }
+    },
 
-        /**
-         * Fetches the object representing a MUC chatroom (aka groupchat)
-         *
-         * @method api.rooms.get
-         * @param {string|string[]} [jids] The room JID (if not specified, all rooms will be returned).
-         * @param {object} [attrs] A map containing any extra room attributes
-         *  to be set if `create` is set to `true`
-         * @param {string} [attrs.nick] Specify the nickname
-         * @param {string} [attrs.password ] Specify a password if needed to enter a new room
-         * @param {boolean} create A boolean indicating whether the room should be created
-         *     if not found (default: `false`)
-         * @returns {Promise<MUC[]>}
-         * @example
-         * api.waitUntil('roomsAutoJoined').then(() => {
-         *     const create_if_not_found = true;
-         *     api.rooms.get(
-         *         'group@muc.example.com',
-         *         {'nick': 'dread-pirate-roberts', 'password': 'secret'},
-         *         create_if_not_found
-         *     )
-         * });
-         */
-        async get (jids, attrs = {}, create = false) {
-            await api.waitUntil('chatBoxesFetched');
+    /**
+     * Fetches the object representing a MUC chatroom (aka groupchat)
+     *
+     * @method api.rooms.get
+     * @param {string|string[]} [jids] The room JID (if not specified, all rooms will be returned).
+     * @param {object} [attrs] A map containing any extra room attributes
+     *  to be set if `create` is set to `true`
+     * @param {string} [attrs.nick] Specify the nickname
+     * @param {string} [attrs.password ] Specify a password if needed to enter a new room
+     * @param {boolean} create A boolean indicating whether the room should be created
+     *     if not found (default: `false`)
+     * @returns {Promise<MUC[]|MUC>}
+     * @example
+     * api.waitUntil('roomsAutoJoined').then(() => {
+     *     const create_if_not_found = true;
+     *     api.rooms.get(
+     *         'group@muc.example.com',
+     *         {'nick': 'dread-pirate-roberts', 'password': 'secret'},
+     *         create_if_not_found
+     *     )
+     * });
+     */
+    async get (jids, attrs = {}, create = false) {
+        await waitUntil('chatBoxesFetched');
 
-            async function _get (jid) {
-                jid = getJIDFromURI(jid);
-                let model = await api.chatboxes.get(jid);
-                if (!model && create) {
-                    model = await api.chatboxes.create(jid, attrs, _converse.exports.MUC);
-                } else {
-                    model = model && model.get('type') === CHATROOMS_TYPE ? model : null;
-                    if (model && Object.keys(attrs).length) {
-                        model.save(attrs);
-                    }
+        /** @param {string} jid */
+        async function _get(jid) {
+            jid = getJIDFromURI(jid);
+            let model = await chatboxes.get(jid);
+            if (!model && create) {
+                model = await chatboxes.create(jid, attrs, _converse.exports.MUC);
+            } else {
+                model = model && model.get('type') === CHATROOMS_TYPE ? model : null;
+                if (model && Object.keys(attrs).length) {
+                    model.save(attrs);
                 }
-                return model;
-            }
-            if (jids === undefined) {
-                const chats = await api.chatboxes.get();
-                return chats.filter(c => c.get('type') === CHATROOMS_TYPE);
-            } else if (typeof jids === 'string') {
-                return _get(jids);
             }
-            return Promise.all(jids.map(jid => _get(jid)));
+            return model;
         }
-    }
-}
+        if (jids === undefined) {
+            const chats = await chatboxes.get();
+            return chats.filter((c) => c.get('type') === CHATROOMS_TYPE);
+        } else if (typeof jids === 'string') {
+            return _get(jids);
+        }
+        return Promise.all(jids.map((jid) => _get(jid)));
+    },
+};
+
+const rooms_api = { rooms };
+
+export default rooms_api;

+ 2 - 1
src/headless/plugins/muc/muc.js

@@ -10,7 +10,8 @@
  * @typedef {import('strophe.js/src/builder.js').Builder} Strophe.Builder
  */
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import ChatBox from '../chat/model';
 import debounce from 'lodash-es/debounce';
 import log from '../../log';

+ 2 - 1
src/headless/plugins/muc/occupants.js

@@ -4,7 +4,8 @@
 import MUCOccupant from './occupant.js';
 import _converse from '../../shared/_converse.js';
 import log from '../../log';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import { Collection, Model } from '@converse/skeletor';
 import { Strophe } from 'strophe.js';
 import { getAffiliationList } from './affiliations/utils.js';

+ 2 - 1
src/headless/plugins/muc/parsers.js

@@ -5,7 +5,8 @@
  */
 import dayjs from 'dayjs';
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import {
     StanzaParseError,
     getChatMarker,

+ 2 - 1
src/headless/plugins/muc/plugin.js

@@ -14,7 +14,8 @@ import MUCOccupants from './occupants.js';
 import affiliations_api from './affiliations/api.js';
 import muc_api from './api.js';
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import { CHATROOMS_TYPE } from '../../shared/constants.js';
 import {
     autoJoinRooms,

+ 2 - 1
src/headless/plugins/muc/utils.js

@@ -2,7 +2,8 @@
  * @typedef {import('@converse/skeletor').Model} Model
  */
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import log from '../../log.js';
 import { ROLES, MUC_ROLE_WEIGHTS } from './constants.js';
 import { safeSave } from '../../utils/index.js';

+ 2 - 1
src/headless/plugins/ping/api.js

@@ -1,5 +1,6 @@
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import log from '../../log.js';
 import { setLastStanzaDate } from './utils.js';
 

+ 2 - 1
src/headless/plugins/ping/index.js

@@ -5,7 +5,8 @@
  * @copyright 2022, the Converse.js contributors
  * @license Mozilla Public License (MPLv2)
  */
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import ping_api from './api.js';
 import { onWindowStateChanged, registerHandlers, unregisterIntervalHandler } from './utils.js';
 

+ 2 - 1
src/headless/plugins/ping/utils.js

@@ -1,4 +1,5 @@
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import { isTestEnv } from '../../utils/session.js';
 
 const { Strophe, $iq } = converse.env;

+ 2 - 1
src/headless/plugins/pubsub.js

@@ -6,7 +6,8 @@
  */
 import "./disco/index.js";
 import _converse from '../shared/_converse.js';
-import api, { converse } from '../shared/api/index.js';
+import api from '../shared/api/index.js';
+import converse from '../shared/api/public.js';
 import log from "../log.js";
 
 const { Strophe, $iq } = converse.env;

+ 2 - 1
src/headless/plugins/roster/api.js

@@ -1,5 +1,6 @@
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 
 const { Strophe } = converse.env;
 

+ 2 - 1
src/headless/plugins/roster/contact.js

@@ -1,6 +1,7 @@
 import '../../plugins/status/api.js';
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import { Model } from '@converse/skeletor';
 import { getOpenPromise } from '@converse/openpromise';
 import { rejectPresenceSubscription } from './utils.js';

+ 2 - 1
src/headless/plugins/roster/contacts.js

@@ -1,6 +1,7 @@
 import RosterContact from './contact.js';
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import log from "../../log.js";
 import { Collection, Model } from "@converse/skeletor";
 import { initStorage } from '../../utils/storage.js';

+ 2 - 1
src/headless/plugins/roster/plugin.js

@@ -6,7 +6,8 @@ import '../../plugins/status/index.js';
 import RosterContact from './contact.js';
 import RosterContacts from './contacts.js';
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import roster_api from './api.js';
 import Presence from './presence.js';
 import Presences from './presences.js';

+ 1 - 1
src/headless/plugins/roster/presence.js

@@ -1,6 +1,6 @@
 import Resources from "./resources.js";
 import { Model } from '@converse/skeletor';
-import { converse } from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import { initStorage } from '../../utils/storage.js';
 
 const { Strophe, dayjs, sizzle } = converse.env;

+ 3 - 2
src/headless/plugins/roster/utils.js

@@ -2,7 +2,8 @@
  * @typedef {import('./contacts').default} RosterContacts
  */
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import log from "../../log.js";
 import { Strophe } from 'strophe.js';
 import { Model } from '@converse/skeletor';
@@ -109,7 +110,7 @@ async function clearPresences () {
  */
 export async function onClearSession () {
     await clearPresences();
-    if (shouldClearCache()) {
+    if (shouldClearCache(_converse)) {
         const roster = /** @type {RosterContacts} */(_converse.state.roster);
         if (roster) {
             roster.data?.destroy();

+ 2 - 1
src/headless/plugins/smacks/index.js

@@ -3,7 +3,8 @@
  * @license Mozilla Public License (MPLv2)
  * @description Converse.js plugin which adds support for XEP-0198: Stream Management
  */
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import { enableStreamManagement, initSessionData, sendEnableStanza, onStanzaSent } from './utils.js';
 
 const { Strophe } = converse.env;

+ 2 - 1
src/headless/plugins/smacks/utils.js

@@ -1,5 +1,6 @@
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import log from '../../log.js';
 import { getOpenPromise } from '@converse/openpromise';
 import { isTestEnv } from '../../utils/session.js';

+ 3 - 2
src/headless/plugins/status/plugin.js

@@ -1,6 +1,7 @@
 import XMPPStatus from './status.js';
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import status_api from './api.js';
 import { shouldClearCache } from '../../utils/session.js';
 import {
@@ -45,7 +46,7 @@ converse.plugins.add('converse-status', {
         api.listen.on('beforeTearDown', tearDown);
 
         api.listen.on('clearSession', () => {
-            if (shouldClearCache() && _converse.state.xmppstatus) {
+            if (shouldClearCache(_converse) && _converse.state.xmppstatus) {
                 _converse.state.xmppstatus.destroy();
                 delete _converse.state.xmppstatus;
                 Object.assign(_converse, { xmppstatus: undefined }); // XXX DEPRECATED

+ 2 - 1
src/headless/plugins/status/status.js

@@ -1,5 +1,6 @@
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import { Model } from '@converse/skeletor';
 import { isIdle, getIdleSeconds } from './utils.js';
 

+ 2 - 1
src/headless/plugins/status/utils.js

@@ -1,5 +1,6 @@
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import { initStorage } from '../../utils/storage.js';
 import { getUnloadEvent } from '../../utils/session.js';
 import { ACTIVE, INACTIVE } from '../../shared/constants.js';

+ 2 - 1
src/headless/plugins/vcard/api.js

@@ -3,7 +3,8 @@
  */
 import log from "../../log.js";
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from "../../shared/api/public.js";
 import { createStanza, getVCard } from './utils.js';
 
 const { dayjs, u } = converse.env;

+ 2 - 1
src/headless/plugins/vcard/plugin.js

@@ -5,7 +5,8 @@
 import "../status/index.js";
 import VCard from './vcard.js';
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import vcard_api from './api.js';
 import VCards from "./vcards";
 import {

+ 3 - 2
src/headless/plugins/vcard/utils.js

@@ -6,7 +6,8 @@
  * @typedef {import('../muc/occupant.js').default} MUCOccupant
  */
 import _converse from '../../shared/_converse.js';
-import api, { converse } from '../../shared/api/index.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
 import log from "../../log.js";
 import { initStorage } from '../../utils/storage.js';
 import { shouldClearCache } from '../../utils/session.js';
@@ -203,7 +204,7 @@ export async function initVCardCollection () {
 
 
 export function clearVCardsSession () {
-    if (shouldClearCache()) {
+    if (shouldClearCache(_converse)) {
         api.promises.add('VCardsInitialized');
         if (_converse.state.vcards) {
             _converse.state.vcards.clearStore();

+ 1 - 1
src/headless/plugins/vcard/vcards.js

@@ -1,6 +1,6 @@
 import VCard from "./vcard";
+import api from "./api.js";
 import { Collection } from "@converse/skeletor";
-import { api } from "../../index.js";
 
 class VCards extends Collection {
 

+ 1 - 1
src/headless/shared/_converse.js

@@ -1,6 +1,6 @@
 /**
  * @module:shared.converse
- * @typedef {import('@converse/skeletor/src/storage').Storage} Storage
+ * @typedef {import('@converse/skeletor').Storage} Storage
  * @typedef {import('@converse/skeletor').Collection} Collection
  * @typedef {import('../plugins/disco/index').DiscoState} DiscoState
  * @typedef {import('../plugins/status/status').default} XMPPStatus

+ 2 - 1
src/headless/shared/actions.js

@@ -1,6 +1,7 @@
 import log from '../log';
 import { Strophe, $msg } from 'strophe.js';
-import api, { converse } from './api/index.js';
+import api from './api/index.js';
+import converse from './api/public.js';
 
 const u = converse.env.utils;
 

+ 0 - 2
src/headless/shared/api/index.js

@@ -40,5 +40,3 @@ const api = {
 };
 
 export default api;
-
-export { converse } from './public.js';

+ 21 - 11
src/headless/shared/api/presence.js

@@ -1,9 +1,16 @@
 /**
- * @typedef {import('strophe.js/src/builder.js').Builder} Strophe.Builder
+ * @typedef {import('strophe.js').Builder} Builder
  * @typedef {import('../../plugins/status/status').default} XMPPStatus
+ * @typedef {import('../../plugins/muc/muc.js').default} MUC
  */
 import _converse from '../_converse.js';
-import api from '../../shared/api/index.js';
+import promise_api from './promise.js';
+import send_api from './send.js';
+import rooms_api from '../../plugins/muc/api.js';
+
+const { waitUntil } = promise_api;
+const { send } = send_api;
+const { rooms } = rooms_api;
 
 export default {
     /**
@@ -17,22 +24,25 @@ export default {
          * @param {String} [type]
          * @param {String} [to]
          * @param {String} [status] - An optional status message
-         * @param {Array<Element>|Array<Strophe.Builder>|Element|Strophe.Builder} [child_nodes]
+         * @param {Array<Element>|Array<Builder>|Element|Builder} [nodes]
          *  Nodes(s) to be added as child nodes of the `presence` XML element.
          */
-        async send (type, to, status, child_nodes) {
-            await api.waitUntil('statusInitialized');
-            if (child_nodes && !Array.isArray(child_nodes)) {
-                child_nodes = [child_nodes];
+        async send (type, to, status, nodes) {
+            await waitUntil('statusInitialized');
+
+            let children = [];
+            if (nodes) {
+                children = Array.isArray(nodes) ? nodes : [nodes];
             }
+
             const model = /** @type {XMPPStatus} */(_converse.state.xmppstatus);
             const presence = await model.constructPresence(type, to, status);
-            child_nodes?.map(c => c?.tree() ?? c).forEach(c => presence.cnode(c).up());
-            api.send(presence);
+            children.map(c => c?.tree() ?? c).forEach(c => presence.cnode(c).up());
+            send(presence);
 
             if (['away', 'chat', 'dnd', 'online', 'xa', undefined].includes(type)) {
-                const mucs = await api.rooms.get();
-                mucs.forEach(muc => muc.sendStatusPresence(type, status, child_nodes));
+                const mucs = /** @type {MUC[]} */(await rooms.get());
+                mucs.forEach(muc => muc.sendStatusPresence(type, status, children));
             }
         }
     }

+ 10 - 4
src/headless/shared/api/public.js

@@ -1,13 +1,15 @@
 /**
  * @typedef {module:shared-api-public.ConversePrivateGlobal} ConversePrivateGlobal
  */
-import ConnectionFeedback from './../connection/feedback.js';
+import dayjs from 'dayjs';
+import sizzle from 'sizzle';
 import URI from 'urijs';
+
+import api from './index.js';
 import _converse from '../_converse.js';
-import dayjs from 'dayjs';
 import i18n from '../i18n';
 import log from '../../log.js';
-import sizzle from 'sizzle';
+import ConnectionFeedback from './../connection/feedback.js';
 import u, { setLogLevelFromRoute } from '../../utils/index.js';
 import { ANONYMOUS, CHAT_STATES, KEYCODES, VERSION_NAME } from '../constants.js';
 import { isTestEnv } from '../../utils/session.js';
@@ -19,6 +21,8 @@ import { html } from 'lit';
 import { initAppSettings } from '../settings/utils.js';
 import { sprintf } from 'sprintf-js';
 
+_converse.api = api;
+
 import {
     cleanup,
     initClientConfig,
@@ -42,7 +46,7 @@ import {
  * @global
  * @namespace converse
  */
-export const converse = Object.assign(/** @type {ConversePrivateGlobal} */(window).converse || {}, {
+const converse = Object.assign(/** @type {ConversePrivateGlobal} */(window).converse || {}, {
 
     CHAT_STATES,
 
@@ -200,3 +204,5 @@ export const converse = Object.assign(/** @type {ConversePrivateGlobal} */(windo
         u,
     }
 });
+
+export default converse;

+ 2 - 2
src/headless/shared/api/user.js

@@ -7,7 +7,7 @@ import connection_api from '../connection/api.js';
 import { replacePromise } from '../../utils/session.js';
 import { attemptNonPreboundSession, setUserJID } from '../../utils/init.js';
 import { getOpenPromise } from '@converse/openpromise';
-import { user_settings_api } from '../settings/api.js';
+import { user_settings_api } from '../settings/user/api.js';
 import { LOGOUT } from '../constants.js';
 
 const api = {
@@ -106,7 +106,7 @@ const api = {
             const promise = getOpenPromise();
             const complete = () => {
                 // Recreate all the promises
-                Object.keys(_converse.promises).forEach(replacePromise);
+                Object.keys(_converse.promises).forEach((p) => replacePromise(_converse, p));
 
                 // Remove the session JID, otherwise the user would just be logged
                 // in again upon reload. See #2759

+ 2 - 1
src/headless/shared/chat/utils.js

@@ -7,7 +7,8 @@
  * @typedef {module:headless-shared-parsers.MediaURLMetadata} MediaURLMetadata
  */
 import debounce from 'lodash-es/debounce.js';
-import api, { converse } from '../../shared/api/index.js';
+import converse from '../api/public.js';
+import api from '../api/index.js';
 
 const { u } = converse.env;
 

+ 18 - 9
src/headless/shared/connection/index.js

@@ -2,7 +2,6 @@ import debounce from 'lodash-es/debounce';
 import log from "../../log.js";
 import sizzle from 'sizzle';
 import _converse from '../_converse.js';
-import api from '../api/index.js';
 import { ANONYMOUS, BOSH_WAIT, LOGOUT } from '../../shared/constants.js';
 import { CONNECTION_STATUS } from '../constants';
 import { Strophe } from 'strophe.js';
@@ -31,11 +30,8 @@ export class Connection extends Strophe.Connection {
         this.debouncedReconnect = debounce(this.reconnect, 3000);
     }
 
-    static generateResource () {
-        return `/converse.js-${Math.floor(Math.random()*139749528).toString()}`;
-    }
-
     async bind () {
+        const { api } = _converse;
         /**
          * Synchronous event triggered before we send an IQ to bind the user's
          * JID resource for this session.
@@ -47,6 +43,7 @@ export class Connection extends Strophe.Connection {
 
 
     async onDomainDiscovered (response) {
+        const { api } = _converse;
         const text = await response.text();
         const xrd = (new window.DOMParser()).parseFromString(text, "text/xml").firstElementChild;
         if (xrd.nodeName != "XRD" || xrd.namespaceURI != "http://docs.oasis-open.org/ns/xri/xrd-1.0") {
@@ -108,6 +105,8 @@ export class Connection extends Strophe.Connection {
      * @param {Function} callback
      */
     async connect (jid, password, callback) {
+        const { api } = _converse;
+
         if (api.settings.get("discover_connection_methods")) {
             const domain = Strophe.getDomainFromJid(jid);
             await this.discoverConnectionMethods(domain);
@@ -137,6 +136,8 @@ export class Connection extends Strophe.Connection {
      * for the old transport are removed.
      */
     async switchTransport () {
+        const { api } = _converse;
+
         const bare_jid = _converse.session.get('bare_jid');
         if (api.connection.isType('websocket') && api.settings.get('bosh_service_url')) {
             await setUserJID(bare_jid);
@@ -159,9 +160,11 @@ export class Connection extends Strophe.Connection {
     }
 
     async reconnect () {
+        const { api } = _converse;
+
         log.debug('RECONNECTING: the connection has dropped, attempting to reconnect.');
         this.reconnecting = true;
-        await tearDown();
+        await tearDown(_converse);
 
         const conn_status = _converse.state.connfeedback.get('connection_status');
         if (conn_status === Strophe.Status.CONNFAIL) {
@@ -181,7 +184,7 @@ export class Connection extends Strophe.Connection {
         api.trigger('will-reconnect');
 
         if (api.settings.get("authentication") === ANONYMOUS) {
-            await clearSession();
+            await clearSession(_converse);
         }
         const jid = _converse.session.get('jid');
         return api.user.login(jid);
@@ -194,6 +197,8 @@ export class Connection extends Strophe.Connection {
      * @param {Boolean} [reconnecting] - Whether Converse.js reconnected from an earlier dropped session.
      */
     async onConnected (reconnecting) {
+        const { api } = _converse;
+
         delete this.reconnecting;
         this.flush(); // Solves problem of returned PubSub BOSH response not received by browser
         await setUserJID(this.jid);
@@ -261,12 +266,13 @@ export class Connection extends Strophe.Connection {
     }
 
     async finishDisconnection () {
+        const { api } = _converse;
         // Properly tear down the session so that it's possible to manually connect again.
         log.debug('DISCONNECTED');
         delete this.reconnecting;
         this.reset();
-        tearDown();
-        await clearSession();
+        tearDown(_converse);
+        await clearSession(_converse);
         api.connection.destroy();
 
         /**
@@ -285,6 +291,7 @@ export class Connection extends Strophe.Connection {
      * @method onDisconnected
      */
     onDisconnected () {
+        const { api } = _converse;
         if (api.settings.get("auto_reconnect")) {
             const reason = this.disconnection_reason;
             if (this.disconnection_cause === Strophe.Status.AUTHFAIL) {
@@ -406,6 +413,7 @@ export class Connection extends Strophe.Connection {
     }
 
     hasResumed () {
+        const { api } = _converse;
         if (api.settings.get("connection_options")?.worker || this.isType('bosh')) {
             return _converse.state.connfeedback.get('connection_status') === Strophe.Status.ATTACHED;
         } else {
@@ -482,6 +490,7 @@ export class MockConnection extends Connection {
     }
 
     async bind () {
+        const { api } = _converse;
         await api.trigger('beforeResourceBinding', {'synchronous': true});
         this.authenticated = true;
         this._changeConnectStatus(Strophe.Status.CONNECTED);

+ 4 - 0
src/headless/shared/connection/utils.js

@@ -2,6 +2,10 @@ import log from '../../log.js';
 import { Strophe } from 'strophe.js';
 import { settings_api } from '../settings/api.js';
 
+export function generateResource () {
+    return `/converse.js-${Math.floor(Math.random() * 139749528).toString()}`;
+}
+
 export function setUpXMLLogging (connection) {
     const lmap = {};
     lmap[Strophe.LogLevel.DEBUG] = 'debug';

+ 1 - 1
src/headless/shared/i18n.js

@@ -5,7 +5,7 @@ import { sprintf } from 'sprintf-js';
  */
 const i18nStub = {
     // eslint-disable-next-line @typescript-eslint/no-empty-function
-    initialize () {},
+    async initialize () {},
 
     /**
      * Overridable string wrapper method which can be used to provide i18n

+ 1 - 1
src/headless/shared/rsm.js

@@ -6,7 +6,7 @@
  *   Some code taken from the Strophe RSM plugin, licensed under the MIT License
  *   Copyright 2006-2017 Strophe (https://github.com/strophe/strophejs)
  */
-import { converse } from './api/index.js';
+import converse from './api/public.js';
 import pick from 'lodash-es/pick';
 
 const { Strophe, $build } = converse.env;

+ 5 - 71
src/headless/shared/settings/api.js

@@ -3,14 +3,12 @@
  */
 import log from '../../log.js';
 import {
-    clearUserSettings,
     extendAppSettings,
     getAppSetting,
-    getUserSettings,
+    getAppSettings,
     registerListener,
     unregisterListener,
     updateAppSettings,
-    updateUserSettings,
 } from './utils.js';
 
 /**
@@ -57,11 +55,13 @@ export const settings_api = {
 
     /**
      * @method _converse.api.settings.get
-     * @returns {*} Value of the particular configuration setting.
+     * @param {string} [key]
+     * @returns {*} Value of the particular configuration setting, or all
+     *  settings if no key was specified.
      * @example api.settings.get("play_sounds");
      */
     get (key) {
-        return getAppSetting(key);
+        return key ? getAppSetting(key) : getAppSettings();
     },
 
     /**
@@ -121,69 +121,3 @@ export const settings_api = {
         }
     }
 };
-
-
-/**
- * API for accessing and setting user settings. User settings are
- * different from the application settings from {@link _converse.api.settings}
- * because they are per-user and set via user action.
- * @namespace _converse.api.user.settings
- * @memberOf _converse.api.user
- */
-export const user_settings_api = {
-    /**
-     * Returns the user settings model. Useful when you want to listen for change events.
-     * @async
-     * @method _converse.api.user.settings.getModel
-     * @returns {Promise<Model>}
-     * @example const settings = await api.user.settings.getModel();
-     */
-    getModel () {
-        return getUserSettings();
-    },
-
-    /**
-     * Get the value of a particular user setting.
-     * @method _converse.api.user.settings.get
-     * @param {string} key - The setting name
-     * @param {*} [fallback] - An optional fallback value if the user setting is undefined
-     * @returns {Promise} Promise which resolves with the value of the particular configuration setting.
-     * @example api.user.settings.get("foo");
-     */
-    async get (key, fallback) {
-        const user_settings = await getUserSettings();
-        return user_settings.get(key) === undefined ? fallback : user_settings.get(key);
-    },
-
-    /**
-     * Set one or many user settings.
-     * @async
-     * @method _converse.api.user.settings.set
-     * @param {Object|string} key An object containing config settings or alternatively a string key
-     * @param {string} [val] The value, if the previous parameter is a key
-     * @example api.user.settings.set("foo", "bar");
-     * @example
-     * api.user.settings.set({
-     *     "foo": "bar",
-     *     "baz": "buz"
-     * });
-     */
-    set (key, val) {
-        if (key instanceof Object) {
-            return updateUserSettings(key, {'promise': true});
-        } else {
-            const o = {};
-            o[key] = val;
-            return updateUserSettings(o, {'promise': true});
-        }
-    },
-
-    /**
-     * Clears all the user settings
-     * @async
-     * @method api.user.settings.clear
-     */
-    clear () {
-        return clearUserSettings();
-    }
-}

+ 73 - 0
src/headless/shared/settings/user/api.js

@@ -0,0 +1,73 @@
+/**
+ * @typedef {import('@converse/skeletor').Model} Model
+ */
+import {
+    clearUserSettings,
+    getUserSettings,
+    updateUserSettings,
+} from './utils.js';
+
+/**
+ * API for accessing and setting user settings. User settings are
+ * different from the application settings from {@link _converse.api.settings}
+ * because they are per-user and set via user action.
+ * @namespace _converse.api.user.settings
+ * @memberOf _converse.api.user
+ */
+export const user_settings_api = {
+    /**
+     * Returns the user settings model. Useful when you want to listen for change events.
+     * @async
+     * @method _converse.api.user.settings.getModel
+     * @returns {Promise<Model>}
+     * @example const settings = await api.user.settings.getModel();
+     */
+    getModel () {
+        return getUserSettings();
+    },
+
+    /**
+     * Get the value of a particular user setting.
+     * @method _converse.api.user.settings.get
+     * @param {string} key - The setting name
+     * @param {*} [fallback] - An optional fallback value if the user setting is undefined
+     * @returns {Promise} Promise which resolves with the value of the particular configuration setting.
+     * @example api.user.settings.get("foo");
+     */
+    async get (key, fallback) {
+        const user_settings = await getUserSettings();
+        return user_settings.get(key) === undefined ? fallback : user_settings.get(key);
+    },
+
+    /**
+     * Set one or many user settings.
+     * @async
+     * @method _converse.api.user.settings.set
+     * @param {Object|string} key An object containing config settings or alternatively a string key
+     * @param {string} [val] The value, if the previous parameter is a key
+     * @example api.user.settings.set("foo", "bar");
+     * @example
+     * api.user.settings.set({
+     *     "foo": "bar",
+     *     "baz": "buz"
+     * });
+     */
+    set (key, val) {
+        if (key instanceof Object) {
+            return updateUserSettings(key, {'promise': true});
+        } else {
+            const o = {};
+            o[key] = val;
+            return updateUserSettings(o, {'promise': true});
+        }
+    },
+
+    /**
+     * Clears all the user settings
+     * @async
+     * @method api.user.settings.clear
+     */
+    clear () {
+        return clearUserSettings();
+    }
+}

+ 44 - 0
src/headless/shared/settings/user/utils.js

@@ -0,0 +1,44 @@
+import _converse from '../../_converse.js';
+import log from '../../../log.js';
+import { Model } from '@converse/skeletor';
+import { initStorage } from '../../../utils/storage.js';
+
+let user_settings; // User settings, populated via api.users.settings
+
+/**
+ * @returns {Promise<void>|void} A promise when the user settings object
+ *  is created anew and it's contents fetched from storage.
+ */
+function initUserSettings () {
+    const bare_jid = _converse.session.get('bare_jid');
+    if (!bare_jid) {
+        const msg = "No JID to fetch user settings for";
+        log.error(msg);
+        throw Error(msg);
+    }
+    const id = `converse.user-settings.${bare_jid}`;
+    if (user_settings?.get('id') !== id) {
+        user_settings = new Model({id});
+        initStorage(user_settings, id);
+        return user_settings.fetch({'promise': true});
+    }
+}
+
+export async function getUserSettings () {
+    await initUserSettings();
+    return user_settings;
+}
+
+export async function updateUserSettings (data, options) {
+    await initUserSettings();
+    return user_settings.save(data, options);
+}
+
+export async function clearUserSettings () {
+    const bare_jid = _converse.session.get('bare_jid');
+    if (bare_jid) {
+        await initUserSettings();
+        return user_settings.clear();
+    }
+    user_settings = undefined;
+}

+ 1 - 44
src/headless/shared/settings/utils.js

@@ -1,17 +1,12 @@
-import EventEmitter from '@converse/skeletor/src/eventemitter.js';
-import _converse from '../_converse.js';
 import isEqual from "lodash-es/isEqual.js";
-import log from '../../log.js';
 import pick from 'lodash-es/pick';
 import { DEFAULT_SETTINGS } from './constants.js';
-import { Model } from '@converse/skeletor';
-import { initStorage } from '../../utils/storage.js';
+import { EventEmitter } from '@converse/skeletor';
 import { merge } from '../../utils/object.js';
 
 
 let app_settings;
 let init_settings = {}; // Container for settings passed in via converse.initialize
-let user_settings; // User settings, populated via api.users.settings
 
 class AppSettings extends EventEmitter(Object) {}
 
@@ -93,41 +88,3 @@ export function updateAppSettings (key, val) {
     Object.keys(changed).forEach(k => app_settings.trigger('change:' + k, changed[k]));
     app_settings.trigger('change', changed);
 }
-
-/**
- * @returns {Promise<void>|void} A promise when the user settings object
- *  is created anew and it's contents fetched from storage.
- */
-function initUserSettings () {
-    const bare_jid = _converse.session.get('bare_jid');
-    if (!bare_jid) {
-        const msg = "No JID to fetch user settings for";
-        log.error(msg);
-        throw Error(msg);
-    }
-    const id = `converse.user-settings.${bare_jid}`;
-    if (user_settings?.get('id') !== id) {
-        user_settings = new Model({id});
-        initStorage(user_settings, id);
-        return user_settings.fetch({'promise': true});
-    }
-}
-
-export async function getUserSettings () {
-    await initUserSettings();
-    return user_settings;
-}
-
-export async function updateUserSettings (data, options) {
-    await initUserSettings();
-    return user_settings.save(data, options);
-}
-
-export async function clearUserSettings () {
-    const bare_jid = _converse.session.get('bare_jid');
-    if (bare_jid) {
-        await initUserSettings();
-        return user_settings.clear();
-    }
-    user_settings = undefined;
-}

+ 1 - 1
src/headless/types/index.d.ts

@@ -1,7 +1,7 @@
 export { XMPPStatus } from "./plugins/status/index.js";
 export default converse;
 import api from "./shared/api/index.js";
-import { converse } from "./shared/api/public.js";
+import converse from "./shared/api/public.js";
 import _converse from "./shared/_converse";
 import i18n from "./shared/i18n";
 import log from "./log.js";

+ 2 - 2
src/headless/types/plugins/chatboxes/api.d.ts

@@ -8,9 +8,9 @@ declare namespace _default {
     function create(jids: string | string[], attrs: any, model: new (attrs: any, options: any) => import("../chat/model.js").default): Promise<import("../chat/model.js").default | import("../chat/model.js").default[]>;
     /**
      * @method api.chatboxes.get
-     * @param {string|string[]} jids - A JID or array of JIDs
+     * @param {string|string[]} [jids] - A JID or array of JIDs
      */
-    function get(jids: string | string[]): Promise<any>;
+    function get(jids?: string | string[]): Promise<any>;
     namespace registry {
         /**
          * @method api.chatboxes.registry.add

+ 113 - 112
src/headless/types/plugins/muc/api.d.ts

@@ -1,114 +1,115 @@
-declare namespace _default {
-    namespace rooms {
-        /**
-         * Creates a new MUC chatroom (aka groupchat)
-         *
-         * Similar to {@link api.rooms.open}, but creates
-         * the chatroom in the background (i.e. doesn't cause a view to open).
-         *
-         * @method api.rooms.create
-         * @param {(string[]|string)} jids The JID or array of
-         *     JIDs of the chatroom(s) to create
-         * @param {object} [attrs] attrs The room attributes
-         * @returns {Promise[]} Promise which resolves with the Model representing the chat.
-         */
-        function create(jids: string | string[], attrs?: any): Promise<any>[];
-        /**
-         * Opens a MUC chatroom (aka groupchat)
-         *
-         * Similar to {@link api.chats.open}, but for groupchats.
-         *
-         * @method api.rooms.open
-         * @param {string|string[]} jids The room JID or JIDs (if not specified, all
-         *     currently open rooms will be returned).
-         * @param {object} attrs A map  containing any extra room attributes.
-         * @param {string} [attrs.nick] The current user's nickname for the MUC
-         * @param {boolean} [attrs.hidden]
-         * @param {boolean} [attrs.auto_configure] A boolean, indicating
-         *     whether the room should be configured automatically or not.
-         *     If set to `true`, then it makes sense to pass in configuration settings.
-         * @param {object} [attrs.roomconfig] A map of configuration settings to be used when the room gets
-         *     configured automatically. Currently it doesn't make sense to specify
-         *     `roomconfig` values if `auto_configure` is set to `false`.
-         *     For a list of configuration values that can be passed in, refer to these values
-         *     in the [XEP-0045 MUC specification](https://xmpp.org/extensions/xep-0045.html#registrar-formtype-owner).
-         *     The values should be named without the `muc#roomconfig_` prefix.
-         * @param {boolean} [attrs.minimized] A boolean, indicating whether the room should be opened minimized or not.
-         * @param {boolean} [attrs.bring_to_foreground] A boolean indicating whether the room should be
-         *     brought to the foreground and therefore replace the currently shown chat.
-         *     If there is no chat currently open, then this option is ineffective.
-         * @param {boolean} [force=false] - By default, a minimized
-         *   room won't be maximized (in `overlayed` view mode) and in
-         *   `fullscreen` view mode a newly opened room won't replace
-         *   another chat already in the foreground.
-         *   Set `force` to `true` if you want to force the room to be
-         *   maximized or shown.
-         * @returns {Promise<MUC[]>} Promise which resolves with the Model representing the chat.
-         *
-         * @example
-         * api.rooms.open('group@muc.example.com')
-         *
-         * @example
-         * // To return an array of rooms, provide an array of room JIDs:
-         * api.rooms.open(['group1@muc.example.com', 'group2@muc.example.com'])
-         *
-         * @example
-         * // To setup a custom nickname when joining the room, provide the optional nick argument:
-         * api.rooms.open('group@muc.example.com', {'nick': 'mycustomnick'})
-         *
-         * @example
-         * // For example, opening a room with a specific default configuration:
-         * api.rooms.open(
-         *     'myroom@conference.example.org',
-         *     { 'nick': 'coolguy69',
-         *       'auto_configure': true,
-         *       'roomconfig': {
-         *           'changesubject': false,
-         *           'membersonly': true,
-         *           'persistentroom': true,
-         *           'publicroom': true,
-         *           'roomdesc': 'Comfy room for hanging out',
-         *           'whois': 'anyone'
-         *       }
-         *     }
-         * );
-         */
-        function open(jids: string | string[], attrs?: {
-            nick?: string;
-            hidden?: boolean;
-            auto_configure?: boolean;
-            roomconfig?: any;
-            minimized?: boolean;
-            bring_to_foreground?: boolean;
-        }, force?: boolean): Promise<import("./muc.js").default[]>;
-        /**
-         * Fetches the object representing a MUC chatroom (aka groupchat)
-         *
-         * @method api.rooms.get
-         * @param {string|string[]} [jids] The room JID (if not specified, all rooms will be returned).
-         * @param {object} [attrs] A map containing any extra room attributes
-         *  to be set if `create` is set to `true`
-         * @param {string} [attrs.nick] Specify the nickname
-         * @param {string} [attrs.password ] Specify a password if needed to enter a new room
-         * @param {boolean} create A boolean indicating whether the room should be created
-         *     if not found (default: `false`)
-         * @returns {Promise<MUC[]>}
-         * @example
-         * api.waitUntil('roomsAutoJoined').then(() => {
-         *     const create_if_not_found = true;
-         *     api.rooms.get(
-         *         'group@muc.example.com',
-         *         {'nick': 'dread-pirate-roberts', 'password': 'secret'},
-         *         create_if_not_found
-         *     )
-         * });
-         */
-        function get(jids?: string | string[], attrs?: {
-            nick?: string;
-            password?: string;
-        }, create?: boolean): Promise<import("./muc.js").default[]>;
-    }
-}
-export default _default;
+export default rooms_api;
 export type MUC = import('./muc.js').default;
+declare namespace rooms_api {
+    export { rooms };
+}
+declare namespace rooms {
+    /**
+     * Creates a new MUC chatroom (aka groupchat)
+     *
+     * Similar to {@link api.rooms.open}, but creates
+     * the chatroom in the background (i.e. doesn't cause a view to open).
+     *
+     * @method api.rooms.create
+     * @param {(string[]|string)} jids The JID or array of
+     *     JIDs of the chatroom(s) to create
+     * @param {object} [attrs] attrs The room attributes
+     * @returns {Promise<MUC[]|MUC>} Promise which resolves with the Model representing the chat.
+     */
+    function create(jids: string | string[], attrs?: any): Promise<import("./muc.js").default | import("./muc.js").default[]>;
+    /**
+     * Opens a MUC chatroom (aka groupchat)
+     *
+     * Similar to {@link api.chats.open}, but for groupchats.
+     *
+     * @method api.rooms.open
+     * @param {string|string[]} jids The room JID or JIDs (if not specified, all
+     *     currently open rooms will be returned).
+     * @param {object} attrs A map  containing any extra room attributes.
+     * @param {string} [attrs.nick] The current user's nickname for the MUC
+     * @param {boolean} [attrs.hidden]
+     * @param {boolean} [attrs.auto_configure] A boolean, indicating
+     *     whether the room should be configured automatically or not.
+     *     If set to `true`, then it makes sense to pass in configuration settings.
+     * @param {object} [attrs.roomconfig] A map of configuration settings to be used when the room gets
+     *     configured automatically. Currently it doesn't make sense to specify
+     *     `roomconfig` values if `auto_configure` is set to `false`.
+     *     For a list of configuration values that can be passed in, refer to these values
+     *     in the [XEP-0045 MUC specification](https://xmpp.org/extensions/xep-0045.html#registrar-formtype-owner).
+     *     The values should be named without the `muc#roomconfig_` prefix.
+     * @param {boolean} [attrs.minimized] A boolean, indicating whether the room should be opened minimized or not.
+     * @param {boolean} [attrs.bring_to_foreground] A boolean indicating whether the room should be
+     *     brought to the foreground and therefore replace the currently shown chat.
+     *     If there is no chat currently open, then this option is ineffective.
+     * @param {boolean} [force=false] - By default, a minimized
+     *   room won't be maximized (in `overlayed` view mode) and in
+     *   `fullscreen` view mode a newly opened room won't replace
+     *   another chat already in the foreground.
+     *   Set `force` to `true` if you want to force the room to be
+     *   maximized or shown.
+     * @returns {Promise<MUC[]|MUC>} Promise which resolves with the Model representing the chat.
+     *
+     * @example
+     * api.rooms.open('group@muc.example.com')
+     *
+     * @example
+     * // To return an array of rooms, provide an array of room JIDs:
+     * api.rooms.open(['group1@muc.example.com', 'group2@muc.example.com'])
+     *
+     * @example
+     * // To setup a custom nickname when joining the room, provide the optional nick argument:
+     * api.rooms.open('group@muc.example.com', {'nick': 'mycustomnick'})
+     *
+     * @example
+     * // For example, opening a room with a specific default configuration:
+     * api.rooms.open(
+     *     'myroom@conference.example.org',
+     *     { 'nick': 'coolguy69',
+     *       'auto_configure': true,
+     *       'roomconfig': {
+     *           'changesubject': false,
+     *           'membersonly': true,
+     *           'persistentroom': true,
+     *           'publicroom': true,
+     *           'roomdesc': 'Comfy room for hanging out',
+     *           'whois': 'anyone'
+     *       }
+     *     }
+     * );
+     */
+    function open(jids: string | string[], attrs?: {
+        nick?: string;
+        hidden?: boolean;
+        auto_configure?: boolean;
+        roomconfig?: any;
+        minimized?: boolean;
+        bring_to_foreground?: boolean;
+    }, force?: boolean): Promise<import("./muc.js").default | import("./muc.js").default[]>;
+    /**
+     * Fetches the object representing a MUC chatroom (aka groupchat)
+     *
+     * @method api.rooms.get
+     * @param {string|string[]} [jids] The room JID (if not specified, all rooms will be returned).
+     * @param {object} [attrs] A map containing any extra room attributes
+     *  to be set if `create` is set to `true`
+     * @param {string} [attrs.nick] Specify the nickname
+     * @param {string} [attrs.password ] Specify a password if needed to enter a new room
+     * @param {boolean} create A boolean indicating whether the room should be created
+     *     if not found (default: `false`)
+     * @returns {Promise<MUC[]|MUC>}
+     * @example
+     * api.waitUntil('roomsAutoJoined').then(() => {
+     *     const create_if_not_found = true;
+     *     api.rooms.get(
+     *         'group@muc.example.com',
+     *         {'nick': 'dread-pirate-roberts', 'password': 'secret'},
+     *         create_if_not_found
+     *     )
+     * });
+     */
+    function get(jids?: string | string[], attrs?: {
+        nick?: string;
+        password?: string;
+    }, create?: boolean): Promise<import("./muc.js").default | import("./muc.js").default[]>;
+}
 //# sourceMappingURL=api.d.ts.map

+ 1 - 1
src/headless/types/shared/_converse.d.ts

@@ -1,5 +1,5 @@
 export default _converse;
-export type Storage = any;
+export type Storage = import('@converse/skeletor').Storage;
 export type Collection = import('@converse/skeletor').Collection;
 export type DiscoState = import('../plugins/disco/index').DiscoState;
 export type XMPPStatus = import('../plugins/status/status').default;

+ 0 - 1
src/headless/types/shared/api/index.d.ts

@@ -1,5 +1,4 @@
 export default api;
-export { converse } from "./public.js";
 export type _converse = {
     initialize(): void;
     VERSION_NAME: string;

+ 4 - 5
src/headless/types/shared/api/presence.d.ts

@@ -6,15 +6,14 @@ declare namespace _default {
          * @param {String} [type]
          * @param {String} [to]
          * @param {String} [status] - An optional status message
-         * @param {Array<Element>|Array<Strophe.Builder>|Element|Strophe.Builder} [child_nodes]
+         * @param {Array<Element>|Array<Builder>|Element|Builder} [nodes]
          *  Nodes(s) to be added as child nodes of the `presence` XML element.
          */
-        function send(type?: string, to?: string, status?: string, child_nodes?: any): Promise<void>;
+        function send(type?: string, to?: string, status?: string, nodes?: Element | import("strophe.js/src/types/builder.js").default | Element[] | import("strophe.js/src/types/builder.js").default[]): Promise<void>;
     }
 }
 export default _default;
-export namespace Strophe {
-    type Builder = any;
-}
+export type Builder = import('strophe.js').Builder;
 export type XMPPStatus = import('../../plugins/status/status').default;
+export type MUC = import('../../plugins/muc/muc.js').default;
 //# sourceMappingURL=presence.d.ts.map

+ 11 - 10
src/headless/types/shared/api/public.d.ts

@@ -1,6 +1,6 @@
+export default converse;
+export type ConversePrivateGlobal = any;
 /**
- * @typedef {Window & {converse: ConversePrivateGlobal} } window
- *
  * ### The Public API
  *
  * This namespace contains public API methods which are are
@@ -9,13 +9,13 @@
  * page can call them. Public methods therefore don’t expose any sensitive
  * or closured data. To do that, you’ll need to create a plugin, which has
  * access to the private API method.
- *
- * @global
- * @namespace converse
  */
-export const converse: any;
-export type ConversePrivateGlobal = any;
+export type window = Window & {
+    converse: ConversePrivateGlobal;
+};
 /**
+ * @typedef {Window & {converse: ConversePrivateGlobal} } window
+ *
  * ### The Public API
  *
  * This namespace contains public API methods which are are
@@ -24,8 +24,9 @@ export type ConversePrivateGlobal = any;
  * page can call them. Public methods therefore don’t expose any sensitive
  * or closured data. To do that, you’ll need to create a plugin, which has
  * access to the private API method.
+ *
+ * @global
+ * @namespace converse
  */
-export type window = Window & {
-    converse: ConversePrivateGlobal;
-};
+declare const converse: any;
 //# sourceMappingURL=public.d.ts.map

+ 1 - 1
src/headless/types/shared/api/user.d.ts

@@ -32,7 +32,7 @@ declare namespace api {
          */
         logout(): Promise<any>;
         presence: {
-            send(type?: string, to?: string, status?: string, child_nodes?: any): Promise<void>;
+            send(type?: string, to?: string, status?: string, nodes?: Element | import("strophe.js/src/types/builder.js").default | Element[] | import("strophe.js/src/types/builder.js").default[]): Promise<void>;
         };
         settings: {
             getModel(): Promise<import("@converse/skeletor").Model>;

+ 0 - 1
src/headless/types/shared/connection/index.d.ts

@@ -5,7 +5,6 @@ declare const Connection_base: typeof import("strophe.js/src/types/connection.js
  * via BOSH or websocket inside a shared worker).
  */
 export class Connection extends Connection_base {
-    static generateResource(): string;
     constructor(service: any, options: any);
     send_initial_presence: boolean;
     debouncedReconnect: any;

+ 1 - 0
src/headless/types/shared/connection/utils.d.ts

@@ -1,3 +1,4 @@
+export function generateResource(): string;
 export function setUpXMLLogging(connection: any): void;
 export function getConnectionServiceURL(): any;
 //# sourceMappingURL=utils.d.ts.map

+ 1 - 1
src/headless/types/shared/i18n.d.ts

@@ -1,6 +1,6 @@
 export default i18nStub;
 declare namespace i18nStub {
-    function initialize(): void;
+    function initialize(): Promise<void>;
     /**
      * Overridable string wrapper method which can be used to provide i18n
      * support.

+ 4 - 41
src/headless/types/shared/settings/api.d.ts

@@ -24,10 +24,12 @@ export namespace settings_api {
     function update(settings: any): void;
     /**
      * @method _converse.api.settings.get
-     * @returns {*} Value of the particular configuration setting.
+     * @param {string} [key]
+     * @returns {*} Value of the particular configuration setting, or all
+     *  settings if no key was specified.
      * @example api.settings.get("play_sounds");
      */
-    function get(key: any): any;
+    function get(key?: string): any;
     /**
      * Set one or many configuration settings.
      *
@@ -70,44 +72,5 @@ export namespace settings_api {
         function not(name: string, handler: Function): void;
     }
 }
-export namespace user_settings_api {
-    /**
-     * Returns the user settings model. Useful when you want to listen for change events.
-     * @async
-     * @method _converse.api.user.settings.getModel
-     * @returns {Promise<Model>}
-     * @example const settings = await api.user.settings.getModel();
-     */
-    function getModel(): Promise<import("@converse/skeletor").Model>;
-    /**
-     * Get the value of a particular user setting.
-     * @method _converse.api.user.settings.get
-     * @param {string} key - The setting name
-     * @param {*} [fallback] - An optional fallback value if the user setting is undefined
-     * @returns {Promise} Promise which resolves with the value of the particular configuration setting.
-     * @example api.user.settings.get("foo");
-     */
-    function get(key: string, fallback?: any): Promise<any>;
-    /**
-     * Set one or many user settings.
-     * @async
-     * @method _converse.api.user.settings.set
-     * @param {Object|string} key An object containing config settings or alternatively a string key
-     * @param {string} [val] The value, if the previous parameter is a key
-     * @example api.user.settings.set("foo", "bar");
-     * @example
-     * api.user.settings.set({
-     *     "foo": "bar",
-     *     "baz": "buz"
-     * });
-     */
-    function set(key: any, val?: string): Promise<any>;
-    /**
-     * Clears all the user settings
-     * @async
-     * @method api.user.settings.clear
-     */
-    function clear(): Promise<any>;
-}
 export type Model = import('@converse/skeletor').Model;
 //# sourceMappingURL=api.d.ts.map

+ 41 - 0
src/headless/types/shared/settings/user/api.d.ts

@@ -0,0 +1,41 @@
+export namespace user_settings_api {
+    /**
+     * Returns the user settings model. Useful when you want to listen for change events.
+     * @async
+     * @method _converse.api.user.settings.getModel
+     * @returns {Promise<Model>}
+     * @example const settings = await api.user.settings.getModel();
+     */
+    function getModel(): Promise<import("@converse/skeletor").Model>;
+    /**
+     * Get the value of a particular user setting.
+     * @method _converse.api.user.settings.get
+     * @param {string} key - The setting name
+     * @param {*} [fallback] - An optional fallback value if the user setting is undefined
+     * @returns {Promise} Promise which resolves with the value of the particular configuration setting.
+     * @example api.user.settings.get("foo");
+     */
+    function get(key: string, fallback?: any): Promise<any>;
+    /**
+     * Set one or many user settings.
+     * @async
+     * @method _converse.api.user.settings.set
+     * @param {Object|string} key An object containing config settings or alternatively a string key
+     * @param {string} [val] The value, if the previous parameter is a key
+     * @example api.user.settings.set("foo", "bar");
+     * @example
+     * api.user.settings.set({
+     *     "foo": "bar",
+     *     "baz": "buz"
+     * });
+     */
+    function set(key: any, val?: string): Promise<any>;
+    /**
+     * Clears all the user settings
+     * @async
+     * @method api.user.settings.clear
+     */
+    function clear(): Promise<any>;
+}
+export type Model = import('@converse/skeletor').Model;
+//# sourceMappingURL=api.d.ts.map

+ 4 - 0
src/headless/types/shared/settings/user/utils.d.ts

@@ -0,0 +1,4 @@
+export function getUserSettings(): Promise<any>;
+export function updateUserSettings(data: any, options: any): Promise<any>;
+export function clearUserSettings(): Promise<any>;
+//# sourceMappingURL=utils.d.ts.map

+ 0 - 3
src/headless/types/shared/settings/utils.d.ts

@@ -19,7 +19,4 @@ export function unregisterListener(name: string, func: Function): void;
  * @param {string} [val] The value, if the previous parameter is a key
  */
 export function updateAppSettings(key: any | string, val?: string): any;
-export function getUserSettings(): Promise<any>;
-export function updateUserSettings(data: any, options: any): Promise<any>;
-export function clearUserSettings(): Promise<any>;
 //# sourceMappingURL=utils.d.ts.map

+ 19 - 47
src/headless/types/utils/session.d.ts

@@ -7,51 +7,23 @@
 export function isUniView(): boolean;
 export function isTestEnv(): boolean;
 export function getUnloadEvent(): "pagehide" | "beforeunload" | "unload";
-export function replacePromise(name: any): void;
-export function shouldClearCache(): any;
-export function tearDown(): Promise<{
-    initialize(): void;
-    VERSION_NAME: string;
-    strict_plugin_dependencies: boolean;
-    pluggable: any;
-    templates: {};
-    storage: {};
-    promises: {
-        initialized: any;
-    };
-    DEFAULT_IMAGE_TYPE: string;
-    DEFAULT_IMAGE: string;
-    NUM_PREKEYS: number;
-    TIMEOUTS: {
-        PAUSED: number;
-        INACTIVE: number;
-    };
-    api: any;
-    labels: Record<string, string | Record<string, string>>;
-    exports: Record<string, any>;
-    state: any;
-    initSession(): void;
-    session: import("@converse/skeletor").Model;
-    __(...args: string[]): any;
-    ___(str: string): string;
-    on(name: string, callback: (event: any, model: import("@converse/skeletor").Model, collection: import("@converse/skeletor").Collection, options?: Record<string, any>) => any, context: any): any;
-    _events: any;
-    _listeners: {};
-    listenTo(obj: any, name: string, callback?: (event: any, model: import("@converse/skeletor").Model, collection: import("@converse/skeletor").Collection, options?: Record<string, any>) => any): any;
-    _listeningTo: {};
-    _listenId: any;
-    off(name: string, callback: (event: any, model: import("@converse/skeletor").Model, collection: import("@converse/skeletor").Collection, options?: Record<string, any>) => any, context?: any): any;
-    stopListening(obj?: any, name?: string, callback?: (event: any, model: import("@converse/skeletor").Model, collection: import("@converse/skeletor").Collection, options?: Record<string, any>) => any): any;
-    once(name: string, callback: (event: any, model: import("@converse/skeletor").Model, collection: import("@converse/skeletor").Collection, options?: Record<string, any>) => any, context: any): any;
-    listenToOnce(obj: any, name: string, callback?: (event: any, model: import("@converse/skeletor").Model, collection: import("@converse/skeletor").Collection, options?: Record<string, any>) => any): any;
-    trigger(name: string, ...args: any[]): any;
-    constructor: Function;
-    toString(): string;
-    toLocaleString(): string;
-    valueOf(): Object;
-    hasOwnProperty(v: PropertyKey): boolean;
-    isPrototypeOf(v: Object): boolean;
-    propertyIsEnumerable(v: PropertyKey): boolean;
-}>;
-export function clearSession(): any;
+/**
+ * @param {ConversePrivateGlobal} _converse
+ * @param {string} name
+ */
+export function replacePromise(_converse: any, name: string): void;
+/**
+ * @param {ConversePrivateGlobal} _converse
+ * @returns {boolean}
+ */
+export function shouldClearCache(_converse: any): boolean;
+/**
+ * @param {ConversePrivateGlobal} _converse
+ */
+export function tearDown(_converse: any): Promise<any>;
+/**
+ * @param {ConversePrivateGlobal} _converse
+ */
+export function clearSession(_converse: any): any;
+export type ConversePrivateGlobal = any;
 //# sourceMappingURL=session.d.ts.map

+ 4 - 4
src/headless/utils/init.js

@@ -3,17 +3,15 @@
  */
 import Storage from '@converse/skeletor/src/storage.js';
 import _converse from '../shared/_converse';
-import api from '../shared/api/index.js';
 import debounce from 'lodash-es/debounce';
 import localDriver from 'localforage-webextensionstorage-driver/local';
 import log from '../log.js';
 import syncDriver from 'localforage-webextensionstorage-driver/sync';
 import { ANONYMOUS, CORE_PLUGINS, EXTERNAL, LOGIN } from '../shared/constants.js';
-import { Connection } from '../shared/connection/index.js';
 import { Model } from '@converse/skeletor';
 import { Strophe } from 'strophe.js';
 import { createStore, initStorage } from './storage.js';
-import { getConnectionServiceURL } from '../shared/connection/utils';
+import { generateResource, getConnectionServiceURL } from '../shared/connection/utils';
 import { isValidJID } from './jid.js';
 import { getUnloadEvent, isTestEnv } from './session.js';
 
@@ -153,9 +151,11 @@ function initPersistentStorage (_converse, store_name) {
  * @param {string} jid
  */
 function saveJIDtoSession (_converse, jid) {
+    const { api } = _converse;
+
     jid = _converse.session.get('jid') || jid;
     if (_converse.api.settings.get("authentication") !== ANONYMOUS && !Strophe.getResourceFromJid(jid)) {
-        jid = jid.toLowerCase() + Connection.generateResource();
+        jid = jid.toLowerCase() + generateResource();
     }
 
     const bare_jid = Strophe.getBareJidFromJid(jid);

+ 25 - 9
src/headless/utils/session.js

@@ -1,9 +1,13 @@
-import _converse from '../shared/_converse.js';
+/**
+ * @typedef {module:shared-api-public.ConversePrivateGlobal} ConversePrivateGlobal
+ */
 import log from '../log.js';
 import { getOpenPromise } from '@converse/openpromise';
 import { settings_api } from '../shared/settings/api.js';
 import { getInitSettings } from '../shared/settings/utils.js';
 
+const settings = settings_api;
+
 /**
  * We distinguish between UniView and MultiView instances.
  *
@@ -11,7 +15,7 @@ import { getInitSettings } from '../shared/settings/utils.js';
  * MultiView means that multiple chats may be visible simultaneously.
  */
 export function isUniView () {
-    return ['mobile', 'fullscreen', 'embedded'].includes(settings_api.get("view_mode"));
+    return ['mobile', 'fullscreen', 'embedded'].includes(settings.get("view_mode"));
 }
 
 export function isTestEnv () {
@@ -31,7 +35,11 @@ export function getUnloadEvent () {
     return 'unload';
 }
 
-export function replacePromise (name) {
+/**
+ * @param {ConversePrivateGlobal} _converse
+ * @param {string} name
+ */
+export function replacePromise (_converse, name) {
     const existing_promise = _converse.promises[name];
     if (!existing_promise) {
         throw new Error(`Tried to replace non-existing promise: ${name}`);
@@ -45,24 +53,32 @@ export function replacePromise (name) {
     }
 }
 
-export function shouldClearCache () {
+/**
+ * @param {ConversePrivateGlobal} _converse
+ * @returns {boolean}
+ */
+export function shouldClearCache (_converse) {
     const { api } = _converse;
     return !_converse.state.config.get('trusted') ||
         api.settings.get('clear_cache_on_logout') ||
         isTestEnv();
 }
 
-
-export async function tearDown () {
+/**
+ * @param {ConversePrivateGlobal} _converse
+ */
+export async function tearDown (_converse) {
     const { api } = _converse;
     await api.trigger('beforeTearDown', {'synchronous': true});
     api.trigger('afterTearDown');
     return _converse;
 }
 
-
-export function clearSession () {
-    shouldClearCache() && _converse.api.user.settings.clear();
+/**
+ * @param {ConversePrivateGlobal} _converse
+ */
+export function clearSession (_converse) {
+    shouldClearCache(_converse) && _converse.api.user.settings.clear();
     _converse.initSession();
     /**
      * Synchronouse event triggered once the user session has been cleared,

+ 4 - 2
src/headless/utils/storage.js

@@ -3,9 +3,11 @@ import _converse from '../shared/_converse.js';
 import { settings_api } from '../shared/settings/api.js';
 import { getUnloadEvent } from './session.js';
 
+const settings = settings_api;
+
 export function getDefaultStore () {
     if (_converse.state.config.get('trusted')) {
-        const is_non_persistent = settings_api.get('persistent_store') === 'sessionStorage';
+        const is_non_persistent = settings.get('persistent_store') === 'sessionStorage';
         return is_non_persistent ? 'session': 'persistent';
     } else {
         return 'session';
@@ -13,7 +15,7 @@ export function getDefaultStore () {
 }
 
 function storeUsesIndexedDB (store) {
-    return store === 'persistent' && settings_api.get('persistent_store') === 'IndexedDB';
+    return store === 'persistent' && settings.get('persistent_store') === 'IndexedDB';
 }
 
 export function createStore (id, store) {

+ 7 - 5
src/headless/utils/url.js

@@ -3,7 +3,9 @@
  */
 import URI from 'urijs';
 import log from '../log.js';
-import api from '../shared/api/index.js';
+import { settings_api } from '../shared/settings/api.js';
+
+const settings = settings_api;
 
 /**
  * Will return false if URL is malformed or contains disallowed characters
@@ -79,7 +81,7 @@ export function shouldRenderMediaFromURL (url_text, type) {
     if (!isAllowedProtocolForMedia(url_text)) {
         return false;
     }
-    const may_render = api.settings.get('render_media');
+    const may_render = settings.get('render_media');
     const is_domain_allowed = isDomainAllowed(url_text, `allowed_${type}_domains`);
 
     if (Array.isArray(may_render)) {
@@ -90,14 +92,14 @@ export function shouldRenderMediaFromURL (url_text, type) {
 }
 
 export function filterQueryParamsFromURL (url) {
-    const paramsArray = api.settings.get('filter_url_query_params');
+    const paramsArray = settings.get('filter_url_query_params');
     if (!paramsArray) return url;
     const parsed_uri = getURI(url);
     return parsed_uri.removeQuery(paramsArray).toString();
 }
 
 export function isDomainAllowed (url, setting) {
-    const allowed_domains = api.settings.get(setting);
+    const allowed_domains = settings.get(setting);
     if (!Array.isArray(allowed_domains)) {
         return true;
     }
@@ -138,7 +140,7 @@ export function isVideoURL (url) {
 }
 
 export function isImageURL (url) {
-    const regex = api.settings.get('image_urls_regex');
+    const regex = settings.get('image_urls_regex');
     return regex?.test(url) || isURLWithImageExtension(url);
 }
 

+ 1 - 2
src/plugins/chatboxviews/view.js

@@ -2,7 +2,6 @@ import tplBackgroundLogo from '../../templates/background_logo.js';
 import tplChats from './templates/chats.js';
 import { CustomElement } from 'shared/components/element.js';
 import { api, _converse } from '@converse/headless';
-import { getAppSettings } from '@converse/headless/shared/settings/utils.js';
 import { render } from 'lit';
 
 
@@ -23,7 +22,7 @@ class ConverseChats extends CustomElement {
         this.listenTo(_converse, 'reconnected', () => this.requestUpdate());
         this.listenTo(_converse, 'disconnected', () => this.requestUpdate());
 
-        const settings = getAppSettings();
+        const settings = api.settings.get();
         this.listenTo(settings, 'change:view_mode', () => this.requestUpdate())
         this.listenTo(settings, 'change:singleton', () => this.requestUpdate())
 

+ 15 - 0
src/plugins/omemo/api.js

@@ -1,5 +1,7 @@
 import { _converse, api } from '@converse/headless';
+import { initStorage } from '@converse/headless/utils/storage.js';
 import { generateFingerprint } from './utils.js';
+import OMEMOStore from './store.js';
 
 export default {
     /**
@@ -18,6 +20,19 @@ export default {
             return _converse.state.omemo_store.get('device_id');
         },
 
+        'session': {
+            async restore () {
+                const { state } = _converse;
+                if (state.omemo_store === undefined) {
+                    const bare_jid = _converse.session.get('bare_jid');
+                    const id = `converse.omemosession-${bare_jid}`;
+                    state.omemo_store = new OMEMOStore({ id });
+                    initStorage(state.omemo_store, id);
+                }
+                await state.omemo_store.fetchSession();
+            }
+        },
+
         /**
          * The "devicelists" namespace groups methods related to OMEMO device lists
          *

+ 1 - 2
src/plugins/omemo/devicelist.js

@@ -2,7 +2,6 @@ import { Model } from '@converse/skeletor';
 import { _converse, api, converse, log } from '@converse/headless';
 import { getOpenPromise } from '@converse/openpromise';
 import { initStorage } from '@converse/headless/utils/storage.js';
-import { restoreOMEMOSession } from './utils.js';
 
 const { Strophe, $build, $iq, sizzle } = converse.env;
 
@@ -82,7 +81,7 @@ class DeviceList extends Model {
         if (this.get('jid') !== bare_jid) {
             return; // We only publish for ourselves.
         }
-        await restoreOMEMOSession();
+        await api.omemo.session.restore();
 
         if (!_converse.state.omemo_store) {
             // Happens during tests. The connection gets torn down

+ 1 - 1
src/plugins/omemo/index.js

@@ -115,7 +115,7 @@ converse.plugins.add('converse-omemo', {
 
         api.listen.on('clearSession', () => {
             delete _converse.state.omemo_store
-            if (shouldClearCache() && _converse.state.devicelists) {
+            if (shouldClearCache(_converse) && _converse.state.devicelists) {
                 _converse.state.devicelists.clearStore();
                 delete _converse.state.devicelists;
             }

+ 1 - 10
src/plugins/omemo/utils.js

@@ -29,7 +29,6 @@ import {
 } from '@converse/headless/utils/arraybuffer.js';
 import MUC from 'headless/plugins/muc/muc.js';
 import {IQError, UserFacingError} from 'shared/errors.js';
-import OMEMOStore from './store.js';
 import DeviceLists from './devicelists.js';
 
 const { Strophe, URI, sizzle, u } = converse.env;
@@ -669,14 +668,6 @@ export function registerPEPPushHandler () {
 }
 
 export async function restoreOMEMOSession () {
-    const { state } = _converse;
-    if (state.omemo_store === undefined) {
-        const bare_jid = _converse.session.get('bare_jid');
-        const id = `converse.omemosession-${bare_jid}`;
-        state.omemo_store = new OMEMOStore({ id });
-        initStorage(state.omemo_store, id);
-    }
-    await state.omemo_store.fetchSession();
 }
 
 async function fetchDeviceLists () {
@@ -707,7 +698,7 @@ export async function initOMEMO (reconnecting) {
     }
     try {
         await fetchDeviceLists();
-        await restoreOMEMOSession();
+        await api.omemo.session.restore();
         await _converse.state.omemo_store.publishBundle();
     } catch (e) {
         log.error('Could not initialize OMEMO support');

+ 0 - 41
src/plugins/roomslist/templates/groups.js

@@ -1,41 +0,0 @@
-import { __ } from 'i18n';
-import { html } from "lit";
-import { tplRoomItem } from 'plugins/roomslist/templates/roomslist.js'
-
-import '../styles/roomsgroups.scss';
-
-function tplRoomDomainGroup (el, domain, rooms) {
-    const i18n_title = __('Click to hide these rooms');
-    const collapsed = el.model.get('collapsed_domains');
-    const is_collapsed = collapsed.includes(domain);
-    return html`
-    <div class="muc-domain-group" data-domain="${domain}">
-        <a href="#" class="list-toggle muc-domain-group-toggle controlbox-padded" title="${i18n_title}" @click=${ev => el.toggleDomainList(ev, domain)}>
-            <converse-icon
-                class="fa ${ is_collapsed ? 'fa-caret-right' : 'fa-caret-down' }"
-                size="1em"
-                color="var(--groupchats-header-color)"></converse-icon>
-            ${domain}
-        </a>
-        <ul class="items-list muc-domain-group-rooms ${ is_collapsed ? 'collapsed' : '' }" data-domain="${domain}">
-            ${ rooms.map(room => tplRoomItem(el, room)) }
-        </ul>
-    </div>`;
-}
-
-export function tplRoomDomainGroupList (el, rooms) {
-    // The rooms should stay sorted as they are iterated and added in order
-    const grouped_rooms = new Map();
-    for (const room of rooms) {
-        const roomdomain = room.get('jid').split('@').at(-1).toLowerCase();
-        if (grouped_rooms.has(roomdomain)) {
-            grouped_rooms.get(roomdomain).push(room);
-        } else {
-            grouped_rooms.set(roomdomain, [room]);
-        }
-    }
-    const sorted_domains = Array.from(grouped_rooms.keys());
-    sorted_domains.sort();
-
-    return sorted_domains.map(domain => tplRoomDomainGroup(el, domain, grouped_rooms.get(domain)))
-}

+ 40 - 6
src/plugins/roomslist/templates/roomslist.js

@@ -4,14 +4,14 @@
  */
 import 'plugins/muc-views/modals/add-muc.js';
 import 'plugins/muc-views/modals/muc-list.js';
+import { CHATROOMS_TYPE, CLOSED } from '@converse/headless/shared/constants.js';
 import { __ } from 'i18n';
 import { _converse, api } from "@converse/headless";
+import { addBookmarkViaEvent } from 'plugins/bookmark-views/utils.js';
 import { html } from "lit";
 import { isUniView } from '@converse/headless/utils/session.js';
-import { addBookmarkViaEvent } from 'plugins/bookmark-views/utils.js';
-import { tplRoomDomainGroupList } from 'plugins/roomslist/templates/groups.js';
-import { CHATROOMS_TYPE, CLOSED } from '@converse/headless/shared/constants.js';
 
+import '../styles/roomsgroups.scss';
 
 /** @param {MUC} room */
 function isCurrentlyOpen (room) {
@@ -35,7 +35,6 @@ function tplBookmark (room) {
         </a>`;
 }
 
-
 /** @param {MUC} room */
 function tplUnreadIndicator (room) {
     return html`<span class="list-item-badge badge badge--muc msgs-indicator">${ room.get('num_unread') }</span>`;
@@ -45,12 +44,11 @@ function tplActivityIndicator () {
     return html`<span class="list-item-badge badge badge--muc msgs-indicator"></span>`;
 }
 
-
 /**
  * @param {RoomsList} el
  * @param {MUC} room
  */
-export function tplRoomItem (el, room) {
+function tplRoomItem (el, room) {
     const i18n_leave_room = __('Leave this groupchat');
     const has_unread_msgs = room.get('num_unread_general') || room.get('has_activity');
     return html`
@@ -84,6 +82,42 @@ export function tplRoomItem (el, room) {
         </div>`;
 }
 
+function tplRoomDomainGroup (el, domain, rooms) {
+    const i18n_title = __('Click to hide these rooms');
+    const collapsed = el.model.get('collapsed_domains');
+    const is_collapsed = collapsed.includes(domain);
+    return html`
+    <div class="muc-domain-group" data-domain="${domain}">
+        <a href="#" class="list-toggle muc-domain-group-toggle controlbox-padded" title="${i18n_title}" @click=${ev => el.toggleDomainList(ev, domain)}>
+            <converse-icon
+                class="fa ${ is_collapsed ? 'fa-caret-right' : 'fa-caret-down' }"
+                size="1em"
+                color="var(--groupchats-header-color)"></converse-icon>
+            ${domain}
+        </a>
+        <ul class="items-list muc-domain-group-rooms ${ is_collapsed ? 'collapsed' : '' }" data-domain="${domain}">
+            ${ rooms.map(room => tplRoomItem(el, room)) }
+        </ul>
+    </div>`;
+}
+
+function tplRoomDomainGroupList (el, rooms) {
+    // The rooms should stay sorted as they are iterated and added in order
+    const grouped_rooms = new Map();
+    for (const room of rooms) {
+        const roomdomain = room.get('jid').split('@').at(-1).toLowerCase();
+        if (grouped_rooms.has(roomdomain)) {
+            grouped_rooms.get(roomdomain).push(room);
+        } else {
+            grouped_rooms.set(roomdomain, [room]);
+        }
+    }
+    const sorted_domains = Array.from(grouped_rooms.keys());
+    sorted_domains.sort();
+
+    return sorted_domains.map(domain => tplRoomDomainGroup(el, domain, grouped_rooms.get(domain)))
+}
+
 /**
  * @param {RoomsList} el
  */

Some files were not shown because too many files changed in this diff