Compare commits

..

No commits in common. "main-chapril-rebranding" and "v10.0.0" have entirely different histories.

332 changed files with 57082 additions and 81301 deletions

View File

@ -1,7 +1,7 @@
{
"parser": "@typescript-eslint/parser",
"parser": "@babel/eslint-parser",
"parserOptions": {
"ecmaVersion": 2020,
"ecmaVersion": 2017,
"sourceType": "module",
"allowImportExportEverywhere": true
},
@ -10,12 +10,8 @@
"jasmine": true,
"es6": true
},
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"plugins": [],
"extends": ["eslint:recommended"],
"globals": {
"Uint8Array": true,
"Promise": true,

View File

@ -1,2 +0,0 @@
paths-ignore:
- '**/tests/*.js'

View File

@ -1,43 +0,0 @@
name: "CodeQL"
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
schedule:
- cron: "11 18 * * 6"
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ javascript ]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
config-file: ./.github/codeql-config.yml
languages: ${{ matrix.language }}
queries: +security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@v2
if: ${{ matrix.language == 'javascript' }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{ matrix.language }}"

View File

@ -8,7 +8,6 @@ on:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
jobs:
build:
@ -18,7 +17,7 @@ jobs:
strategy:
matrix:
node-version: [18.x]
node-version: [16.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:

3
.gitignore vendored
View File

@ -1,9 +1,6 @@
# Distribution directory
dist
# conversejs/media repo checkout
media
# Editor fluff
*~
.sw?

2
.nvmrc
View File

@ -1 +1 @@
v18.15.0
v16.13.1

View File

@ -1,4 +1,5 @@
{
"arrowParens": "avoid",
"printWidth": 120,
"quoteProps": "preserve",
"singleQuote": true,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,61 +1,5 @@
# Changelog
## 10.1.6 (2023-08-31)
- #3246: Badge color not responsive to dark theme
- Fix a GIF rendering bug that causes a memory overflow
## 10.1.5 (2023-06-29)
- #3209: Fix error when importing the `converse` global with bootstrap modal API
- #3207: `.po` translation files weren't included in previous release
- Updated Galician and Portuguese translations
## 10.1.4 (2023-06-25)
- Fix `dist` directory not included in NPM package
## 10.1.3 (2023-06-23)
- Add the ability to set roles and affiliations via the MUC occupant modal
- Fix `isOnlyEmojis is not a function` when using only `@converse/headless`
- Fix `autojoin` checkbox state in MUC bookmark form
- Remove call to `api.confirm` in `@converse/headless`
- Generate TypeScript declaration files into `dist/types`
- Removed documentation about the no longer implemented `fullname` option.
- Updated translations
- #3123: Contacts do not show up online until chat is opened with them.
- #3156: Add function to prevent drag stutter effect over iframes when resize is called in overlay mode
- #3165: Use configured nickname in profile view in the control box
- New config option [stanza_timeout](https://conversejs.org/docs/html/configuration.html#stanza-timeout)
## 10.1.2 (2023-02-17)
- #1490: Busy-loop when fetching registration form fails
- #1556: Can't switch to registration form afrer logout
- #3137: Various UI/UX bugfixes regarding the registration form
- XEP-0437: Room Activity Indicators (RAI) optimizations
## 10.1.1 (2023-02-15)
- #1851: Sort open groupchats alphabetically
- #2240: Ad-Hoc command result form not shown
- #3128: Second bookmarked room shows info of the first one
- Bugfix. Uyghur translations weren't loading
## 10.1.0 (2023-01-07)
- #326: Add the ability to reset your password
- #2759: Don't automatically log in again if the user manually logged out
- #2816: Chat highlight behaves odd
- #2925: File upload is not always enabled
- #3001: Add option to save SCRAM details and to use them to stay logged in upon reload
- Add a "Add to Contacts" button in MUC occupant modals
- Updated translations and add support for Uyghur
- New config option [reuse_scram_keys](https://conversejs.org/docs/html/configuration.html#reuse-scram-keys)
## 10.0.0 (2022-10-30)
- Update to Strophe.js 1.6.0 which adds support for SCRAM-SHA-256 and SCRAM-SHA-512

View File

@ -2,7 +2,7 @@
*
* An XMPP chat client that runs in the browser.
*
* Version: 10.1.6
* Version: 10.0.0
*
* Copyright: JC Brand 2013-2018
* Except for 3rd party dependencies.

View File

@ -3,11 +3,13 @@ BOOTSTRAP = ./node_modules/
BUILDDIR = ./docs
KARMA ?= ./node_modules/.bin/karma
CLEANCSS ?= ./node_modules/clean-css-cli/bin/cleancss
ESLINT ?= ./node_modules/.bin/eslint
HTTPSERVE ?= ./node_modules/.bin/http-server
HTTPSERVE_PORT ?= 8000
INKSCAPE ?= inkscape
INSTALL ?= install
JSDOC ?= ./node_modules/.bin/jsdoc
LERNA ?= ./node_modules/.bin/lerna
OXIPNG ?= oxipng
PAPER =
RJS ?= ./node_modules/.bin/r.js
@ -58,14 +60,13 @@ serve: node_modules dist
serve_bg: node_modules
$(HTTPSERVE) -p $(HTTPSERVE_PORT) -c-1 -s &
certs:
mkdir certs
cd certs && openssl req -newkey rsa:4096 -x509 -sha256 -days 365 -nodes -out chat.example.org.crt -keyout chat.example.org.key
########################################################################
## Translation machinery
GETTEXT = $(XGETTEXT) --from-code=UTF-8 --language=JavaScript --keyword=__ --keyword=___ --keyword=i18n_ --force-po --output=src/i18n/converse.pot --package-name=Converse.js --copyright-holder="Jan-Carel Brand" --package-version=10.1.6 dist/converse-no-dependencies.js -c
dist/converse-no-dependencies.js: src webpack/webpack.common.js webpack/webpack.nodeps.js @converse/headless node_modules
npm run nodeps
GETTEXT = $(XGETTEXT) --from-code=UTF-8 --language=JavaScript --keyword=__ --keyword=___ --keyword=i18n_ --force-po --output=src/i18n/converse.pot --package-name=Converse.js --copyright-holder="Jan-Carel Brand" --package-version=10.0.0 dist/converse-no-dependencies.js -c
src/i18n/converse.pot: dist/converse-no-dependencies.js
$(GETTEXT) 2>&1 > /dev/null; exit $$?;
@ -82,9 +83,10 @@ po:
########################################################################
## Release management
.PHONY: version
version:
$(SED) -i '/^export const VERSION_NAME =/s/=.*/= "v$(VERSION)";/' src/headless/shared/constants.js
.PHONY: release
release:
find ./src -name "*~" -exec rm {} \;
$(SED) -i '/^_converse.VERSION_NAME =/s/=.*/= "v$(VERSION)";/' src/headless/core.js
$(SED) -i '/Version:/s/:.*/: $(VERSION)/' COPYRIGHT
$(SED) -i '/Project-Id-Version:/s/:.*/: Converse.js $(VERSION)\n"/' src/i18n/converse.pot
$(SED) -i '/"version":/s/:.*/: "$(VERSION)",/' manifest.json
@ -99,33 +101,19 @@ version:
make pot
make po
make dist
release-checkout:
git clone git@github.com:conversejs/converse.js.git --depth 1 --branch $(BRANCH) release-$(BRANCH)
cd release-$(BRANCH) && make dist
.PHONY: publish
publish:
make release-checkout
cd release-$(BRANCH) && npm pack && npm publish
cd release-$(BRANCH)/src/headless && npm pack && npm publish
find ./release-$(BRANCH)/ -name "converse.js-*.tgz" -exec mv {} . \;
find ./release-$(BRANCH)/src/headless -name "converse-headless-*.tgz" -exec mv {} . \;
rm -rf release-$(BRANCH)
npm pack
cd src/headless && npm pack
.PHONY: postrelease
postrelease:
$(SED) -i '/^export const VERSION_NAME =/s/=.*/= "v$(VERSION)dev";/' src/headless/shared/constants.js
.PHONY: deploy
deploy:
git clone --branch v$(VERSION) git@github.com:conversejs/converse.js.git --depth 1 $(VERSION)
cd $(VERSION) && make node && ASSET_PATH=https://cdn.conversejs.org/$(VERSION)/dist/ make dist && make doc
cd .. && git pull && make node && ASSET_PATH=https://cdn.conversejs.org/dist/ make dist && make doc
$(SED) -i '/^_converse.VERSION_NAME =/s/=.*/= "v$(VERSION)dev";/' src/headless/core.js
########################################################################
## Install dependencies
$(LERNA):
npm install lerna
${NVM_DIR}/nvm.sh:
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
source ~/.bashrc
@ -137,9 +125,12 @@ nvm: ${NVM_DIR}/nvm.sh
node: .nvmrc
. $(HOME)/.nvm/nvm.sh && nvm install
node_modules: package.json src/headless/package.json
package-lock.json: package.json
npm install
node_modules: $(LERNA) package.json package-lock.json src/headless/package.json src/headless/package-lock.json
npm run lerna
.PHONY: clean
clean:
npm run clean
@ -156,9 +147,6 @@ devserver: node_modules
########################################################################
## Builds
dist/converse-no-dependencies.js: src webpack/webpack.common.js webpack/webpack.nodeps.js @converse/headless node_modules
npm run nodeps
dist/converse.js:: node_modules
npm run build
@ -205,16 +193,7 @@ src/headless/dist/converse-headless.min.js: src webpack/webpack.common.js node_m
dist:: node_modules src/* | dist/website.css dist/website.min.css
npm run headless
# Ideally this should just be `npm run build`.
# The additional steps are necessary to properly generate JSON chunk files
# from the .po files. The nodeps config uses preset-env with IE11.
# Somehow this is necessary.
npm run nodeps
$(eval TMPD := $(shell mktemp -d))
mv dist/locales $(TMPD) && \
npm run build && \
mv $(TMPD)/locales/*-po.js dist/locales/ && \
rm -rf $(TMPD)
npm run build
.PHONY: install
install:: dist
@ -223,24 +202,20 @@ install:: dist
cdn:: node_modules
npm run cdn
.PHONY: types
types:: node_modules
npm run types
########################################################################
## Tests
.PHONY: eslint
eslint: node_modules
npm run lint
$(ESLINT) src/**/*.js
.PHONY: check
check: eslint | dist/converse.js dist/converse.css
npm run test -- $(ARGS)
$(KARMA) start karma.conf.js $(ARGS)
.PHONY: test
test:
npm run test -- $(ARGS)
$(KARMA) start karma.conf.js $(ARGS)
########################################################################
## Documentation

View File

@ -1,17 +0,0 @@
# Construire une version Chapril de ConverseJS
```
cd .../conversejs
# La première fois, installer nvm (attention, ça va modifier le .bashrc, entre autres choses)
make nvm
export V=7.0.4
git rebase v${V?}
# [... Résoudre les conflits]
git checkout -b v${V?}-chapril
nvm install
make dist
# [... Tester les livrables présents dans dist/, et si tout est ok :]
make version VERSION=${V?}-chapril
rsync -av dist/ chapril-xmpp:/var/www/xmpp.chapril.org/public_html/dist-custom-chapril-${V?}/
```

View File

@ -166,19 +166,14 @@ We accept donations via [Patreon](https://www.patreon.com/jcbrand) and [Liberapa
## Sponsors
<p>
<a href="https://bairesdev.com/sponsoring-open-source-projects/?utm_source=conversejs" target="_blank" rel="noopener">
<img alt="BairesDev" src="https://raw.githubusercontent.com/conversejs/media/main/logos/bairesdev-primary.png" width="200">
</a>
</p>
<p>
<a href="https://blokt.com?utm_source=conversejs" target="_blank" rel="noopener">
<img alt="Blokt Crypto & Privacy" src="https://raw.githubusercontent.com/conversejs/converse.js/541613d1fea8aef364af00180f60e959162e5e4b/logo/blokt.png" width="200">
</a>
</p>
<p>
<a href="https://primesound.org/?utm_source=conversejs" target="_blank" rel="noopener">
<img alt="Prime Sound" src="https://raw.githubusercontent.com/conversejs/media/main/logos/primesound.png" width="200">
<a href="https://www.codefirst.co.uk?utm_source=conversejs" target="_blank" rel="noopener">
<img alt="Codefirst" src="https://raw.githubusercontent.com/conversejs/converse.js/541613d1fea8aef364af00180f60e959162e5e4b/logo/codefirst.png" width="200">
</a>
</p>
<p>
@ -186,3 +181,8 @@ We accept donations via [Patreon](https://www.patreon.com/jcbrand) and [Liberapa
<img alt="KeyCDN" src="https://raw.githubusercontent.com/conversejs/converse.js/541613d1fea8aef364af00180f60e959162e5e4b/logo/keycdn.png" width="200">
</a>
</p>
<p>
<a href="https://primesound.org/?utm_source=conversejs" target="_blank" rel="noopener">
<img alt="Prime Sound" src="https://raw.githubusercontent.com/conversejs/media/main/logos/primesound.png" width="200">
</a>
</p>

View File

@ -1,18 +1,19 @@
# Release checklist
1. Merge weblate translations: https://hosted.weblate.org/projects/conversejs/translations/#repository
1. Check that weblate translations are all merged in
2. Run `make check` to check that all tests pass.
3. Run `make version VERSION=10.1.6`
3. Run `make release VERSION=10.0.1`
4. Do a `git diff` to check if things look sane.
5. Do a quick manual test with the `dist` files (via `index.html`)
6. `git commit -am "Release 10.1.6"`
7. `git tag -s v10.1.6 -m "Release 10.1.6"`
8. `git push && git push origin v10.1.6`
9. `make publish BRANCH=v10.1.6`
10. Update release page on Github
* Upload tar files
11. Update https://conversejs.org
6. `git commit -am "Release 10.0.1"`
7. `git tag -s v10.0.1 -m "Release 10.0.1"`
8. Run `git push && git push origin v10.0.1`
9. Update https://conversejs.org
* `cd /home/conversejs/converse.js`
* `make deploy VERSION=10.1.6`
* `git clone --branch v10.0.1 git@github.com:conversejs/converse.js.git 10.0.1`
* `cd 10.0.1 && nvm install && ASSET_PATH=https://cdn.conversejs.org/10.0.1/dist/ make dist && make doc`
* `cd .. && git pull && nvm install && ASSET_PATH=https://cdn.conversejs.org/dist/ make dist && make doc`
10. Update release page on Github
11. Run `npm publish && cd src/headless/ && npm publish`
12. Update the repository on weblate
13. Decide on next release number and run `make postrelease VERSION=10.1.7`
13. Decide on next release number and run `make postrelease VERSION=9.1.2`

23
converse-logs/README.md Normal file
View File

@ -0,0 +1,23 @@
# How to use saved Chrome/Chromium logs to replay events
**NOTE**: This feature is very experimental and in many cases doesn't work
without data massaging and ugly hacks.
It's possible to save the log output from Chrome/Chromium (I haven't tried this
yet with any other browser) and then to replay that log output in the browser.
This can be a very helpful technique to track down bugs.
To do this, follow the following steps:
1. Save the log file (right click and then click "Save as" in the browser's console).
2. Rename the log file, making sure it ends in `.html`
3. Move the log file to the `converse-logs` directory in the converse.js repo.
4. Add `<log>` to the top of the log file and `</log>` to the bottom of the log file.
5. In `converse-logs/converse-logs.js`, add a new entry for the log file (don't
include the `.html` part of the file name.
6. Make sure that `spec/transcripts` is "required"-ed in `tests/main.js`
6. Open `tests.html` in your browser.
Your logs will run first, and then all the other tests will run afterwards.

View File

@ -0,0 +1,5 @@
define("transcripts", [
"tpl!../../converse-logs/missing_messages",
], function () {
return arguments;
});

View File

@ -10,9 +10,6 @@
<category rdf:resource="https://linkmauve.fr/ns/xmpp-doap#category-client"/>
<programming-language>JavaScript</programming-language>
<os>Browser</os>
<os>Linux</os>
<os>macOS</os>
<os>Windows</os>
<schema:logo rdf:resource="https://raw.githubusercontent.com/conversejs/converse.js/master/logo/conversejs-filled.svg"/>
<repository>
<GitRepository>

View File

@ -28,22 +28,18 @@
});
converse.initialize({
i18n: 'af',
theme: 'dracula',
auto_away: 300,
enable_smacks: true,
loglevel: 'debug',
reuse_scram_keys: true,
prune_messages_above: 100,
message_archiving: 'always',
muc_respect_autojoin: true,
muc_show_logs_before_join: true,
notify_all_room_messages: ['discuss@conference.conversejs.org'],
view_mode: 'fullscreen',
// websocket_url: 'wss://conversejs.org/xmpp-websocket',
websocket_url: 'ws://chat.example.org:5380/xmpp-websocket',
websocket_url: 'wss://conversejs.org/xmpp-websocket',
// websocket_url: 'ws://chat.example.org:5380/xmpp-websocket',
whitelisted_plugins: ['converse-debug'],
// connection_options: { worker: '/dist/shared-connection-worker.js' }
});
</script>
</body>

View File

@ -2,21 +2,25 @@
<h4 class="sidebar-title">Sponsored by</h4>
</span>
<ul class="sponsors-list">
<li><a href="https://bairesdev.com/sponsoring-open-source-projects/?utm_source=conversejs" target="_blank" rel="noopener">
<img style="width: 10em" src="/media/logos/bairesdev-primary.png" alt="BairesDev">
<li><a href="https://www.keycdn.com/?utm_source=conversejs" target="_blank" rel="noopener">
<img style="height: 2.5em" src="/logo/keycdn.png" alt="KeyCDN">
</a>
</li>
<li><a href="https://www.codefirst.co.uk/?utm_source=conversejs" target="_blank" rel="noopener">
<img style="width: 9em" src="/logo/codefirst.png" alt="Codefirst">
</a>
</li>
<li><a href="https://blokt.com/?utm_source=conversejs" target="_blank" rel="noopener">
<img style="width: 9em" src="/logo/blokt.png" alt="Blokt Crypto & Privacy">
</a>
</li>
<li><a href="https://originalenergie.de/?utm_source=conversejs" target="_blank" rel="noopener">
<img style="width: 9em" src="/logo/originalenergie-black.png" alt="Original Energie">
</a>
</li>
<li><a href="https://primesound.org/?utm_source=conversejs" target="_blank" rel="noopener">
<img style="width: 9em" src="/media/logos/primesound.png" alt="Prime Sound">
</a>
</li>
<li><a href="https://www.keycdn.com/?utm_source=conversejs" target="_blank" rel="noopener">
<img style="height: 2.5em" src="/logo/keycdn.png" alt="KeyCDN">
</a>
</li>
</ul>
<span class="centered-text-container patreon-link-container"><a href="https://conversejs.org#sponsors">Become a sponsor</a></span>

View File

@ -48,9 +48,9 @@ copyright = u'2018, JC Brand'
# built documents.
#
# The short X.Y version.
version = '10.1.6'
version = '10.0.0'
# The full version, including alpha/beta/rc tags.
release = '10.1.6'
release = '10.0.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View File

@ -407,6 +407,7 @@ in to their XMPP account.
So currently if EITHER ``keepalive`` or ``auto_login`` is ``true`` and
`authentication`_ is set to ``login``, then Converse will try to log the user in.
auto_away
---------
@ -693,7 +694,7 @@ appear in another.
.. code-block:: javascript
converse.initialize({
connection_options: { worker: '/dist/shared-connection-worker.js' }
connection_options: { 'worker': true }
});
@ -925,6 +926,12 @@ filter_url_query_params
Accepts a string or array of strings. Any query strings from URLs that match this setting will be removed.
fullname
--------
If you are using prebinding, can specify the fullname of the currently
logged in user, otherwise the user's vCard will be fetched.
geouri_regex
------------
@ -1813,27 +1820,6 @@ Based on the OGP metadata Converse will render a URL preview (also known as an
the ``show_images_inline``, ``embed_audio`` and ``embed_videos`` settings.
reuse_scram_keys
----------------
* Default: ``false``
Most XMPP servers enable the Salted Challenge Response Authentication Mechanism
or SCRAM for short. This allows the user and the server to mutually
authenticate *without* the need to transmit the user's password in plaintext.
Assuming the server does not alter the user's password or the
storage parameters, we can authenticate with the same SCRAM key multiple times.
This opens an opportunity: we can store the user's login credentials in the
browser without storing the sensitive plaintext password, or the
need to set up complicated third party backends, like OAuth.
Enabling this option will let Converse save a user's SCRAM keys upon successful
login, and next time Converse is loaded the user will be automatically logged in
with those SCRAM keys.
.. _`roomconfig_whitelist`:
roomconfig_whitelist
@ -2083,13 +2069,6 @@ themselves).
In order to support all browsers we need both an MP3 and an Ogg file. Make sure
to name your files ``msg_received.ogg`` and ``msg_received.mp3``.
stanza_timeout
--------------
* Default: ``20000`` (20 seconds)
The time to wait, in milliseconds, for a response stanza (for example to an IQ
request), before a timeout error is thrown and Converse stops waiting.
sticky_controlbox
-----------------

View File

@ -56,16 +56,16 @@ might break when a new backwards-incompatible version of Converse is released.
To load a specific version of Converse you can put the version in the URL:
* https://cdn.conversejs.org/10.1.6/dist/converse.min.js
* https://cdn.conversejs.org/10.1.6/dist/converse.min.css
* https://cdn.conversejs.org/10.0.0/dist/converse.min.js
* https://cdn.conversejs.org/10.0.0/dist/converse.min.css
You can include these two URLs inside the *<head>* element of your website
via the *script* and *link* tags:
.. code-block:: html
<link rel="stylesheet" type="text/css" media="screen" href="https://cdn.conversejs.org/10.1.6/dist/converse.min.css">
<script src="https://cdn.conversejs.org/10.1.6/dist/converse.min.js" charset="utf-8"></script>
<link rel="stylesheet" type="text/css" media="screen" href="https://cdn.conversejs.org/10.0.0/dist/converse.min.css">
<script src="https://cdn.conversejs.org/10.0.0/dist/converse.min.js" charset="utf-8"></script>
Option 2: Download the builds from Github

View File

@ -202,4 +202,5 @@ Storing the SASL SCRAM-SHA1 hash in IndexedDB
Another suggestion that's been suggested is to store the SCRAM-SHA1 computed
``clientKey`` in localStorage and to use that upon page reload to log the user in again.
This has been implemented since version 10, see documentation on `reuse_scram_keys <https://conversejs.org/docs/html/configuration.html#reuse-scram-keys>`_
We might implement this feature in core Converse.js eventually.
As always, contributions welcome!

View File

@ -240,11 +240,11 @@
<div class="sponsors">
<h2>Converse is supported by:</h2>
<ul >
<li><a href="https://bairesdev.com/sponsoring-open-source-projects/?utm_source=conversejs" target="_blank" rel="noopener"><img style="width: 13em" src="/media/logos/bairesdev-primary.png" alt="BairesDev"></a></li>
<li><a href="https://blokt.com?utm_source=conversejs" target="_blank" rel="noopener"><img style="width: 12em" src="/logo/blokt.png" alt="Blokt Crypto & Privacy"></a></li>
<li><a href="https://primesound.org/?utm_source=conversejs" target="_blank" rel="noopener"><img style="width: 10em" src="/media/logos/primesound.png" alt="Prime Sound"></a></li>
<li><a href="https://www.keycdn.com?utm_source=conversejs" target="_blank" rel="noopener"><img style="height: 3em" src="/logo/keycdn.svg" alt="KeyCDN"></a></li>
<li><a href="https://weblate.org?utm_source=conversejs" target="_blank" rel="noopener"><img style="height: 2.6em" src="/logo/weblate-button.svg" alt="Weblate"></a></li>
<li><a href="https://www.codefirst.co.uk?utm_source=conversejs" target="_blank" rel="noopener"><img style="width: 12em; padding-top: 0.5em" src="/logo/codefirst.png" alt="Codefirst"></a></li>
<li><a href="https://blokt.com?utm_source=conversejs" target="_blank" rel="noopener"><img style="width: 12em" src="/logo/blokt.png" alt="Blokt Crypto & Privacy"></a></li>
<li><a href="https://primesound.org/?utm_source=conversejs" target="_blank" rel="noopener"><img style="width: 10em" src="/media/logos/primesound.png" alt="Prime Sound"></a></li>
</ul>
</div>

View File

@ -11,17 +11,17 @@
<!-- These files are NOT needed when using converse.js in your own project. -->
<link rel="shortcut icon" type="image/ico" href="images/favicon.ico"/>
<link type="text/css" rel="stylesheet" media="screen" href="https://cdn.conversejs.org/10.1.6/css/font-awesome.min.css" />
<link type="text/css" rel="stylesheet" media="screen" href="https://cdn.conversejs.org/10.1.6/css/website.min.css" />
<link type="text/css" rel="stylesheet" media="screen" href="https://cdn.conversejs.org/10.0.0/css/font-awesome.min.css" />
<link type="text/css" rel="stylesheet" media="screen" href="https://cdn.conversejs.org/10.0.0/css/website.min.css" />
<noscript><p><img src="//stats.opkode.com/piwik.php?idsite=1" style="border:0;" alt="" /></p></noscript>
<script type="text/javascript" src="/src/website.js"></script>
<script type="text/javascript" src="analytics.js"></script>
<!-- *********************************************************************** -->
<![if gte IE 11]>
<link type="text/css" rel="stylesheet" media="screen" href="https://cdn.conversejs.org/10.1.6/css/converse.min.css" />
<link type="text/css" rel="stylesheet" media="screen" href="https://cdn.conversejs.org/10.0.0/css/converse.min.css" />
<script src="https://cdn.conversejs.org/3rdparty/libsignal-protocol.min.js"></script>
<script src="https://cdn.conversejs.org/10.1.6/dist/converse.min.js"></script>
<script src="https://cdn.conversejs.org/10.0.0/dist/converse.min.js"></script>
<![endif]>
</head>
@ -66,7 +66,7 @@
<table id="jslicense-labels1" style="width: 100%">
<tr>
<td>
<a href="https://cdn.conversejs.org/10.1.6/dist/converse.min.js">converse.min.js</a>
<a href="https://cdn.conversejs.org/10.0.0/dist/converse.min.js">converse.min.js</a>
</td>
<td>
<a href="https://www.mozilla.org/en-US/MPL/2.0/">MPL-2.0</a>

View File

@ -103,9 +103,6 @@ module.exports = function(config) {
{ pattern: "src/plugins/omemo/tests/media-sharing.js", type: 'module' },
{ pattern: "src/plugins/omemo/tests/muc.js", type: 'module' },
{ pattern: "src/plugins/omemo/tests/omemo.js", type: 'module' },
{ pattern: "src/plugins/profile/tests/password-reset.js", type: 'module' },
{ pattern: "src/plugins/profile/tests/profile.js", type: 'module' },
{ pattern: "src/plugins/profile/tests/status.js", type: 'module' },
{ pattern: "src/plugins/push/tests/push.js", type: 'module' },
{ pattern: "src/plugins/register/tests/register.js", type: 'module' },
{ pattern: "src/plugins/roomslist/tests/roomslist.js", type: 'module' },

7
lerna.json Normal file
View File

@ -0,0 +1,7 @@
{
"packages": [
".",
"src/*"
],
"version": "4.0.3"
}

BIN
logo/codefirst.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,34 +1,19 @@
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" id="svg108" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"
xml:space="preserve" version="1.1"
viewBox="0 0 376 311" height="20%" width="10rem"
sodipodi:docname="chapril-logo.svg" inkscape:version="0.92.1 r15371">
<sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="640" inkscape:window-height="480" id="namedview16" showgrid="false" inkscape:zoom="0.75884244" inkscape:cx="188" inkscape:cy="155.5" inkscape:window-x="0" inkscape:window-y="24" inkscape:window-maximized="0" inkscape:current-layer="svg108"/>
<metadata id="metadata114">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
<cc:license rdf:resource="GFDL version 1.3 ou ultérieure, Creative Commons By Sa version 2.0 ou ultérieure, Licence Art Libre version 1.3 ou ultérieure"/>
<dc:creator>
<cc:Agent>
<dc:title>Antoine BARDELLI</dc:title>
</cc:Agent>
</dc:creator>
</cc:Work>
</rdf:RDF>
</metadata>
<defs id="defs112"/>
<g id="g250" style="clip-rule:evenodd;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41420996" transform="translate(4.6079614e-7,-4.4571451e-6)">
<path id="path124" style="clip-rule:evenodd;fill:#005184;fill-rule:nonzero;stroke-linejoin:round;stroke-miterlimit:1.41420996" d="m 96.70711,209.69029 c -2.742,1.114 -5.399,1.8 -7.885,2.057 -0.771,0.085 -1.629,0.171 -2.4,0.171 -2.571,0 -5.314,-0.428 -8.228,-1.286 -2.486,-0.857 -4.628,-2.314 -6.514,-4.371 -1.886,-1.8 -3.343,-3.942 -4.371,-6.428 -0.943,-2.486 -1.457,-5.057 -1.543,-7.714 0,-0.086 0,-0.257 0,-0.343 0,-2.485 0.514,-4.971 1.543,-7.371 1.028,-2.314 2.485,-4.457 4.371,-6.428 1.886,-2.057 4.028,-3.514 6.514,-4.371 2.828,-1.029 5.571,-1.543 8.142,-1.543 0.257,0 0.6,0 0.857,0 3.686,0.171 6.857,1.028 9.686,2.657 -0.086,1.457 -0.343,3.514 -0.6,6.343 -2.743,-1.372 -5.4,-2.143 -7.971,-2.315 -1.972,-0.171 -3.943,0.086 -6,0.772 -1.629,0.514 -3.171,1.457 -4.457,2.914 -1.286,1.371 -2.228,2.828 -2.914,4.457 -0.772,1.628 -1.114,3.342 -1.114,5.142 0.085,1.886 0.428,3.6 1.114,5.229 0.686,1.714 1.628,3.171 2.914,4.457 1.286,1.285 2.828,2.228 4.457,2.914 2.486,0.685 4.543,1.028 6.343,1.028 3.342,-0.343 5.999,-1.2 8.056,-2.657 0,1.457 0,3.686 0,6.686 z"/>
<path id="path126" style="clip-rule:evenodd;fill:#005184;fill-rule:nonzero;stroke-linejoin:round;stroke-miterlimit:1.41420996" d="m 137.33411,184.91929 c 0,1.8 0,3.515 0,5.314 0,2.657 0,5.4 -0.086,8.057 0,4.457 0,8.743 0,12.771 -1.714,0 -4.285,0 -7.799,0 0,-3.857 0,-11.571 0.085,-23.142 0.086,-1.114 -0.171,-2.228 -0.6,-3.257 -0.428,-1.028 -1.028,-1.971 -1.8,-2.742 -0.942,-0.943 -2.142,-1.715 -3.599,-2.143 -1.029,-0.257 -2.229,-0.429 -3.6,-0.343 -1.115,0.086 -2.229,0.343 -3.257,0.686 -1.029,0.428 -2.143,1.028 -3.343,1.8 -0.6,0.6 -1.029,1.2 -1.457,1.8 v 27.341 h -8.057 v -62.997 h 8.057 v 29.313 c 0.943,-1.114 1.8,-1.971 2.571,-2.486 1.629,-0.942 3,-1.542 4.2,-1.885 1.971,-0.429 3.771,-0.6 5.571,-0.6 2.143,0.085 3.943,0.343 5.4,0.943 1.543,0.6 2.914,1.542 4.028,2.742 2.4,2.572 3.6,5.486 3.686,8.828 z"/>
<path id="path128" style="clip-rule:evenodd;fill-rule:nonzero;stroke-linejoin:round;stroke-miterlimit:1.41420996" d="m 169.38911,146.86429 c 4.714,10.714 14.228,32.056 28.37,64.111 -1.371,0 -4.114,0 -8.313,0 -1.286,-2.657 -3.772,-7.971 -7.543,-15.942 -4.028,0 -12.085,0 -24.17,0 -1.114,2.657 -3.514,7.971 -7.028,15.942 -1.457,0 -4.2,0 -8.4,0 4.543,-10.713 13.542,-32.055 27.084,-64.111 z m 9.171,40.884 c -1.457,-3.686 -4.542,-10.885 -9.085,-21.77 -1.457,3.599 -4.285,10.885 -8.571,21.77 3,0 8.828,0 17.656,0 z"/>
<path id="path130" style="clip-rule:evenodd;fill-rule:nonzero;stroke-linejoin:round;stroke-miterlimit:1.41420996" d="m 212.15911,199.83329 c 0.257,0.429 0.6,0.771 0.857,1.114 1.114,1.457 2.657,2.743 4.457,3.686 1.457,0.685 3.085,1.028 4.8,1.028 0.085,0 0.257,0 0.428,0 1.8,0.086 3.514,-0.257 5.143,-1.028 1.628,-0.6 3.171,-1.629 4.457,-2.914 1.285,-1.286 2.228,-2.743 2.914,-4.457 0.685,-1.543 1.028,-3.343 1.028,-5.314 0.086,-1.715 -0.257,-3.429 -1.028,-5.057 -0.6,-1.629 -1.629,-3.171 -2.914,-4.457 -1.286,-1.286 -2.743,-2.229 -4.457,-2.914 -1.115,-0.515 -2.572,-0.772 -4.2,-0.772 -0.172,0 -0.343,0 -0.429,0 -2.657,0.086 -4.714,0.429 -5.999,1.2 -3.429,1.8 -5.143,3.857 -5.143,6.171 0,3 0,7.629 0.086,13.714 z m 0,8.314 c -0.086,3.343 -0.086,10.028 -0.086,20.056 -1.286,0 -4.028,0 -8.057,0 v -54.769 c 2.657,0 5.4,0 8.057,0 0.086,1.286 0.086,2.4 0.086,3.257 0.857,-1.114 2.314,-2.142 4.457,-3.085 2.4,-1.029 4.971,-1.543 7.714,-1.543 2.657,0 5.228,0.514 7.628,1.543 2.485,1.028 4.714,2.485 6.514,4.371 1.971,1.886 3.428,4.028 4.371,6.428 1.028,2.486 1.543,4.971 1.543,7.543 0,0.085 0,0.085 0,0.171 0,2.657 -0.515,5.228 -1.543,7.714 -1.029,2.486 -2.486,4.628 -4.371,6.428 -1.886,1.972 -4.029,3.429 -6.514,4.371 -2.486,1.029 -5.057,1.543 -7.628,1.543 -2.657,-0.085 -5.229,-0.6 -7.714,-1.543 -1.372,-0.514 -2.829,-1.371 -4.457,-2.485 z"/>
<path id="path132" style="clip-rule:evenodd;fill-rule:nonzero;stroke-linejoin:round;stroke-miterlimit:1.41420996" d="m 271.04211,180.46329 c 0,-0.086 -0.686,-0.172 -2.143,-0.429 -2.743,-0.086 -5.228,0.857 -7.371,2.828 -0.857,0.772 -1.543,1.886 -2.057,3.343 -0.343,1.371 -0.515,2.829 -0.6,4.457 0,4.543 0,11.399 0,20.399 -1.286,0 -3.943,0 -7.971,0 0,-6.257 0,-18.856 -0.086,-37.627 1.286,0 3.943,0 7.8,0 0.085,0.772 0.085,2.4 0.085,4.8 0,0.514 0,1.543 -0.085,3.171 1.028,-4.199 3.085,-6.856 6.085,-7.971 1.2,-0.343 2.4,-0.514 3.686,-0.514 0.857,-0.086 1.714,0 2.657,0.171 0,1.629 0,4.115 0,7.372 z"/>
<path id="path134" style="clip-rule:evenodd;fill:#005184;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41420996" d="m 277.47011,163.23529 c 0,-1.372 0.514,-2.4 1.457,-3.171 0.943,-0.772 1.971,-1.2 3.085,-1.2 0.086,0 0.172,0 0.258,0 1.114,0 2.057,0.342 2.999,1.028 0.943,0.771 1.372,1.8 1.372,3.257 0,1.457 -0.429,2.571 -1.372,3.428 -0.942,0.858 -2.057,1.286 -3.257,1.286 -1.114,0 -2.142,-0.428 -3.085,-1.2 -0.943,-0.771 -1.457,-1.971 -1.457,-3.428 z"/>
<path style="clip-rule:evenodd;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41420996" id="path136" d="m 278.15611,173.34929 c 1.371,0 4.028,0 8.056,0 0,6.342 0,18.942 0,37.883 -1.285,0 -3.942,0 -7.885,0 0,-6.256 -0.086,-18.941 -0.171,-37.883 z"/>
<path id="path138" style="clip-rule:evenodd;fill-rule:nonzero;stroke-linejoin:round;stroke-miterlimit:1.41420996" d="m 295.55511,146.26429 v 64.797 c 1.285,0 3.942,0 7.885,0 0,-10.799 0,-32.398 0,-64.797 -1.286,0 -3.943,0 -7.885,0 z"/>
<path id="path144" style="clip-rule:evenodd;fill:#005184;fill-rule:nonzero;stroke-linejoin:round;stroke-miterlimit:1.41420996" d="m 79.90511,45.008286 c 0.902,0.305 0.903,0.306 1.74,0.76 0,0 2.36,1.6 3.541,2.399 16.192,11.056 46.905,36.807 46.905,36.807 l -0.62,0.933 c 0,0 -30.962,-17.269 -47.812,-25.837 -0.154,2.104 -0.309,4.208 -0.465,6.312 l -0.126,1.689 c -0.635,7.993 -1.295,15.97 -2,23.945 l -0.096,1.076 c -0.824,8.583004 -1.124,17.369004 -3.069,25.573004 -1.802,7.601 -10.27,13.477 -15.485,20.114 -10.856,13.816 -19.361,29.582 -21.64,47.603 -2.579,20.389 5.773,40.853 21.062,55.815 5.92,5.794 12.534,10.853 19.323,15.617 l -3.768,5.852 c -20.581,-12.139 -40.182,-28.618 -47.181,-51.981 -6.615,-22.081 -1.608,-47.917 10.144,-67.278 6.689,-11.02 14.834,-21.098 23.413,-30.201 0,0 0.585,-2.521 0.874,-4.27 3.14,-19.016004 5.383,-38.057004 7.92,-57.227004 l 0.274,-2.074 0.28,-1.686 c 0.307,-0.8 0.337,-1.033 0.841,-1.737 1.119,-1.562 2.062,-2.818 5.945,-2.204 z"/>
<path id="path146" style="clip-rule:evenodd;fill:#005184;fill-rule:nonzero;stroke-linejoin:round;stroke-miterlimit:1.41420996" d="m 300.65411,44.858286 c 2.74,0.486 2.778,0.936 3.448,1.587 2.555,2.484 2.191,7.238 2.75,11.471 2.467,18.612 4.172,37.353 7.871,55.935004 0,0 3.607,4.013 6.264,7.099 11.064,12.849 21.019,26.411 26.284,43.975 6.27,20.92 3.585,41.82 -7.633,60.315 -9.349,15.414 -24.268,26.668 -39.964,35.962 l -0.593,-1.853 c 17.279,-12.08 34.133,-26.972 39.318,-48.24 3.677,-15.083 -0.809,-32.515 -6.571,-47.292 -5.094,-13.065 -14.589,-24.209 -23.233,-34.394 -1.789,-2.107 -3.603,-4.192 -5.472,-6.229 0,0 -2.204,-3.069 -2.806,-6.739 -2.582,-15.749 -3.416,-31.540004 -4.699,-47.326004 0,0 -0.34,-4.424 -0.589,-7.754 l -0.098,-1.322 -30.582,18.428 -15.221,9.11 -0.801,0.477 -3.955,-6.151 0.767,-0.531 14.605,-10.067 37.28,-25.534 c 0,0 1.172,-0.939 3.63,-0.927 z m 14.362,70.312004 c 0.178,0.639 -0.049,-0.186 0,0 z"/>
</g>
</svg>
<svg class="converse-svg-logo"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 364 364">
<title>Converse</title>
<g class="cls-1" id="g904">
<g data-name="Layer 2">
<g data-name="Layer 7">
<path
class="cls-3"
d="M221.46,103.71c0,18.83-29.36,18.83-29.12,0C192.1,84.88,221.46,84.88,221.46,103.71Z" />
<path
class="cls-4"
d="M179.9,4.15A175.48,175.48,0,1,0,355.38,179.63,175.48,175.48,0,0,0,179.9,4.15Zm-40.79,264.5c-.23-17.82,27.58-17.82,27.58,0S138.88,286.48,139.11,268.65ZM218.6,168.24A79.65,79.65,0,0,1,205.15,174a12.76,12.76,0,0,0-6.29,4.65L167.54,222a1.36,1.36,0,0,1-2.46-.8v-35.8a2.58,2.58,0,0,0-3.06-2.53c-15.43,3-30.23,7.7-42.73,19.94-38.8,38-29.42,105.69,16.09,133.16a162.25,162.25,0,0,1-91.47-67.27C-3.86,182.26,34.5,47.25,138.37,25.66c46.89-9.75,118.25,5.16,123.73,62.83C265.15,120.64,246.56,152.89,218.6,168.24Z" />
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,19 +0,0 @@
<svg class="converse-svg-logo"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 364 364">
<title>Converse</title>
<g class="cls-1" id="g904">
<g data-name="Layer 2">
<g data-name="Layer 7">
<path
class="cls-3"
d="M221.46,103.71c0,18.83-29.36,18.83-29.12,0C192.1,84.88,221.46,84.88,221.46,103.71Z" />
<path
class="cls-4"
d="M179.9,4.15A175.48,175.48,0,1,0,355.38,179.63,175.48,175.48,0,0,0,179.9,4.15Zm-40.79,264.5c-.23-17.82,27.58-17.82,27.58,0S138.88,286.48,139.11,268.65ZM218.6,168.24A79.65,79.65,0,0,1,205.15,174a12.76,12.76,0,0,0-6.29,4.65L167.54,222a1.36,1.36,0,0,1-2.46-.8v-35.8a2.58,2.58,0,0,0-3.06-2.53c-15.43,3-30.23,7.7-42.73,19.94-38.8,38-29.42,105.69,16.09,133.16a162.25,162.25,0,0,1-91.47-67.27C-3.86,182.26,34.5,47.25,138.37,25.66c46.89-9.75,118.25,5.16,123.73,62.83C265.15,120.64,246.56,152.89,218.6,168.24Z" />
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,46 +1,108 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="chapril-logo"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"
class="converse-svg-logo"
xml:space="preserve" version="1.1"
viewBox="0 0 376 311" height="20%" width="6rem"
sodipodi:docname="chapril-logo.svg" inkscape:version="0.92.1 r15371">
<sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="640" inkscape:window-height="480" id="namedview16" showgrid="false" inkscape:zoom="0.75884244" inkscape:cx="188" inkscape:cy="155.5" inkscape:window-x="0" inkscape:window-y="24" inkscape:window-maximized="0" inkscape:current-layer="svg108"/>
<metadata id="metadata114">
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
class="converse-svg-logo"
viewBox="0 0 364 364"
version="1.1"
id="svg13"
sodipodi:docname="conversejs-with-byline.svg"
inkscape:version="0.92.2 5c3e80d, 2017-08-06">
<metadata
id="metadata19">
<rdf:RDF>
<cc:Work rdf:about="">
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
<cc:license rdf:resource="GFDL version 1.3 ou ultérieure, Creative Commons By Sa version 2.0 ou ultérieure, Licence Art Libre version 1.3 ou ultérieure"/>
<dc:creator>
<cc:Agent>
<dc:title>Antoine BARDELLI</dc:title>
</cc:Agent>
</dc:creator>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Converse</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs id="defs112"/>
<g id="g250" style="clip-rule:evenodd;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41420996" transform="translate(4.6079614e-7,-4.4571451e-6)">
<path id="path124" style="clip-rule:evenodd;fill:#005184;fill-rule:nonzero;stroke-linejoin:round;stroke-miterlimit:1.41420996" d="m 96.70711,209.69029 c -2.742,1.114 -5.399,1.8 -7.885,2.057 -0.771,0.085 -1.629,0.171 -2.4,0.171 -2.571,0 -5.314,-0.428 -8.228,-1.286 -2.486,-0.857 -4.628,-2.314 -6.514,-4.371 -1.886,-1.8 -3.343,-3.942 -4.371,-6.428 -0.943,-2.486 -1.457,-5.057 -1.543,-7.714 0,-0.086 0,-0.257 0,-0.343 0,-2.485 0.514,-4.971 1.543,-7.371 1.028,-2.314 2.485,-4.457 4.371,-6.428 1.886,-2.057 4.028,-3.514 6.514,-4.371 2.828,-1.029 5.571,-1.543 8.142,-1.543 0.257,0 0.6,0 0.857,0 3.686,0.171 6.857,1.028 9.686,2.657 -0.086,1.457 -0.343,3.514 -0.6,6.343 -2.743,-1.372 -5.4,-2.143 -7.971,-2.315 -1.972,-0.171 -3.943,0.086 -6,0.772 -1.629,0.514 -3.171,1.457 -4.457,2.914 -1.286,1.371 -2.228,2.828 -2.914,4.457 -0.772,1.628 -1.114,3.342 -1.114,5.142 0.085,1.886 0.428,3.6 1.114,5.229 0.686,1.714 1.628,3.171 2.914,4.457 1.286,1.285 2.828,2.228 4.457,2.914 2.486,0.685 4.543,1.028 6.343,1.028 3.342,-0.343 5.999,-1.2 8.056,-2.657 0,1.457 0,3.686 0,6.686 z"/>
<path id="path126" style="clip-rule:evenodd;fill:#005184;fill-rule:nonzero;stroke-linejoin:round;stroke-miterlimit:1.41420996" d="m 137.33411,184.91929 c 0,1.8 0,3.515 0,5.314 0,2.657 0,5.4 -0.086,8.057 0,4.457 0,8.743 0,12.771 -1.714,0 -4.285,0 -7.799,0 0,-3.857 0,-11.571 0.085,-23.142 0.086,-1.114 -0.171,-2.228 -0.6,-3.257 -0.428,-1.028 -1.028,-1.971 -1.8,-2.742 -0.942,-0.943 -2.142,-1.715 -3.599,-2.143 -1.029,-0.257 -2.229,-0.429 -3.6,-0.343 -1.115,0.086 -2.229,0.343 -3.257,0.686 -1.029,0.428 -2.143,1.028 -3.343,1.8 -0.6,0.6 -1.029,1.2 -1.457,1.8 v 27.341 h -8.057 v -62.997 h 8.057 v 29.313 c 0.943,-1.114 1.8,-1.971 2.571,-2.486 1.629,-0.942 3,-1.542 4.2,-1.885 1.971,-0.429 3.771,-0.6 5.571,-0.6 2.143,0.085 3.943,0.343 5.4,0.943 1.543,0.6 2.914,1.542 4.028,2.742 2.4,2.572 3.6,5.486 3.686,8.828 z"/>
<path id="path128" style="clip-rule:evenodd;fill-rule:nonzero;stroke-linejoin:round;stroke-miterlimit:1.41420996" d="m 169.38911,146.86429 c 4.714,10.714 14.228,32.056 28.37,64.111 -1.371,0 -4.114,0 -8.313,0 -1.286,-2.657 -3.772,-7.971 -7.543,-15.942 -4.028,0 -12.085,0 -24.17,0 -1.114,2.657 -3.514,7.971 -7.028,15.942 -1.457,0 -4.2,0 -8.4,0 4.543,-10.713 13.542,-32.055 27.084,-64.111 z m 9.171,40.884 c -1.457,-3.686 -4.542,-10.885 -9.085,-21.77 -1.457,3.599 -4.285,10.885 -8.571,21.77 3,0 8.828,0 17.656,0 z"/>
<path id="path130" style="clip-rule:evenodd;fill-rule:nonzero;stroke-linejoin:round;stroke-miterlimit:1.41420996" d="m 212.15911,199.83329 c 0.257,0.429 0.6,0.771 0.857,1.114 1.114,1.457 2.657,2.743 4.457,3.686 1.457,0.685 3.085,1.028 4.8,1.028 0.085,0 0.257,0 0.428,0 1.8,0.086 3.514,-0.257 5.143,-1.028 1.628,-0.6 3.171,-1.629 4.457,-2.914 1.285,-1.286 2.228,-2.743 2.914,-4.457 0.685,-1.543 1.028,-3.343 1.028,-5.314 0.086,-1.715 -0.257,-3.429 -1.028,-5.057 -0.6,-1.629 -1.629,-3.171 -2.914,-4.457 -1.286,-1.286 -2.743,-2.229 -4.457,-2.914 -1.115,-0.515 -2.572,-0.772 -4.2,-0.772 -0.172,0 -0.343,0 -0.429,0 -2.657,0.086 -4.714,0.429 -5.999,1.2 -3.429,1.8 -5.143,3.857 -5.143,6.171 0,3 0,7.629 0.086,13.714 z m 0,8.314 c -0.086,3.343 -0.086,10.028 -0.086,20.056 -1.286,0 -4.028,0 -8.057,0 v -54.769 c 2.657,0 5.4,0 8.057,0 0.086,1.286 0.086,2.4 0.086,3.257 0.857,-1.114 2.314,-2.142 4.457,-3.085 2.4,-1.029 4.971,-1.543 7.714,-1.543 2.657,0 5.228,0.514 7.628,1.543 2.485,1.028 4.714,2.485 6.514,4.371 1.971,1.886 3.428,4.028 4.371,6.428 1.028,2.486 1.543,4.971 1.543,7.543 0,0.085 0,0.085 0,0.171 0,2.657 -0.515,5.228 -1.543,7.714 -1.029,2.486 -2.486,4.628 -4.371,6.428 -1.886,1.972 -4.029,3.429 -6.514,4.371 -2.486,1.029 -5.057,1.543 -7.628,1.543 -2.657,-0.085 -5.229,-0.6 -7.714,-1.543 -1.372,-0.514 -2.829,-1.371 -4.457,-2.485 z"/>
<path id="path132" style="clip-rule:evenodd;fill-rule:nonzero;stroke-linejoin:round;stroke-miterlimit:1.41420996" d="m 271.04211,180.46329 c 0,-0.086 -0.686,-0.172 -2.143,-0.429 -2.743,-0.086 -5.228,0.857 -7.371,2.828 -0.857,0.772 -1.543,1.886 -2.057,3.343 -0.343,1.371 -0.515,2.829 -0.6,4.457 0,4.543 0,11.399 0,20.399 -1.286,0 -3.943,0 -7.971,0 0,-6.257 0,-18.856 -0.086,-37.627 1.286,0 3.943,0 7.8,0 0.085,0.772 0.085,2.4 0.085,4.8 0,0.514 0,1.543 -0.085,3.171 1.028,-4.199 3.085,-6.856 6.085,-7.971 1.2,-0.343 2.4,-0.514 3.686,-0.514 0.857,-0.086 1.714,0 2.657,0.171 0,1.629 0,4.115 0,7.372 z"/>
<path id="path134" style="clip-rule:evenodd;fill:#005184;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41420996" d="m 277.47011,163.23529 c 0,-1.372 0.514,-2.4 1.457,-3.171 0.943,-0.772 1.971,-1.2 3.085,-1.2 0.086,0 0.172,0 0.258,0 1.114,0 2.057,0.342 2.999,1.028 0.943,0.771 1.372,1.8 1.372,3.257 0,1.457 -0.429,2.571 -1.372,3.428 -0.942,0.858 -2.057,1.286 -3.257,1.286 -1.114,0 -2.142,-0.428 -3.085,-1.2 -0.943,-0.771 -1.457,-1.971 -1.457,-3.428 z"/>
<path style="clip-rule:evenodd;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41420996" id="path136" d="m 278.15611,173.34929 c 1.371,0 4.028,0 8.056,0 0,6.342 0,18.942 0,37.883 -1.285,0 -3.942,0 -7.885,0 0,-6.256 -0.086,-18.941 -0.171,-37.883 z"/>
<path id="path138" style="clip-rule:evenodd;fill-rule:nonzero;stroke-linejoin:round;stroke-miterlimit:1.41420996" d="m 295.55511,146.26429 v 64.797 c 1.285,0 3.942,0 7.885,0 0,-10.799 0,-32.398 0,-64.797 -1.286,0 -3.943,0 -7.885,0 z"/>
<path id="path144" style="clip-rule:evenodd;fill:#005184;fill-rule:nonzero;stroke-linejoin:round;stroke-miterlimit:1.41420996" d="m 79.90511,45.008286 c 0.902,0.305 0.903,0.306 1.74,0.76 0,0 2.36,1.6 3.541,2.399 16.192,11.056 46.905,36.807 46.905,36.807 l -0.62,0.933 c 0,0 -30.962,-17.269 -47.812,-25.837 -0.154,2.104 -0.309,4.208 -0.465,6.312 l -0.126,1.689 c -0.635,7.993 -1.295,15.97 -2,23.945 l -0.096,1.076 c -0.824,8.583004 -1.124,17.369004 -3.069,25.573004 -1.802,7.601 -10.27,13.477 -15.485,20.114 -10.856,13.816 -19.361,29.582 -21.64,47.603 -2.579,20.389 5.773,40.853 21.062,55.815 5.92,5.794 12.534,10.853 19.323,15.617 l -3.768,5.852 c -20.581,-12.139 -40.182,-28.618 -47.181,-51.981 -6.615,-22.081 -1.608,-47.917 10.144,-67.278 6.689,-11.02 14.834,-21.098 23.413,-30.201 0,0 0.585,-2.521 0.874,-4.27 3.14,-19.016004 5.383,-38.057004 7.92,-57.227004 l 0.274,-2.074 0.28,-1.686 c 0.307,-0.8 0.337,-1.033 0.841,-1.737 1.119,-1.562 2.062,-2.818 5.945,-2.204 z"/>
<path id="path146" style="clip-rule:evenodd;fill:#005184;fill-rule:nonzero;stroke-linejoin:round;stroke-miterlimit:1.41420996" d="m 300.65411,44.858286 c 2.74,0.486 2.778,0.936 3.448,1.587 2.555,2.484 2.191,7.238 2.75,11.471 2.467,18.612 4.172,37.353 7.871,55.935004 0,0 3.607,4.013 6.264,7.099 11.064,12.849 21.019,26.411 26.284,43.975 6.27,20.92 3.585,41.82 -7.633,60.315 -9.349,15.414 -24.268,26.668 -39.964,35.962 l -0.593,-1.853 c 17.279,-12.08 34.133,-26.972 39.318,-48.24 3.677,-15.083 -0.809,-32.515 -6.571,-47.292 -5.094,-13.065 -14.589,-24.209 -23.233,-34.394 -1.789,-2.107 -3.603,-4.192 -5.472,-6.229 0,0 -2.204,-3.069 -2.806,-6.739 -2.582,-15.749 -3.416,-31.540004 -4.699,-47.326004 0,0 -0.34,-4.424 -0.589,-7.754 l -0.098,-1.322 -30.582,18.428 -15.221,9.11 -0.801,0.477 -3.955,-6.151 0.767,-0.531 14.605,-10.067 37.28,-25.534 c 0,0 1.172,-0.939 3.63,-0.927 z m 14.362,70.312004 c 0.178,0.639 -0.049,-0.186 0,0 z"/>
<defs
id="defs17">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 182 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="364 : 182 : 1"
inkscape:persp3d-origin="182 : 121.33333 : 1"
id="perspective2147" />
</defs>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1434"
inkscape:window-height="951"
id="namedview15"
showgrid="false"
inkscape:zoom="1.8338154"
inkscape:cx="225.17086"
inkscape:cy="243.79827"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg13" />
<title
id="title2">Converse</title>
<g
class="cls-1"
id="g904"
transform="matrix(0.2441072,0,0,0.2441072,12.20969,55.55023)">
<g
data-name="Layer 2"
id="g10">
<g
data-name="Layer 7"
id="g8">
<path
class="cls-3"
d="m 221.46,103.71 c 0,18.83 -29.36,18.83 -29.12,0 -0.24,-18.83 29.12,-18.83 29.12,0 z"
id="path4"
inkscape:connector-curvature="0" />
<path
class="cls-4"
d="M 179.9,4.15 C 108.92504,4.15 44.938239,46.904566 17.778836,112.4757 -9.3805118,178.0467 5.6365472,253.52014 55.823205,303.70679 106.00986,353.89345 181.4833,368.91051 247.0543,341.75116 312.62543,314.59176 355.38,250.60496 355.38,179.63 355.38,82.715072 276.81493,4.15 179.9,4.15 Z m -40.79,264.5 c -0.23,-17.82 27.58,-17.82 27.58,0 0,17.82 -27.81,17.83 -27.58,0 z M 218.6,168.24 c -4.29711,2.32859 -8.79944,4.25673 -13.45,5.76 -2.53177,0.85328 -12.23498,3.26952 -13.79313,5.4398 C 180.90809,189.252 165.08,221.2 165.08,221.2 v -35.8 c -0.003,-1.6153 -1.4729,-2.83052 -3.06,-2.53 -15.43,3 -30.23,7.7 -42.73,19.94 -38.8,38 -29.025098,103.71549 16.4849,131.18549 C 98.17801,323.32071 65.725789,295.74404 44.332966,263.03587 -3.4370336,176.59587 35.058475,51.159326 138.92848,29.569326 185.81848,19.819326 256.62,30.82 262.1,88.49 c 3.05,32.15 -15.54,64.4 -43.5,79.75 z"
id="path6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="sssssscsccccccccccccc" />
</g>
</g>
</g>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:50.63063431px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.10960984px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="110.04511"
y="98.826035"
id="text861"><tspan
sodipodi:role="line"
id="tspan859"
x="110.04511"
y="98.826035"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:50.63063431px;font-family:Baumans;-inkscape-font-specification:'Baumans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:2.10960984px">converse<tspan
style="fill:#a2a2a2;fill-opacity:1;stroke-width:2.10960984px"
id="tspan867">.js</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:18.49652481px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.77068853px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="124.58434"
y="128.44286"
id="text865"><tspan
sodipodi:role="line"
id="tspan863"
x="124.58434"
y="128.44286"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:18.49652481px;font-family:Muli;-inkscape-font-specification:'Muli, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#484848;fill-opacity:1;stroke-width:0.77068853px">messaging freedom</tspan></text>
</svg>

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -2,7 +2,7 @@
"short_name": "Converse",
"name": "Converse Chat",
"description": "Messaging Freedom",
"version": "10.1.6",
"version": "10.0.0",
"categories": ["social"],
"icons": [
{

View File

@ -19,9 +19,9 @@
<script type="text/javascript" src="analytics.js"></script>
<!-- *********************************************************************** -->
<link type="text/css" rel="stylesheet" media="screen" href="https://cdn.conversejs.org/10.1.6/dist/converse.min.css" />
<link type="text/css" rel="stylesheet" media="screen" href="https://cdn.conversejs.org/10.0.0/dist/converse.min.css" />
<script src="https://cdn.conversejs.org/3rdparty/libsignal-protocol.min.js"></script>
<script src="https://cdn.conversejs.org/10.1.6/dist/converse.min.js"></script>
<script src="https://cdn.conversejs.org/10.0.0/dist/converse.min.js"></script>
</head>
<body id="page-top" data-spy="scroll" class="converse-website">
@ -241,9 +241,11 @@
<h2>Converse is supported by:</h2>
<ul >
<li><a href="https://www.keycdn.com?utm_source=conversejs" target="_blank" rel="noopener"><img style="height: 3em" src="/logo/keycdn.svg" alt="KeyCDN"></a></li>
<li><a href="https://wikisuite.org/?utm_source=conversejs" target="_blank" rel="noopener"><img style="height: 4em" src="/logo/wikisuite-white.png" alt="WikiSuite"></a></li>
<li><a href="https://weblate.org?utm_source=conversejs" target="_blank" rel="noopener"><img style="height: 2.6em" src="/logo/weblate-button.svg" alt="Weblate"></a></li>
<li><a href="https://www.codefirst.co.uk?utm_source=conversejs" target="_blank" rel="noopener"><img style="width: 12em; padding-top: 0.5em" src="/logo/codefirst.png" alt="Codefirst"></a></li>
<li><a href="https://www.b1-systems.de?utm_source=conversejs" target="_blank" rel="noopener"><img style="height: 5em" src="/logo/b1-systems.svg" alt="B1 Systems"></a></li>
<li><a href="https://blokt.com?utm_source=conversejs" target="_blank" rel="noopener"><img style="width: 12em" src="/logo/blokt.png" alt="Blokt Crypto & Privacy"></a></li>
<li><a href="https://primesound.org/?utm_source=conversejs" target="_blank" rel="noopener"><img style="width: 10em" src="/media/logos/primesound.png" alt="Prime Sound"></a></li>
</ul>
</div>

24697
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,46 +1,42 @@
{
"name": "converse.js",
"version": "10.1.6",
"version": "10.0.0",
"description": "Browser based XMPP chat client",
"browser": "dist/converse.js",
"module": "src/index.js",
"workspaces": [
"src/headless"
],
"module": "src/converse.js",
"files": [
"*.js",
"*.json",
"CHANGES.md",
"LICENSE.txt",
"README.md",
"COPYRIGHT",
"dist/",
"docs/**/*.md",
"docs/**/*.rst",
"docs/**/*.md",
"sass/**/*.scss",
"src/**/*.html",
"src/**/*.js",
"src/**/*.json",
"src/**/*.md",
"src/**/*.po",
"src/*.js",
"src/**/*.pot",
"src/**/*.scss",
"src/**/*.po",
"src/**/*.js",
"src/**/*.html",
"src/**/*.svg",
"src/**/*.md",
"src/**/*.txt",
"3rdparty/*.js"
"src/**/*.json"
],
"scripts": {
"build": "webpack --config webpack/webpack.build.js",
"lint": "eslint src/**/*.js",
"test": "karma start karma.conf",
"cdn": "ASSET_PATH=https://cdn.conversejs.org/dist/ npm run build",
"clean": "rm -rf node_modules dist *.zip src/headless/dist src/headless/node_modules",
"dev": "webpack --config webpack/webpack.build.js --mode=development",
"headless": "webpack --config webpack/webpack.headless.js",
"headless-dev": "webpack --config webpack/webpack.headless.js --mode=development",
"lerna": "lerna bootstrap --hoist --ignore-scripts",
"nodeps": "webpack --config webpack/webpack.nodeps.js",
"prepare": "npm run lerna && npm run build",
"serve": "webpack serve --config webpack/webpack.serve.js",
"watch": "webpack --watch --config webpack/webpack.build.js --mode=development",
"types": "tsc --declaration --emitDeclarationOnly --allowJs",
"check:types": "tsc --noEmit"
"watch": "webpack --watch --config webpack/webpack.build.js --mode=development"
},
"repository": {
"type": "git",
@ -69,13 +65,13 @@
"devDependencies": {
"@babel/cli": "^7.17.10",
"@babel/core": "^7.18.5",
"@babel/eslint-parser": "^7.18.9",
"@babel/preset-env": "^7.18.2",
"@converse/headless": "file:src/headless",
"@typescript-eslint/eslint-plugin": "^5.48.0",
"autoprefixer": "^10.4.5",
"babel-loader": "^9.1.0",
"babel-loader": "^8.2.5",
"bootstrap.native-loader": "2.0.0",
"clean-css-cli": "^5.6.2",
"clean-css-cli": "^5.6.0",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.1",
"eslint": "^8.21.0",
@ -84,50 +80,48 @@
"http-server": "^14.1.0",
"imports-loader": "^4.0.0",
"install": "^0.13.0",
"jsdoc": "^4.0.0",
"jsdoc": "^3.6.7",
"karma": "^6.3.19",
"karma-chrome-launcher": "^3.1.1",
"karma-cli": "^2.0.0",
"karma-jasmine": "^5.0.0",
"karma-jasmine-html-reporter": "^2.0.0",
"karma-jasmine-html-reporter": "^1.7.0",
"karma-webpack": "^5.0.0",
"lerna": "^5.6.2",
"mini-css-extract-plugin": "^2.6.0",
"minimist": "^1.2.6",
"po-loader": "0.7.0",
"po2json": "^1.0.0-beta-3",
"postcss": "^8.4.16",
"postcss": "^8.4.14",
"postcss-clean": "1.2.0",
"postcss-loader": "^7.0.1",
"prettierx": "^0.19.0",
"sass": "^1.51.0",
"sass-loader": "^13.1.0",
"sass-loader": "^12.6.0",
"style-loader": "^3.1.0",
"tsc": "^2.0.4",
"typescript": "^4.9.5",
"typescript-eslint-parser": "^22.0.0",
"uglify-js": "^3.17.4",
"webpack": "^5.86.0",
"webpack-cli": "^5.1.4",
"webpack": "^5.72.0",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^4.8.1",
"webpack-merge": "^5.8.0"
},
"dependencies": {
"@converse/openpromise": "^0.0.1",
"@converse/skeletor": "^0.0.8",
"@converse/skeletor": "0.0.7",
"bootstrap": "^4.6.0",
"bootstrap.native": "^2.0.27",
"client-compress": "^2.2.2",
"dayjs": "^1.11.8",
"dayjs": "1.11.6",
"dompurify": "^2.3.1",
"favico.js-slevomat": "^0.3.11",
"gifuct-js": "^2.1.2",
"filesize": "^7.0.0",
"jed": "1.1.1",
"lit": "^2.4.0",
"localforage-webextensionstorage-driver": "^3.0.0",
"lodash-es": "^4.17.21",
"pluggable.js": "^3.0.1",
"pluggable.js": "3.0.1",
"sizzle": "^2.3.5",
"sprintf-js": "^1.1.2",
"strophe.js": "^1.6.2",
"strophe.js": "1.6.0",
"urijs": "^1.19.10"
},
"resolutions": {

View File

@ -2,5 +2,6 @@
module.exports = {
plugins: [
require('autoprefixer'),
require('postcss-clean')
]
}

View File

@ -4,7 +4,7 @@
* @license Mozilla Public License (MPLv2)
*/
import "@converse/headless";
import "@converse/headless/headless";
import "./i18n/index.js";
import "shared/registry.js";
import { CustomElement } from 'shared/components/element';

View File

@ -49,7 +49,7 @@ const converse = {
*
* @memberOf converse
* @method load
* @param { object } settings A map of configuration-settings that are needed at load time.
* @param {object} settings A map of configuration-settings that are needed at load time.
* @example
* converse.load({assets_path: '/path/to/assets/'});
*/
@ -57,7 +57,7 @@ const converse = {
if (settings.assets_path) {
__webpack_public_path__ = settings.assets_path; // eslint-disable-line no-undef
}
require('./index.js');
require('./converse.js');
Object.keys(plugins).forEach(name => converse.plugins.add(name, plugins[name]));
return converse;
}

View File

@ -13,5 +13,5 @@ It's also installable with NPM/Yarn as [@converse/headless](https://www.npmjs.co
The main distribution of Converse relies on the headless build.
The file [src/headless/index.js](https://github.com/jcbrand/converse.js/blob/master/src/headless/index.js)
The file [src/headless/headless.js](https://github.com/jcbrand/converse.js/blob/master/src/headless/headless.js)
is used to determine which plugins are included in the build.

View File

@ -2,16 +2,753 @@
* @copyright The Converse.js contributors
* @license Mozilla Public License (MPLv2)
*/
import './shared/constants.js';
import _converse from './shared/_converse';
import URI from 'urijs';
import _converse from '@converse/headless/shared/_converse';
import advancedFormat from 'dayjs/plugin/advancedFormat';
import api from './shared/api/index.js';
import connection_api from '@converse/headless/shared/connection/api.js';
import dayjs from 'dayjs';
import i18n from './shared/i18n';
import i18n from '@converse/headless/shared/i18n';
import invoke from 'lodash-es/invoke';
import isFunction from 'lodash-es/isFunction';
import log from '@converse/headless/log.js';
import pluggable from 'pluggable.js/src/pluggable.js';
import sizzle from 'sizzle';
import u, { setUnloadEvent, replacePromise } from '@converse/headless/utils/core.js';
import { CHAT_STATES, KEYCODES } from './shared/constants.js';
import { Collection } from "@converse/skeletor/src/collection";
import { Connection, MockConnection } from '@converse/headless/shared/connection/index.js';
import { Events } from '@converse/skeletor/src/events.js';
import { Model } from '@converse/skeletor/src/model.js';
import { Strophe, $build, $iq, $msg, $pres } from 'strophe.js/src/strophe';
import { TimeoutError } from '@converse/headless/shared/errors';
import { getOpenPromise } from '@converse/openpromise';
import { html } from 'lit';
import { initAppSettings } from '@converse/headless/shared/settings/utils.js';
import { settings_api, user_settings_api } from '@converse/headless/shared/settings/api.js';
import { sprintf } from 'sprintf-js';
export { converse } from './shared/api/public.js';
export { _converse };
export { i18n };
export { api };
import {
attemptNonPreboundSession,
cleanup,
getConnectionServiceURL,
initClientConfig,
initPlugins,
initSessionStorage,
registerGlobalEventHandlers,
setUserJID,
} from './utils/init.js';
dayjs.extend(advancedFormat);
// Add Strophe Namespaces
Strophe.addNamespace('ACTIVITY', 'http://jabber.org/protocol/activity');
Strophe.addNamespace('CARBONS', 'urn:xmpp:carbons:2');
Strophe.addNamespace('CHATSTATES', 'http://jabber.org/protocol/chatstates');
Strophe.addNamespace('CSI', 'urn:xmpp:csi:0');
Strophe.addNamespace('DELAY', 'urn:xmpp:delay');
Strophe.addNamespace('EME', 'urn:xmpp:eme:0');
Strophe.addNamespace('FASTEN', 'urn:xmpp:fasten:0');
Strophe.addNamespace('FORWARD', 'urn:xmpp:forward:0');
Strophe.addNamespace('HINTS', 'urn:xmpp:hints');
Strophe.addNamespace('HTTPUPLOAD', 'urn:xmpp:http:upload:0');
Strophe.addNamespace('MAM', 'urn:xmpp:mam:2');
Strophe.addNamespace('MARKERS', 'urn:xmpp:chat-markers:0');
Strophe.addNamespace('MENTIONS', 'urn:xmpp:mmn:0');
Strophe.addNamespace('MESSAGE_CORRECT', 'urn:xmpp:message-correct:0');
Strophe.addNamespace('MODERATE', 'urn:xmpp:message-moderate:0');
Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick');
Strophe.addNamespace('OCCUPANTID', 'urn:xmpp:occupant-id:0');
Strophe.addNamespace('OMEMO', 'eu.siacs.conversations.axolotl');
Strophe.addNamespace('OUTOFBAND', 'jabber:x:oob');
Strophe.addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub');
Strophe.addNamespace('RAI', 'urn:xmpp:rai:0');
Strophe.addNamespace('RECEIPTS', 'urn:xmpp:receipts');
Strophe.addNamespace('REFERENCE', 'urn:xmpp:reference:0');
Strophe.addNamespace('REGISTER', 'jabber:iq:register');
Strophe.addNamespace('RETRACT', 'urn:xmpp:message-retract:0');
Strophe.addNamespace('ROSTERX', 'http://jabber.org/protocol/rosterx');
Strophe.addNamespace('RSM', 'http://jabber.org/protocol/rsm');
Strophe.addNamespace('SID', 'urn:xmpp:sid:0');
Strophe.addNamespace('SPOILER', 'urn:xmpp:spoiler:0');
Strophe.addNamespace('STANZAS', 'urn:ietf:params:xml:ns:xmpp-stanzas');
Strophe.addNamespace('STYLING', 'urn:xmpp:styling:0');
Strophe.addNamespace('VCARD', 'vcard-temp');
Strophe.addNamespace('VCARDUPDATE', 'vcard-temp:x:update');
Strophe.addNamespace('XFORM', 'jabber:x:data');
Strophe.addNamespace('XHTML', 'http://www.w3.org/1999/xhtml');
_converse.VERSION_NAME = "v10.0.0";
Object.assign(_converse, Events);
// Make converse pluggable
pluggable.enable(_converse, '_converse', 'pluggable');
/**
* ### The private API
*
* The private API methods are only accessible via the closured {@link _converse}
* object, which is only available to plugins.
*
* These methods are kept private (i.e. not global) because they may return
* sensitive data which should be kept off-limits to other 3rd-party scripts
* that might be running in the page.
*
* @namespace _converse.api
* @memberOf _converse
*/
export const api = _converse.api = {
connection: connection_api,
settings: settings_api,
/**
* Lets you trigger events, which can be listened to via
* {@link _converse.api.listen.on} or {@link _converse.api.listen.once}
* (see [_converse.api.listen](http://localhost:8000/docs/html/api/-_converse.api.listen.html)).
*
* Some events also double as promises and can be waited on via {@link _converse.api.waitUntil}.
*
* @method _converse.api.trigger
* @param {string} name - The event name
* @param {...any} [argument] - Argument to be passed to the event handler
* @param {object} [options]
* @param {boolean} [options.synchronous] - Whether the event is synchronous or not.
* When a synchronous event is fired, a promise will be returned
* by {@link _converse.api.trigger} which resolves once all the
* event handlers' promises have been resolved.
*/
async trigger (name) {
if (!_converse._events) {
return;
}
const args = Array.from(arguments);
const options = args.pop();
if (options && options.synchronous) {
const events = _converse._events[name] || [];
const event_args = args.splice(1);
await Promise.all(events.map(e => e.callback.apply(e.ctx, event_args)));
} else {
_converse.trigger.apply(_converse, arguments);
}
const promise = _converse.promises[name];
if (promise !== undefined) {
promise.resolve();
}
},
/**
* Triggers a hook which can be intercepted by registered listeners via
* {@link _converse.api.listen.on} or {@link _converse.api.listen.once}.
* (see [_converse.api.listen](http://localhost:8000/docs/html/api/-_converse.api.listen.html)).
* A hook is a special kind of event which allows you to intercept a data
* structure in order to modify it, before passing it back.
* @async
* @param {string} name - The hook name
* @param {...any} context - The context to which the hook applies (could be for example, a {@link _converse.ChatBox)).
* @param {...any} data - The data structure to be intercepted and modified by the hook listeners.
* @returns {Promise<any>} - A promise that resolves with the modified data structure.
*/
hook (name, context, data) {
const events = _converse._events[name] || [];
if (events.length) {
// Create a chain of promises, with each one feeding its output to
// the next. The first input is a promise with the original data
// sent to this hook.
return events.reduce((o, e) => o.then(d => e.callback(context, d)), Promise.resolve(data));
} else {
return data;
}
},
/**
* This grouping collects API functions related to the current logged in user.
*
* @namespace _converse.api.user
* @memberOf _converse.api
*/
user: {
settings: user_settings_api,
/**
* @method _converse.api.user.jid
* @returns {string} The current user's full JID (Jabber ID)
* @example _converse.api.user.jid())
*/
jid () {
return _converse.connection.jid;
},
/**
* Logs the user in.
*
* If called without any parameters, Converse will try
* to log the user in by calling the `prebind_url` or `credentials_url` depending
* on whether prebinding is used or not.
*
* @method _converse.api.user.login
* @param {string} [jid]
* @param {string} [password]
* @param {boolean} [automatic=false] - An internally used flag that indicates whether
* this method was called automatically once the connection has been
* initialized. It's used together with the `auto_login` configuration flag
* to determine whether Converse should try to log the user in if it
* fails to restore a previous auth'd session.
* @returns {void}
*/
async login (jid, password, automatic=false) {
jid = jid || api.settings.get('jid');
if (!_converse.connection?.jid || (jid && !u.isSameDomain(_converse.connection.jid, jid))) {
await _converse.initConnection();
}
if (api.settings.get("connection_options")?.worker && (await _converse.connection.restoreWorkerSession())) {
return;
}
if (jid) {
jid = await setUserJID(jid);
}
// See whether there is a BOSH session to re-attach to
const bosh_plugin = _converse.pluggable.plugins['converse-bosh'];
if (bosh_plugin?.enabled()) {
if (await _converse.restoreBOSHSession()) {
return;
} else if (api.settings.get("authentication") === _converse.PREBIND && (!automatic || api.settings.get("auto_login"))) {
return _converse.startNewPreboundBOSHSession();
}
}
password = password || api.settings.get("password");
const credentials = (jid && password) ? { jid, password } : null;
attemptNonPreboundSession(credentials, automatic);
},
/**
* Logs the user out of the current XMPP session.
* @method _converse.api.user.logout
* @example _converse.api.user.logout();
*/
async logout () {
/**
* Triggered before the user is logged out
* @event _converse#beforeLogout
*/
await api.trigger('beforeLogout', {'synchronous': true});
const promise = getOpenPromise();
const complete = () => {
// Recreate all the promises
Object.keys(_converse.promises).forEach(replacePromise);
delete _converse.jid
/**
* Triggered once the user has logged out.
* @event _converse#logout
*/
api.trigger('logout');
promise.resolve();
}
_converse.connection.setDisconnectionCause(_converse.LOGOUT, undefined, true);
if (_converse.connection !== undefined) {
api.listen.once('disconnected', () => complete());
_converse.connection.disconnect();
} else {
complete();
}
return promise;
}
},
/**
* Converse and its plugins trigger various events which you can listen to via the
* {@link _converse.api.listen} namespace.
*
* Some of these events are also available as [ES2015 Promises](http://es6-features.org/#PromiseUsage)
* although not all of them could logically act as promises, since some events
* might be fired multpile times whereas promises are to be resolved (or
* rejected) only once.
*
* Events which are also promises include:
*
* * [cachedRoster](/docs/html/events.html#cachedroster)
* * [chatBoxesFetched](/docs/html/events.html#chatBoxesFetched)
* * [pluginsInitialized](/docs/html/events.html#pluginsInitialized)
* * [roster](/docs/html/events.html#roster)
* * [rosterContactsFetched](/docs/html/events.html#rosterContactsFetched)
* * [rosterGroupsFetched](/docs/html/events.html#rosterGroupsFetched)
* * [rosterInitialized](/docs/html/events.html#rosterInitialized)
*
* The various plugins might also provide promises, and they do this by using the
* `promises.add` api method.
*
* @namespace _converse.api.promises
* @memberOf _converse.api
*/
promises: {
/**
* By calling `promises.add`, a new [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
* is made available for other code or plugins to depend on via the
* {@link _converse.api.waitUntil} method.
*
* Generally, it's the responsibility of the plugin which adds the promise to
* also resolve it.
*
* This is done by calling {@link _converse.api.trigger}, which not only resolves the
* promise, but also emits an event with the same name (which can be listened to
* via {@link _converse.api.listen}).
*
* @method _converse.api.promises.add
* @param {string|array} [name|names] The name or an array of names for the promise(s) to be added
* @param {boolean} [replace=true] Whether this promise should be replaced with a new one when the user logs out.
* @example _converse.api.promises.add('foo-completed');
*/
add (promises, replace=true) {
promises = Array.isArray(promises) ? promises : [promises];
promises.forEach(name => {
const promise = getOpenPromise();
promise.replace = replace;
_converse.promises[name] = promise;
});
}
},
/**
* Converse emits events to which you can subscribe to.
*
* The `listen` namespace exposes methods for creating event listeners
* (aka handlers) for these events.
*
* @namespace _converse.api.listen
* @memberOf _converse
*/
listen: {
/**
* Lets you listen to an event exactly once.
* @method _converse.api.listen.once
* @param {string} name The event's name
* @param {function} callback The callback method to be called when the event is emitted.
* @param {object} [context] The value of the `this` parameter for the callback.
* @example _converse.api.listen.once('message', function (messageXML) { ... });
*/
once: _converse.once.bind(_converse),
/**
* Lets you subscribe to an event.
* Every time the event fires, the callback method specified by `callback` will be called.
* @method _converse.api.listen.on
* @param {string} name The event's name
* @param {function} callback The callback method to be called when the event is emitted.
* @param {object} [context] The value of the `this` parameter for the callback.
* @example _converse.api.listen.on('message', function (messageXML) { ... });
*/
on: _converse.on.bind(_converse),
/**
* To stop listening to an event, you can use the `not` method.
* @method _converse.api.listen.not
* @param {string} name The event's name
* @param {function} callback The callback method that is to no longer be called when the event fires
* @example _converse.api.listen.not('message', function (messageXML);
*/
not: _converse.off.bind(_converse),
/**
* Subscribe to an incoming stanza
* Every a matched stanza is received, the callback method specified by
* `callback` will be called.
* @method _converse.api.listen.stanza
* @param {string} name The stanza's name
* @param {object} options Matching options (e.g. 'ns' for namespace, 'type' for stanza type, also 'id' and 'from');
* @param {function} handler The callback method to be called when the stanza appears
*/
stanza (name, options, handler) {
if (isFunction(options)) {
handler = options;
options = {};
} else {
options = options || {};
}
_converse.connection.addHandler(
handler,
options.ns,
name,
options.type,
options.id,
options.from,
options
);
}
},
/**
* Wait until a promise is resolved or until the passed in function returns
* a truthy value.
* @method _converse.api.waitUntil
* @param {string|function} condition - The name of the promise to wait for,
* or a function which should eventually return a truthy value.
* @returns {Promise}
*/
waitUntil (condition) {
if (isFunction(condition)) {
return u.waitUntil(condition);
} else {
const promise = _converse.promises[condition];
if (promise === undefined) {
return null;
}
return promise;
}
},
/**
* Allows you to send XML stanzas.
* @method _converse.api.send
* @param {XMLElement} stanza
* @return {void}
* @example
* const msg = converse.env.$msg({
* 'from': 'juliet@example.com/balcony',
* 'to': 'romeo@example.net',
* 'type':'chat'
* });
* _converse.api.send(msg);
*/
send (stanza) {
if (!api.connection.connected()) {
log.warn("Not sending stanza because we're not connected!");
log.warn(Strophe.serialize(stanza));
return;
}
if (typeof stanza === 'string') {
stanza = u.toStanza(stanza);
} else if (stanza?.nodeTree) {
stanza = stanza.nodeTree;
}
if (stanza.tagName === 'iq') {
return api.sendIQ(stanza);
} else {
_converse.connection.send(stanza);
api.trigger('send', stanza);
}
},
/**
* Send an IQ stanza
* @method _converse.api.sendIQ
* @param {XMLElement} stanza
* @param {Integer} [timeout=_converse.STANZA_TIMEOUT]
* @param {Boolean} [reject=true] - Whether an error IQ should cause the promise
* to be rejected. If `false`, the promise will resolve instead of being rejected.
* @returns {Promise} A promise which resolves (or potentially rejected) once we
* receive a `result` or `error` stanza or once a timeout is reached.
* If the IQ stanza being sent is of type `result` or `error`, there's
* nothing to wait for, so an already resolved promise is returned.
*/
sendIQ (stanza, timeout=_converse.STANZA_TIMEOUT, reject=true) {
let promise;
stanza = stanza?.nodeTree ?? stanza;
if (['get', 'set'].includes(stanza.getAttribute('type'))) {
timeout = timeout || _converse.STANZA_TIMEOUT;
if (reject) {
promise = new Promise((resolve, reject) => _converse.connection.sendIQ(stanza, resolve, reject, timeout));
promise.catch(e => {
if (e === null) {
throw new TimeoutError(
`Timeout error after ${timeout}ms for the following IQ stanza: ${Strophe.serialize(stanza)}`
);
}
});
} else {
promise = new Promise(resolve => _converse.connection.sendIQ(stanza, resolve, resolve, timeout));
}
} else {
_converse.connection.sendIQ(stanza);
promise = Promise.resolve();
}
api.trigger('send', stanza);
return promise;
}
};
_converse.shouldClearCache = () => (
!_converse.config.get('trusted') ||
api.settings.get('clear_cache_on_logout') ||
_converse.isTestEnv()
);
_converse.initConnection = function () {
const api = _converse.api;
if (! api.settings.get('bosh_service_url')) {
if (api.settings.get("authentication") === _converse.PREBIND) {
throw new Error("authentication is set to 'prebind' but we don't have a BOSH connection");
}
}
const XMPPConnection = _converse.isTestEnv() ? MockConnection : Connection;
_converse.connection = new XMPPConnection(
getConnectionServiceURL(),
Object.assign(
_converse.default_connection_options,
api.settings.get("connection_options"),
{'keepalive': api.settings.get("keepalive")}
)
);
setUpXMLLogging();
/**
* Triggered once the `Connection` constructor has been initialized, which
* will be responsible for managing the connection to the XMPP server.
*
* @event _converse#connectionInitialized
*/
api.trigger('connectionInitialized');
}
function setUpXMLLogging () {
const lmap = {}
lmap[Strophe.LogLevel.DEBUG] = 'debug';
lmap[Strophe.LogLevel.INFO] = 'info';
lmap[Strophe.LogLevel.WARN] = 'warn';
lmap[Strophe.LogLevel.ERROR] = 'error';
lmap[Strophe.LogLevel.FATAL] = 'fatal';
Strophe.log = (level, msg) => log.log(msg, lmap[level]);
Strophe.error = (msg) => log.error(msg);
_converse.connection.xmlInput = body => log.debug(body.outerHTML, 'color: darkgoldenrod');
_converse.connection.xmlOutput = body => log.debug(body.outerHTML, 'color: darkcyan');
}
_converse.saveWindowState = function (ev) {
// XXX: eventually we should be able to just use
// document.visibilityState (when we drop support for older
// browsers).
let state;
const event_map = {
'focus': "visible",
'focusin': "visible",
'pageshow': "visible",
'blur': "hidden",
'focusout': "hidden",
'pagehide': "hidden"
};
ev = ev || document.createEvent('Events');
if (ev.type in event_map) {
state = event_map[ev.type];
} else {
state = document.hidden ? "hidden" : "visible";
}
_converse.windowState = state;
/**
* Triggered when window state has changed.
* Used to determine when a user left the page and when came back.
* @event _converse#windowStateChanged
* @type { object }
* @property{ string } state - Either "hidden" or "visible"
* @example _converse.api.listen.on('windowStateChanged', obj => { ... });
*/
api.trigger('windowStateChanged', {state});
}
_converse.ConnectionFeedback = Model.extend({
defaults: {
'connection_status': Strophe.Status.DISCONNECTED,
'message': ''
},
initialize () {
this.on('change', () => api.trigger('connfeedback', _converse.connfeedback));
}
});
export const converse = window.converse || {};
/**
* ### The Public API
*
* This namespace contains public API methods which are are
* accessible on the global `converse` object.
* They are public, because any JavaScript in the
* page can call them. Public methods therefore dont expose any sensitive
* or closured data. To do that, youll need to create a plugin, which has
* access to the private API method.
*
* @global
* @namespace converse
*/
Object.assign(converse, {
CHAT_STATES,
keycodes: KEYCODES,
/**
* Public API method which initializes Converse.
* This method must always be called when using Converse.
* @async
* @memberOf converse
* @method initialize
* @param {object} config A map of [configuration-settings](https://conversejs.org/docs/html/configuration.html#configuration-settings).
* @example
* converse.initialize({
* auto_list_rooms: false,
* auto_subscribe: false,
* bosh_service_url: 'https://bind.example.com',
* hide_muc_server: false,
* i18n: 'en',
* play_sounds: true,
* show_controlbox_by_default: true,
* debug: false,
* roster_groups: true
* });
*/
async initialize (settings) {
await cleanup(_converse);
setUnloadEvent();
initAppSettings(settings);
_converse.strict_plugin_dependencies = settings.strict_plugin_dependencies; // Needed by pluggable.js
log.setLogLevel(api.settings.get("loglevel"));
if (api.settings.get("authentication") === _converse.ANONYMOUS) {
if (api.settings.get("auto_login") && !api.settings.get('jid')) {
throw new Error("Config Error: you need to provide the server's " +
"domain via the 'jid' option when using anonymous " +
"authentication with auto_login.");
}
}
_converse.router.route(
/^converse\?loglevel=(debug|info|warn|error|fatal)$/, 'loglevel',
l => log.setLogLevel(l)
);
_converse.connfeedback = new _converse.ConnectionFeedback();
/* When reloading the page:
* For new sessions, we need to send out a presence stanza to notify
* the server/network that we're online.
* When re-attaching to an existing session we don't need to again send out a presence stanza,
* because it's as if "we never left" (see onConnectStatusChanged).
* https://github.com/conversejs/converse.js/issues/521
*/
_converse.send_initial_presence = true;
await initSessionStorage(_converse);
await initClientConfig(_converse);
await i18n.initialize();
initPlugins(_converse);
// Register all custom elements
// XXX: api.elements is defined in the UI part of Converse, outside of @converse/headless.
// This line should probably be moved to the UI code as part of a larger refactoring.
api.elements?.register();
registerGlobalEventHandlers(_converse);
try {
!History.started && _converse.router.history.start();
} catch (e) {
log.error(e);
}
const plugins = _converse.pluggable.plugins
if (api.settings.get("auto_login") || api.settings.get("keepalive") && invoke(plugins['converse-bosh'], 'enabled')) {
await api.user.login(null, null, true);
}
/**
* Triggered once converse.initialize has finished.
* @event _converse#initialized
*/
api.trigger('initialized');
if (_converse.isTestEnv()) {
return _converse;
}
},
/**
* Exposes methods for adding and removing plugins. You'll need to write a plugin
* if you want to have access to the private API methods defined further down below.
*
* For more information on plugins, read the documentation on [writing a plugin](/docs/html/plugin_development.html).
* @namespace plugins
* @memberOf converse
*/
plugins: {
/**
* Registers a new plugin.
* @method converse.plugins.add
* @param {string} name The name of the plugin
* @param {object} plugin The plugin object
* @example
* const plugin = {
* initialize: function () {
* // Gets called as soon as the plugin has been loaded.
*
* // Inside this method, you have access to the private
* // API via `_covnerse.api`.
*
* // The private _converse object contains the core logic
* // and data-structures of Converse.
* }
* }
* converse.plugins.add('myplugin', plugin);
*/
add (name, plugin) {
plugin.__name__ = name;
if (_converse.pluggable.plugins[name] !== undefined) {
throw new TypeError(
`Error: plugin with name "${name}" has already been ` + 'registered!'
);
} else {
_converse.pluggable.plugins[name] = plugin;
}
}
},
/**
* Utility methods and globals from bundled 3rd party libraries.
* @typedef ConverseEnv
* @property {function} converse.env.$build - Creates a Strophe.Builder, for creating stanza objects.
* @property {function} converse.env.$iq - Creates a Strophe.Builder with an <iq/> element as the root.
* @property {function} converse.env.$msg - Creates a Strophe.Builder with an <message/> element as the root.
* @property {function} converse.env.$pres - Creates a Strophe.Builder with an <presence/> element as the root.
* @property {function} converse.env.Promise - The Promise implementation used by Converse.
* @property {function} converse.env.Strophe - The [Strophe](http://strophe.im/strophejs) XMPP library used by Converse.
* @property {function} converse.env.f - And instance of Lodash with its methods wrapped to produce immutable auto-curried iteratee-first data-last methods.
* @property {function} converse.env.sizzle - [Sizzle](https://sizzlejs.com) CSS selector engine.
* @property {function} converse.env.sprintf
* @property {object} converse.env._ - The instance of [lodash-es](http://lodash.com) used by Converse.
* @property {object} converse.env.dayjs - [DayJS](https://github.com/iamkun/dayjs) date manipulation library.
* @property {object} converse.env.utils - Module containing common utility methods used by Converse.
* @memberOf converse
*/
'env': {
$build,
$iq,
$msg,
$pres,
'utils': u,
Collection,
Model,
Promise,
Strophe,
URI,
dayjs,
html,
log,
sizzle,
sprintf,
stx: u.stx,
u,
}
});

View File

@ -2,13 +2,13 @@
* --------------------
* Any of the following components may be removed if they're not needed.
*/
import "./plugins/adhoc.js"; // XEP-0050 Ad Hoc Commands
import "./plugins/bookmarks/index.js"; // XEP-0199 XMPP Ping
import "./plugins/bosh.js"; // XEP-0206 BOSH
import "./plugins/caps/index.js"; // XEP-0115 Entity Capabilities
import "./plugins/chat/index.js"; // RFC-6121 Instant messaging
import "./plugins/chatboxes/index.js";
import "./plugins/disco/index.js"; // XEP-0030 Service discovery
import "./plugins/adhoc/index.js"; // XEP-0050 Ad Hoc Commands
import "./plugins/headlines/index.js"; // Support for headline messages
import "./plugins/mam/index.js"; // XEP-0313 Message Archive Management
import "./plugins/muc/index.js"; // XEP-0045 Multi-user chat

View File

@ -1,4 +1,4 @@
import { isElement } from './utils/core.js';
import isElement from 'lodash-es/isElement';
const LEVELS = {
'debug': 0,
@ -8,26 +8,24 @@ const LEVELS = {
'fatal': 4
}
/* eslint-disable @typescript-eslint/no-empty-function */
const logger = Object.assign({
'debug': console?.log ? console.log.bind(console) : function noop () {},
'error': console?.log ? console.log.bind(console) : function noop () {},
'info': console?.log ? console.log.bind(console) : function noop () {},
'warn': console?.log ? console.log.bind(console) : function noop () {}
}, console);
/* eslint-enable @typescript-eslint/no-empty-function */
/**
* The log namespace
* @namespace log
*/
export default {
const log = {
/**
* The the log-level, which determines how verbose the logging is.
* @method log#setLogLevel
* @param { number } level - The loglevel which allows for filtering of log messages
* @param { integer } level - The loglevel which allows for filtering of log messages
*/
setLogLevel (level) {
if (!['debug', 'info', 'warn', 'error', 'fatal'].includes(level)) {
@ -44,7 +42,7 @@ export default {
* logged as well.
* @method log#log
* @param { string | Error } message - The message to be logged
* @param { number } level - The loglevel which allows for filtering of log messages
* @param { integer } level - The loglevel which allows for filtering of log messages
*/
log (message, level, style='') {
if (LEVELS[level] < LEVELS[this.loglevel]) {
@ -95,3 +93,5 @@ export default {
this.log(message, 'fatal', style);
}
}
export default log;

View File

@ -1,17 +1,17 @@
{
"name": "@converse/headless",
"version": "10.1.0",
"version": "9.1.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@converse/headless",
"version": "10.1.0",
"version": "9.1.1",
"license": "MPL-2.0",
"dependencies": {
"@converse/openpromise": "^0.0.1",
"@converse/skeletor": "0.0.8",
"dayjs": "1.11.8",
"@converse/skeletor": "0.0.7",
"dayjs": "1.11.6",
"dompurify": "^2.3.1",
"filesize": "^7.0.0",
"localforage-webextensionstorage-driver": "^3.0.0",
@ -23,37 +23,29 @@
"urijs": "^1.19.10"
}
},
"node_modules/@converse/localforage-getitems": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/@converse/localforage-getitems/-/localforage-getitems-1.4.3.tgz",
"integrity": "sha512-ofch1Zv9+CxU4xYxBq+QmsJFKLi6FZ69REPoTc56eeWN6ps0/+g5gD4/gwukEFhAuE8jsBpjcrnizXsa4WsMxQ==",
"dependencies": {
"localforage": ">=1.4.0"
}
},
"node_modules/@converse/openpromise": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/@converse/openpromise/-/openpromise-0.0.1.tgz",
"integrity": "sha512-oA1TKrm6H838isYZJxMWXpXyOUezkD49eMJ6bkI+FfL2MsVuOV3ZbhBV+c07mLSknKXO7pUbWTVa5f7bXJXYjQ=="
},
"node_modules/@converse/skeletor": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/@converse/skeletor/-/skeletor-0.0.8.tgz",
"integrity": "sha512-8/wAenuk7QKOHaOsk89e5zFyQZz5HhsuqWBzrzDxmepiBVlRvnxjVdB6619IFyW0VWf0ezcm5Rl4JndUx2sbqg==",
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@converse/skeletor/-/skeletor-0.0.7.tgz",
"integrity": "sha512-JqK1lND0R1l9UEH/0cA7AGcP7EzB+NdPoMmCYNHBaSBins7ir+HHsGxw9eIHWJMPNXIN4ERG2oKwUTwVLryxmA==",
"dependencies": {
"@converse/localforage-getitems": "1.4.3",
"lit-html": "^2.0.0-rc.2",
"localforage": "^1.10.0",
"localforage-driver-memory": "^1.0.5",
"localforage-getitems": "github:conversejs/localForage-getItems#0f129c5c9bb0d23f8dbb64c9dfbb003c8cdf7285",
"localforage-setitems": "^1.4.0",
"lodash-es": "^4.17.21",
"mergebounce": "0.1.1"
}
},
"node_modules/@types/trusted-types": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz",
"integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g=="
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz",
"integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg=="
},
"node_modules/@xmldom/xmldom": {
"version": "0.8.3",
@ -70,9 +62,9 @@
"integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA=="
},
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
@ -153,9 +145,9 @@
"integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug=="
},
"node_modules/dompurify": {
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.4.tgz",
"integrity": "sha512-1e2SpqHiRx4DPvmRuXU5J0di3iQACwJM+mFGE2HAkkK7Tbnfk9WcghcAmyWc9CRrjyRRUpmuhPUH6LphQQR3EQ=="
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.0.tgz",
"integrity": "sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA=="
},
"node_modules/filesize": {
"version": "7.0.0",
@ -267,9 +259,9 @@
}
},
"node_modules/lit-html": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.6.1.tgz",
"integrity": "sha512-Z3iw+E+3KKFn9t2YKNjsXNEu/LRLI98mtH/C6lnFg7kvaqPIzPn124Yd4eT/43lyqrejpc5Wb6BHq3fdv4S8Rw==",
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.4.0.tgz",
"integrity": "sha512-G6qXu4JNUpY6aaF2VMfaszhO9hlWw0hOTRFDmuMheg/nDYGB+2RztUSOyrzALAbr8Nh0Y7qjhYkReh3rPnplVg==",
"dependencies": {
"@types/trusted-types": "^2.0.2"
}
@ -302,6 +294,14 @@
"localforage": "^1.5.0"
}
},
"node_modules/localforage-getitems": {
"version": "1.4.2",
"resolved": "git+ssh://git@github.com/conversejs/localForage-getItems.git#0f129c5c9bb0d23f8dbb64c9dfbb003c8cdf7285",
"integrity": "sha512-J1Q2IqJgwWNJOOX8Q0UULrBEXZExmLwuGWXp/bsjWpuA5LL8RDJcmbUX/3CD9DqjvxvNTnutw2/973CD/EqXMQ==",
"dependencies": {
"localforage": ">=1.4.0"
}
},
"node_modules/localforage-setitems": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/localforage-setitems/-/localforage-setitems-1.4.0.tgz",
@ -379,9 +379,9 @@
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
},
"node_modules/rollup": {
"version": "3.15.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.15.0.tgz",
"integrity": "sha512-F9hrCAhnp5/zx/7HYmftvsNBkMfLfk/dXUh73hPSM2E3CRgap65orDNJbLetoiUFwSAk6iHPLvBrZ5iHYvzqsg==",
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.2.3.tgz",
"integrity": "sha512-qfadtkY5kl0F5e4dXVdj2D+GtOdifasXHFMiL1SMf9ADQDv5Eti6xReef9FKj+iQPR2pvtqWna57s/PjARY4fg==",
"peer": true,
"bin": {
"rollup": "dist/bin/rollup"
@ -395,9 +395,9 @@
}
},
"node_modules/sizzle": {
"version": "2.3.10",
"resolved": "https://registry.npmjs.org/sizzle/-/sizzle-2.3.10.tgz",
"integrity": "sha512-kPGev+SiByuzi/YPDTqCwdKLWCaN9+14ve86yH0gP6Efue04xjLYWJrcLC6y1buFyIVXkwHNXPsOTEd1MYVPbQ=="
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/sizzle/-/sizzle-2.3.7.tgz",
"integrity": "sha512-f3gf191yiPkCbcRR9IjHajmlrGN/MCXxAzNzIwVW0qZic2wbGKXn+KjbrVfqCaA3zEgCawqwtj2Y5LiBvOUddg=="
},
"node_modules/sprintf-js": {
"version": "1.1.2",
@ -439,16 +439,16 @@
"integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ=="
},
"node_modules/ws": {
"version": "8.12.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.12.1.tgz",
"integrity": "sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==",
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.10.0.tgz",
"integrity": "sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw==",
"optional": true,
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
@ -461,37 +461,29 @@
}
},
"dependencies": {
"@converse/localforage-getitems": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/@converse/localforage-getitems/-/localforage-getitems-1.4.3.tgz",
"integrity": "sha512-ofch1Zv9+CxU4xYxBq+QmsJFKLi6FZ69REPoTc56eeWN6ps0/+g5gD4/gwukEFhAuE8jsBpjcrnizXsa4WsMxQ==",
"requires": {
"localforage": ">=1.4.0"
}
},
"@converse/openpromise": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/@converse/openpromise/-/openpromise-0.0.1.tgz",
"integrity": "sha512-oA1TKrm6H838isYZJxMWXpXyOUezkD49eMJ6bkI+FfL2MsVuOV3ZbhBV+c07mLSknKXO7pUbWTVa5f7bXJXYjQ=="
},
"@converse/skeletor": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/@converse/skeletor/-/skeletor-0.0.8.tgz",
"integrity": "sha512-8/wAenuk7QKOHaOsk89e5zFyQZz5HhsuqWBzrzDxmepiBVlRvnxjVdB6619IFyW0VWf0ezcm5Rl4JndUx2sbqg==",
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@converse/skeletor/-/skeletor-0.0.7.tgz",
"integrity": "sha512-JqK1lND0R1l9UEH/0cA7AGcP7EzB+NdPoMmCYNHBaSBins7ir+HHsGxw9eIHWJMPNXIN4ERG2oKwUTwVLryxmA==",
"requires": {
"@converse/localforage-getitems": "1.4.3",
"lit-html": "^2.0.0-rc.2",
"localforage": "^1.10.0",
"localforage-driver-memory": "^1.0.5",
"localforage-getitems": "github:conversejs/localForage-getItems#0f129c5c9bb0d23f8dbb64c9dfbb003c8cdf7285",
"localforage-setitems": "^1.4.0",
"lodash-es": "^4.17.21",
"mergebounce": "0.1.1"
}
},
"@types/trusted-types": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz",
"integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g=="
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz",
"integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg=="
},
"@xmldom/xmldom": {
"version": "0.8.3",
@ -505,9 +497,9 @@
"integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA=="
},
"anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"requires": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
@ -566,9 +558,9 @@
"integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug=="
},
"dompurify": {
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.4.tgz",
"integrity": "sha512-1e2SpqHiRx4DPvmRuXU5J0di3iQACwJM+mFGE2HAkkK7Tbnfk9WcghcAmyWc9CRrjyRRUpmuhPUH6LphQQR3EQ=="
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.0.tgz",
"integrity": "sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA=="
},
"filesize": {
"version": "7.0.0",
@ -646,9 +638,9 @@
}
},
"lit-html": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.6.1.tgz",
"integrity": "sha512-Z3iw+E+3KKFn9t2YKNjsXNEu/LRLI98mtH/C6lnFg7kvaqPIzPn124Yd4eT/43lyqrejpc5Wb6BHq3fdv4S8Rw==",
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.4.0.tgz",
"integrity": "sha512-G6qXu4JNUpY6aaF2VMfaszhO9hlWw0hOTRFDmuMheg/nDYGB+2RztUSOyrzALAbr8Nh0Y7qjhYkReh3rPnplVg==",
"requires": {
"@types/trusted-types": "^2.0.2"
}
@ -676,6 +668,14 @@
"tslib": "^1.6.0"
}
},
"localforage-getitems": {
"version": "git+ssh://git@github.com/conversejs/localForage-getItems.git#0f129c5c9bb0d23f8dbb64c9dfbb003c8cdf7285",
"integrity": "sha512-J1Q2IqJgwWNJOOX8Q0UULrBEXZExmLwuGWXp/bsjWpuA5LL8RDJcmbUX/3CD9DqjvxvNTnutw2/973CD/EqXMQ==",
"from": "localforage-getitems@github:conversejs/localForage-getItems#0f129c5c9bb0d23f8dbb64c9dfbb003c8cdf7285",
"requires": {
"localforage": ">=1.4.0"
}
},
"localforage-setitems": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/localforage-setitems/-/localforage-setitems-1.4.0.tgz",
@ -738,18 +738,18 @@
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
},
"rollup": {
"version": "3.15.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.15.0.tgz",
"integrity": "sha512-F9hrCAhnp5/zx/7HYmftvsNBkMfLfk/dXUh73hPSM2E3CRgap65orDNJbLetoiUFwSAk6iHPLvBrZ5iHYvzqsg==",
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.2.3.tgz",
"integrity": "sha512-qfadtkY5kl0F5e4dXVdj2D+GtOdifasXHFMiL1SMf9ADQDv5Eti6xReef9FKj+iQPR2pvtqWna57s/PjARY4fg==",
"peer": true,
"requires": {
"fsevents": "~2.3.2"
}
},
"sizzle": {
"version": "2.3.10",
"resolved": "https://registry.npmjs.org/sizzle/-/sizzle-2.3.10.tgz",
"integrity": "sha512-kPGev+SiByuzi/YPDTqCwdKLWCaN9+14ve86yH0gP6Efue04xjLYWJrcLC6y1buFyIVXkwHNXPsOTEd1MYVPbQ=="
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/sizzle/-/sizzle-2.3.7.tgz",
"integrity": "sha512-f3gf191yiPkCbcRR9IjHajmlrGN/MCXxAzNzIwVW0qZic2wbGKXn+KjbrVfqCaA3zEgCawqwtj2Y5LiBvOUddg=="
},
"sprintf-js": {
"version": "1.1.2",
@ -786,9 +786,9 @@
"integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ=="
},
"ws": {
"version": "8.12.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.12.1.tgz",
"integrity": "sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==",
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.10.0.tgz",
"integrity": "sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw==",
"optional": true,
"requires": {}
}

View File

@ -1,15 +1,12 @@
{
"name": "@converse/headless",
"version": "10.1.6",
"version": "10.0.0",
"description": "Converse.js Headless build",
"author": "JC Brand <jc@opkode.com>",
"contributors": [
"cmrd Senya <senya@riseup.net>"
],
"author": "cmrd Senya <senya@riseup.net>",
"homepage": "https://conversejs.org",
"license": "MPL-2.0",
"main": "dist/converse-headless.js",
"module": "index.js",
"main": "dist/converse-headless.min.js",
"module": "headless.js",
"keywords": [
"converse.js",
"XMPP",
@ -30,19 +27,19 @@
"bugs": {
"url": "https://github.com/conversejs/converse.js/issues"
},
"gitHead": "9641dcdc820e029b05930479c242d2b707bbe8e2",
"dependencies": {
"@converse/openpromise": "^0.0.1",
"@converse/skeletor": "^0.0.8",
"dayjs": "^1.11.8",
"@converse/skeletor": "0.0.7",
"dayjs": "1.11.6",
"dompurify": "^2.3.1",
"filesize": "^10.0.7",
"filesize": "^7.0.0",
"localforage-webextensionstorage-driver": "^3.0.0",
"lodash-es": "^4.17.21",
"pluggable.js": "3.0.1",
"sizzle": "^2.3.5",
"sprintf-js": "^1.1.2",
"strophe.js": "^1.6.2",
"strophe.js": "1.6.0",
"urijs": "^1.19.10"
},
"devDependencies": {}
}
}

View File

@ -0,0 +1,61 @@
import { converse } from "../core.js";
import log from "@converse/headless/log";
import sizzle from 'sizzle';
import { getAttributes } from '@converse/headless/shared/parsers';
const { Strophe } = converse.env;
let _converse, api;
Strophe.addNamespace('ADHOC', 'http://jabber.org/protocol/commands');
function parseForCommands (stanza) {
const items = sizzle(`query[xmlns="${Strophe.NS.DISCO_ITEMS}"][node="${Strophe.NS.ADHOC}"] item`, stanza);
return items.map(getAttributes)
}
const adhoc_api = {
/**
* The XEP-0050 Ad-Hoc Commands API
*
* This API lets you discover ad-hoc commands available for an entity in the XMPP network.
*
* @namespace api.adhoc
* @memberOf api
*/
adhoc: {
/**
* @method api.adhoc.getCommands
* @param { String } to_jid
*/
async getCommands (to_jid) {
let commands = [];
try {
commands = parseForCommands(await api.disco.items(to_jid, Strophe.NS.ADHOC));
} catch (e) {
if (e === null) {
log.error(`Error: timeout while fetching ad-hoc commands for ${to_jid}`);
} else {
log.error(`Error while fetching ad-hoc commands for ${to_jid}`);
log.error(e);
}
}
return commands;
}
}
}
converse.plugins.add('converse-adhoc', {
dependencies: ["converse-disco"],
initialize () {
_converse = this._converse;
api = _converse.api;
Object.assign(api, adhoc_api);
}
});
export default adhoc_api;

View File

@ -1,109 +0,0 @@
import log from '@converse/headless/log';
import { _converse, api, converse } from "@converse/headless/core";
import { getCommandFields, parseForCommands } from './utils.js';
const { Strophe, $iq, u, stx } = converse.env;
export default {
/**
* The XEP-0050 Ad-Hoc Commands API
*
* This API lets you discover ad-hoc commands available for an entity in the XMPP network.
*
* @namespace api.adhoc
* @memberOf api
*/
adhoc: {
/**
* @method api.adhoc.getCommands
* @param { String } to_jid
*/
async getCommands (to_jid) {
try {
return parseForCommands(await api.disco.items(to_jid, Strophe.NS.ADHOC));
} catch (e) {
if (e === null) {
log.error(`Error: timeout while fetching ad-hoc commands for ${to_jid}`);
} else {
log.error(`Error while fetching ad-hoc commands for ${to_jid}`);
log.error(e);
}
return [];
}
},
/**
* @method api.adhoc.fetchCommandForm
*/
async fetchCommandForm (command) {
const node = command.node;
const jid = command.jid;
const stanza = $iq({
'type': 'set',
'to': jid
}).c('command', {
'xmlns': Strophe.NS.ADHOC,
'node': node,
'action': 'execute'
});
try {
return getCommandFields(await api.sendIQ(stanza), jid);
} catch (e) {
if (e === null) {
log.error(`Error: timeout while trying to execute command for ${jid}`);
} else {
log.error(`Error while trying to execute command for ${jid}`);
log.error(e);
}
const { __ } = _converse;
return {
instructions: __('An error occurred while trying to fetch the command form'),
fields: []
}
}
},
/**
* @method api.adhoc.runCommand
* @param { String } jid
* @param { String } sessionid
* @param { 'execute' | 'cancel' | 'prev' | 'next' | 'complete' } action
* @param { String } node
* @param { Array<{ string: string }> } inputs
*/
async runCommand (jid, sessionid, node, action, inputs) {
const iq =
stx`<iq type="set" to="${jid}" xmlns="jabber:client">
<command sessionid="${sessionid}" node="${node}" action="${action}" xmlns="${Strophe.NS.ADHOC}">
${ !['cancel', 'prev'].includes(action) ? stx`
<x xmlns="${Strophe.NS.XFORM}" type="submit">
${ inputs.reduce((out, { name, value }) => out + `<field var="${name}"><value>${value}</value></field>`, '') }
</x>` : '' }
</command>
</iq>`;
const result = await api.sendIQ(iq, null, false);
if (result === null) {
log.warn(`A timeout occurred while trying to run an ad-hoc command`);
const { __ } = _converse;
return {
status: 'error',
note: __('A timeout occurred'),
}
} else if (u.isErrorStanza(result)) {
log.error('Error while trying to execute an ad-hoc command');
log.error(result);
}
const command = result.querySelector('command');
const status = command?.getAttribute('status');
return {
status,
...(status === 'executing' ? getCommandFields(result) : {}),
note: result.querySelector('note')?.textContent
}
}
}
}

View File

@ -1,16 +0,0 @@
import adhoc_api from './api.js';
import { converse } from "@converse/headless/core";
const { Strophe } = converse.env;
Strophe.addNamespace('ADHOC', 'http://jabber.org/protocol/commands');
converse.plugins.add('converse-adhoc', {
dependencies: ["converse-disco"],
initialize () {
Object.assign(this._converse.api, adhoc_api);
}
});

View File

@ -1,22 +0,0 @@
import sizzle from 'sizzle';
import { converse } from "@converse/headless/core";
import { getAttributes } from '@converse/headless/shared/parsers';
const { Strophe, u } = converse.env;
export function parseForCommands (stanza) {
const items = sizzle(`query[xmlns="${Strophe.NS.DISCO_ITEMS}"][node="${Strophe.NS.ADHOC}"] item`, stanza);
return items.map(getAttributes)
}
export function getCommandFields (iq, jid) {
const cmd_el = sizzle(`command[xmlns="${Strophe.NS.ADHOC}"]`, iq).pop();
const data = {
sessionid: cmd_el.getAttribute('sessionid'),
instructions: sizzle('x[type="form"][xmlns="jabber:x:data"] instructions', cmd_el).pop()?.textContent,
fields: sizzle('x[type="form"][xmlns="jabber:x:data"] field', cmd_el)
.map(f => u.xForm2TemplateResult(f, cmd_el, { domain: jid })),
actions: Array.from(cmd_el.querySelector('actions')?.children).map((a) => a.nodeName.toLowerCase()) ?? []
}
return data;
}

View File

@ -136,7 +136,7 @@ converse.plugins.add('converse-bosh', {
tokens: {
/**
* @method api.tokens.get
* @param { string } [id] The type of token to return ('rid' or 'sid').
* @param {string} [id] The type of token to return ('rid' or 'sid').
* @returns 'string' A token, either the RID or SID token depending on what's asked for.
* @example _converse.api.tokens.get('rid');
*/

View File

@ -31,13 +31,13 @@ async function createCapsNode () {
'hash': "sha-1",
'node': "https://conversejs.org",
'ver': await generateVerificationString()
}).tree();
}).nodeTree;
}
/**
* Given a stanza, adds a XEP-0115 CAPS element
* @param { Element } stanza
* @param { XMLElement } stanza
*/
export async function addCapsNode (stanza) {
const caps_el = await createCapsNode();

View File

@ -13,7 +13,7 @@ export default {
/**
* @method api.chats.create
* @param {string|string[]} jid|jids An jid or array of jids
* @param { object } [attrs] An object containing configuration attributes.
* @param {object} [attrs] An object containing configuration attributes.
*/
async create (jids, attrs) {
if (typeof jids === 'string') {
@ -44,9 +44,9 @@ export default {
*
* @method api.chats.open
* @param {String|string[]} name - e.g. 'buddy@example.com' or ['buddy1@example.com', 'buddy2@example.com']
* @param { Object } [attrs] - Attributes to be set on the _converse.ChatBox model.
* @param { Boolean } [attrs.minimized] - Should the chat be created in minimized state.
* @param { Boolean } [force=false] - By default, a minimized
* @param {Object} [attrs] - Attributes to be set on the _converse.ChatBox model.
* @param {Boolean} [attrs.minimized] - Should the chat be created in minimized state.
* @param {Boolean} [force=false] - By default, a minimized
* chat won't be maximized (in `overlayed` view mode) and in
* `fullscreen` view mode a newly opened chat won't replace
* another chat already in the foreground.
@ -102,8 +102,8 @@ export default {
*
* @method api.chats.get
* @param {String|string[]} jids - e.g. 'buddy@example.com' or ['buddy1@example.com', 'buddy2@example.com']
* @param { Object } [attrs] - Attributes to be set on the _converse.ChatBox model.
* @param { Boolean } [create=false] - Whether the chat should be created if it's not found.
* @param {Object} [attrs] - Attributes to be set on the _converse.ChatBox model.
* @param {Boolean} [create=false] - Whether the chat should be created if it's not found.
* @returns { Promise<_converse.ChatBox> }
*
* @example

View File

@ -7,8 +7,7 @@ import { getOpenPromise } from '@converse/openpromise';
const { Strophe, sizzle, u } = converse.env;
/**
* Mixin which turns a `ModelWithContact` model into a non-MUC message.
* These can be either `chat`, `normal` or `headline` messages.
* Mixin which turns a `ModelWithContact` model into a non-MUC message. These can be either `chat` messages or `headline` messages.
* @mixin
* @namespace _converse.Message
* @memberOf _converse
@ -48,8 +47,9 @@ const MessageMixin = {
this.initialized.resolve();
},
setContact () {
if (['chat', 'normal'].includes(this.get('type'))) {
if (this.get('type') === 'chat') {
ModelWithContact.prototype.initialize.apply(this, arguments);
this.setRosterContact(Strophe.getBareJidFromJid(this.get('from')));
}
@ -164,7 +164,7 @@ const MessageMixin = {
if (this.get('is_encrypted')) {
const { __ } = _converse;
return this.get('plaintext') || this.get('body') || __('Undecryptable OMEMO message');
} else if (['groupchat', 'chat', 'normal'].includes(this.get('type'))) {
} else if (['groupchat', 'chat'].includes(this.get('type'))) {
return this.get('body');
} else {
return this.get('message');

View File

@ -1,13 +1,12 @@
import ModelWithContact from './model-with-contact.js';
import filesize from "filesize";
import isMatch from "lodash-es/isMatch";
import isObject from "lodash-es/isObject";
import log from '@converse/headless/log';
import pick from "lodash-es/pick";
import { Model } from '@converse/skeletor/src/model.js';
import { TimeoutError } from '../../shared/errors.js';
import { _converse, api, converse } from "../../core.js";
import { debouncedPruneHistory, handleCorrection } from '@converse/headless/shared/chat/utils.js';
import { filesize } from "filesize";
import { getMediaURLsMetadata } from '@converse/headless/shared/parsers.js';
import { getOpenPromise } from '@converse/openpromise';
import { initStorage } from '@converse/headless/utils/storage.js';
@ -369,7 +368,7 @@ const ChatBox = ModelWithContact.extend({
},
async createMessageFromError (error) {
if (error instanceof TimeoutError) {
if (error instanceof _converse.TimeoutError) {
const msg = await this.createMessage({
'type': 'error',
'message': error.message,

View File

@ -32,7 +32,7 @@ const { Strophe, sizzle } = converse.env;
/**
* Parses a passed in message stanza and returns an object of attributes.
* @method st#parseMessage
* @param { Element } stanza - The message stanza
* @param { XMLElement } stanza - The message stanza
* @param { _converse } _converse
* @returns { (MessageAttributes|Error) }
*/
@ -178,7 +178,7 @@ export async function parseMessage (stanza) {
'thread': stanza.querySelector('thread')?.textContent,
'time': delay ? dayjs(delay.getAttribute('stamp')).toISOString() : now,
'to': stanza.getAttribute('to'),
'type': stanza.getAttribute('type') || 'normal'
'type': stanza.getAttribute('type')
},
getErrorAttributes(stanza),
getOutOfBandAttributes(stanza),

View File

@ -1,10 +1,9 @@
import log from '@converse/headless/log.js';
import { _converse, api, converse } from '@converse/headless/core.js';
import { isArchived, isHeadline, isServerMessage, } from '@converse/headless/shared/parsers';
import { isServerMessage, } from '@converse/headless/shared/parsers';
import { parseMessage } from './parsers.js';
import { shouldClearCache } from '@converse/headless/utils/core.js';
import log from '@converse/headless/log.js';
const { Strophe, u } = converse.env;
const { Strophe, sizzle, u } = converse.env;
export function openChat (jid) {
if (!u.isValidJID(jid)) {
@ -14,7 +13,7 @@ export function openChat (jid) {
}
export async function onClearSession () {
if (shouldClearCache()) {
if (_converse.shouldClearCache()) {
await Promise.all(
_converse.chatboxes.map(c => c.messages && c.messages.clearStore({ 'silent': true }))
);
@ -61,22 +60,43 @@ export function autoJoinChats () {
export function registerMessageHandlers () {
_converse.connection.addHandler(
stanza => {
if (
['groupchat', 'error'].includes(stanza.getAttribute('type')) ||
isHeadline(stanza) ||
isServerMessage(stanza) ||
isArchived(stanza)
) {
if (sizzle(`message > result[xmlns="${Strophe.NS.MAM}"]`, stanza).pop()) {
// MAM messages are handled in converse-mam.
// We shouldn't get MAM messages here because
// they shouldn't have a `type` attribute.
log.warn(`Received a MAM message with type "chat".`);
return true;
}
return _converse.handleMessageStanza(stanza) || true;
_converse.handleMessageStanza(stanza);
return true;
},
null,
'message',
'chat'
);
_converse.connection.addHandler(
stanza => handleErrorMessage(stanza) || true,
stanza => {
// Message receipts are usually without the `type` attribute. See #1353
if (stanza.getAttribute('type') !== null) {
// TODO: currently Strophe has no way to register a handler
// for stanzas without a `type` attribute.
// We could update it to accept null to mean no attribute,
// but that would be a backward-incompatible change
return true; // Gets handled above.
}
_converse.handleMessageStanza(stanza);
return true;
},
Strophe.NS.RECEIPTS,
'message'
);
_converse.connection.addHandler(
stanza => {
handleErrorMessage(stanza);
return true;
},
null,
'message',
'error'
@ -89,8 +109,6 @@ export function registerMessageHandlers () {
* @param { MessageAttributes } attrs - The message attributes
*/
export async function handleMessageStanza (stanza) {
stanza = stanza.tree?.() ?? stanza;
if (isServerMessage(stanza)) {
// Prosody sends headline messages with type `chat`, so we need to filter them out here.
const from = stanza.getAttribute('from');
@ -98,7 +116,7 @@ export async function handleMessageStanza (stanza) {
}
let attrs;
try {
attrs = await parseMessage(stanza);
attrs = await parseMessage(stanza, _converse);
} catch (e) {
return log.error(e);
}
@ -114,7 +132,7 @@ export async function handleMessageStanza (stanza) {
* @typedef { Object } MessageData
* An object containing the original message stanza, as well as the
* parsed attributes.
* @property { Element } stanza
* @property { XMLElement } stanza
* @property { MessageAttributes } stanza
* @property { ChatBox } chatbox
*/

View File

@ -16,7 +16,7 @@ const ChatBoxes = Collection.extend({
* @event _converse#chatBoxesFetched
* @type { object }
* @property { _converse.ChatBox | _converse.ChatRoom } chatbox
* @property { Element } stanza
* @property { XMLElement } stanza
* @example _converse.api.listen.on('chatBoxesFetched', obj => { ... });
* @example _converse.api.waitUntil('chatBoxesFetched').then(() => { ... });
*/

View File

@ -24,8 +24,8 @@ export default {
stream: {
/**
* @method api.disco.stream.getFeature
* @param { String } name The feature name
* @param { String } xmlns The XML namespace
* @param {String} name The feature name
* @param {String} xmlns The XML namespace
* @example _converse.api.disco.stream.getFeature('ver', 'urn:xmpp:features:rosterver')
*/
async getFeature (name, xmlns) {
@ -57,10 +57,10 @@ export default {
* Lets you add new identities for this client (i.e. instance of Converse)
* @method api.disco.own.identities.add
*
* @param { String } category - server, client, gateway, directory, etc.
* @param { String } type - phone, pc, web, etc.
* @param { String } name - "Converse"
* @param { String } lang - en, el, de, etc.
* @param {String} category - server, client, gateway, directory, etc.
* @param {String} type - phone, pc, web, etc.
* @param {String} name - "Converse"
* @param {String} lang - en, el, de, etc.
*
* @example _converse.api.disco.own.identities.clear();
*/
@ -102,7 +102,7 @@ export default {
/**
* Lets you register new disco features for this client (i.e. instance of Converse)
* @method api.disco.own.features.add
* @param { String } name - e.g. http://jabber.org/protocol/caps
* @param {String} name - e.g. http://jabber.org/protocol/caps
* @example _converse.api.disco.own.features.add("http://jabber.org/protocol/caps");
*/
add (name) {
@ -134,8 +134,8 @@ export default {
* Query for information about an XMPP entity
*
* @method api.disco.info
* @param { string } jid The Jabber ID of the entity to query
* @param { string } [node] A specific node identifier associated with the JID
* @param {string} jid The Jabber ID of the entity to query
* @param {string} [node] A specific node identifier associated with the JID
* @returns {promise} Promise which resolves once we have a result from the server.
*/
info (jid, node) {
@ -155,8 +155,8 @@ export default {
* Query for items associated with an XMPP entity
*
* @method api.disco.items
* @param { string } jid The Jabber ID of the entity to query for items
* @param { string } [node] A specific node identifier associated with the JID
* @param {string} jid The Jabber ID of the entity to query for items
* @param {string} [node] A specific node identifier associated with the JID
* @returns {promise} Promise which resolves once we have a result from the server.
*/
items (jid, node) {
@ -184,8 +184,8 @@ export default {
* Get the corresponding `DiscoEntity` instance.
*
* @method api.disco.entities.get
* @param { string } jid The Jabber ID of the entity
* @param { boolean } [create] Whether the entity should be created if it doesn't exist.
* @param {string} jid The Jabber ID of the entity
* @param {boolean} [create] Whether the entity should be created if it doesn't exist.
* @example _converse.api.disco.entities.get(jid);
*/
async get (jid, create=false) {
@ -195,29 +195,19 @@ export default {
}
if (_converse.disco_entities === undefined) {
// Happens during tests when disco lookups happen asynchronously after teardown.
log.warn(`Tried to look up entity ${jid} but _converse.disco_entities has been torn down`);
const msg = `Tried to look up entity ${jid} but _converse.disco_entities has been torn down`;
log.warn(msg);
return;
}
const entity = _converse.disco_entities.get(jid);
if (entity || !create) {
return entity;
}
return api.disco.entities.create({ jid });
return api.disco.entities.create(jid);
},
/**
* Return any disco items advertised on this entity
*
* @method api.disco.entities.items
* @param { string } jid The Jabber ID of the entity for which we want to fetch items
* @example api.disco.entities.items(jid);
*/
items (jid) {
return _converse.disco_entities.filter(e => e.get('parent_jids')?.includes(jid));
},
/**
* Create a new disco entity. It's identity and features
* Create a new disco entity. It's identity and features
* will automatically be fetched from cache or from the
* XMPP server.
*
@ -225,17 +215,14 @@ export default {
* `ignore_cache: true` in the options parameter.
*
* @method api.disco.entities.create
* @param { object } data
* @param { string } data.jid - The Jabber ID of the entity
* @param { string } data.parent_jid - The Jabber ID of the parent entity
* @param { string } data.name
* @param { object } [options] - Additional options
* @param { boolean } [options.ignore_cache]
* @param {string} jid The Jabber ID of the entity
* @param {object} [options] Additional options
* @param {boolean} [options.ignore_cache]
* If true, fetch all features from the XMPP server instead of restoring them from cache
* @example _converse.api.disco.entities.create({ jid }, {'ignore_cache': true});
* @example _converse.api.disco.entities.create(jid, {'ignore_cache': true});
*/
create (data, options) {
return _converse.disco_entities.create(data, options);
create (jid, options) {
return _converse.disco_entities.create({'jid': jid}, options);
}
},
@ -248,11 +235,11 @@ export default {
* Return a given feature of a disco entity
*
* @method api.disco.features.get
* @param { string } feature The feature that might be
* @param {string} feature The feature that might be
* supported. In the XML stanza, this is the `var`
* attribute of the `<feature>` element. For
* example: `http://jabber.org/protocol/muc`
* @param { string } jid The JID of the entity
* @param {string} jid The JID of the entity
* (and its associated items) which should be queried
* @returns {promise} A promise which resolves with a list containing
* _converse.Entity instances representing the entity
@ -262,56 +249,22 @@ export default {
* api.disco.features.get(Strophe.NS.MAM, _converse.bare_jid);
*/
async get (feature, jid) {
if (!jid) throw new TypeError('You need to provide an entity JID');
const entity = await api.disco.entities.get(jid, true);
if (!jid) {
throw new TypeError('You need to provide an entity JID');
}
await api.waitUntil('discoInitialized');
let entity = await api.disco.entities.get(jid, true);
if (_converse.disco_entities === undefined && !api.connection.connected()) {
// Happens during tests when disco lookups happen asynchronously after teardown.
log.warn(`Tried to get feature ${feature} for ${jid} but _converse.disco_entities has been torn down`);
return [];
const msg = `Tried to get feature ${feature} for ${jid} but _converse.disco_entities has been torn down`;
log.warn(msg);
return;
}
const promises = [
entity.getFeature(feature),
...api.disco.entities.items(jid).map(i => i.getFeature(feature))
];
entity = await entity.waitUntilFeaturesDiscovered;
const promises = [...entity.items.map(i => i.hasFeature(feature)), entity.hasFeature(feature)];
const result = await Promise.all(promises);
return result.filter(isObject);
},
/**
* Returns true if an entity with the given JID, or if one of its
* associated items, supports a given feature.
*
* @method api.disco.features.has
* @param { string } feature The feature that might be
* supported. In the XML stanza, this is the `var`
* attribute of the `<feature>` element. For
* example: `http://jabber.org/protocol/muc`
* @param { string } jid The JID of the entity
* (and its associated items) which should be queried
* @returns {Promise} A promise which resolves with a boolean
* @example
* api.disco.features.has(Strophe.NS.MAM, _converse.bare_jid);
*/
async has (feature, jid) {
if (!jid) throw new TypeError('You need to provide an entity JID');
const entity = await api.disco.entities.get(jid, true);
if (_converse.disco_entities === undefined && !api.connection.connected()) {
// Happens during tests when disco lookups happen asynchronously after teardown.
log.warn(`Tried to check if ${jid} supports feature ${feature}`);
return false;
}
if (await entity.getFeature(feature)) {
return true;
}
const result = await Promise.all(api.disco.entities.items(jid).map(i => i.getFeature(feature)));
return result.map(isObject).includes(true);
}
},
@ -319,11 +272,11 @@ export default {
* Used to determine whether an entity supports a given feature.
*
* @method api.disco.supports
* @param { string } feature The feature that might be
* @param {string} feature The feature that might be
* supported. In the XML stanza, this is the `var`
* attribute of the `<feature>` element. For
* example: `http://jabber.org/protocol/muc`
* @param { string } jid The JID of the entity
* @param {string} jid The JID of the entity
* (and its associated items) which should be queried
* @returns {promise} A promise which resolves with `true` or `false`.
* @example
@ -333,15 +286,16 @@ export default {
* // The feature is not supported
* }
*/
supports (feature, jid) {
return api.disco.features.has(feature, jid);
async supports (feature, jid) {
const features = await api.disco.features.get(feature, jid) || [];
return features.length > 0;
},
/**
* Refresh the features, fields and identities associated with a
* disco entity by refetching them from the server
* @method api.disco.refresh
* @param { string } jid The JID of the entity whose features are refreshed.
* @param {string} jid The JID of the entity whose features are refreshed.
* @returns {promise} A promise which resolves once the features have been refreshed
* @example
* await api.disco.refresh('room@conference.example.org');
@ -362,7 +316,7 @@ export default {
entity.queryInfo();
} else {
// Create it if it doesn't exist
entity = await api.disco.entities.create({ jid }, {'ignore_cache': true});
entity = await api.disco.entities.create(jid, {'ignore_cache': true});
}
return entity.waitUntilFeaturesDiscovered;
},
@ -379,7 +333,7 @@ export default {
* Return all the features associated with a disco entity
*
* @method api.disco.getFeatures
* @param { string } jid The JID of the entity whose features are returned.
* @param {string} jid The JID of the entity whose features are returned.
* @returns {promise} A promise which resolves with the returned features
* @example
* const features = await api.disco.getFeatures('room@conference.example.org');
@ -401,7 +355,7 @@ export default {
* See [XEP-0129: Service Discovery Extensions](https://xmpp.org/extensions/xep-0128.html)
*
* @method api.disco.getFields
* @param { string } jid The JID of the entity whose fields are returned.
* @param {string} jid The JID of the entity whose fields are returned.
* @example
* const fields = await api.disco.getFields('room@conference.example.org');
*/
@ -424,15 +378,15 @@ export default {
* XEP-0163: https://xmpp.org/extensions/xep-0163.html#support
*
* @method api.disco.getIdentity
* @param { string } The identity category.
* @param {string} The identity category.
* In the XML stanza, this is the `category`
* attribute of the `<identity>` element.
* For example: 'pubsub'
* @param { string } type The identity type.
* @param {string} type The identity type.
* In the XML stanza, this is the `type`
* attribute of the `<identity>` element.
* For example: 'pep'
* @param { string } jid The JID of the entity which might have the identity
* @param {string} jid The JID of the entity which might have the identity
* @returns {promise} A promise which resolves with a map indicating
* whether an identity with a given type is provided by the entity.
* @example

View File

@ -1,8 +1,8 @@
import log from '@converse/headless/log.js';
import sizzle from 'sizzle';
import { Collection } from '@converse/skeletor/src/collection';
import log from "@converse/headless/log.js";
import sizzle from "sizzle";
import { Collection } from "@converse/skeletor/src/collection";
import { Model } from '@converse/skeletor/src/model.js';
import { _converse, api, converse } from '@converse/headless/core.js';
import { _converse, api, converse } from "@converse/headless/core.js";
import { getOpenPromise } from '@converse/openpromise';
const { Strophe } = converse.env;
@ -19,7 +19,7 @@ const { Strophe } = converse.env;
const DiscoEntity = Model.extend({
idAttribute: 'jid',
initialize (_, options) {
async initialize (_, options) {
this.waitUntilFeaturesDiscovered = getOpenPromise();
this.dataforms = new Collection();
@ -29,12 +29,17 @@ const DiscoEntity = Model.extend({
this.features = new Collection();
id = `converse.features-${this.get('jid')}`;
this.features.browserStorage = _converse.createStore(id, 'session');
this.listenTo(this.features, 'add', this.onFeatureAdded);
this.listenTo(this.features, 'add', this.onFeatureAdded)
this.fields = new Collection();
id = `converse.fields-${this.get('jid')}`;
this.fields.browserStorage = _converse.createStore(id, 'session');
this.listenTo(this.fields, 'add', this.onFieldAdded);
this.listenTo(this.fields, 'add', this.onFieldAdded)
this.items = new _converse.DiscoEntities();
id = `converse.disco-items-${this.get('jid')}`;
this.items.browserStorage = _converse.createStore(id, 'session');
await new Promise(f => this.items.fetch({'success': f, 'error': f}));
this.identities = new Collection();
id = `converse.identities-${this.get('jid')}`;
@ -54,7 +59,7 @@ const DiscoEntity = Model.extend({
await this.waitUntilFeaturesDiscovered;
return this.identities.findWhere({
'category': category,
'type': type,
'type': type
});
},
@ -62,12 +67,12 @@ const DiscoEntity = Model.extend({
* Returns a Promise which resolves with a map indicating
* whether a given feature is supported.
* @private
* @method _converse.DiscoEntity#getFeature
* @method _converse.DiscoEntity#hasFeature
* @param { String } feature - The feature that might be supported.
*/
async getFeature (feature) {
await this.waitUntilFeaturesDiscovered;
if (this.features.findWhere({ 'var': feature })) {
async hasFeature (feature) {
await this.waitUntilFeaturesDiscovered
if (this.features.findWhere({'var': feature})) {
return this;
}
},
@ -101,7 +106,7 @@ const DiscoEntity = Model.extend({
} else {
const store_id = this.features.browserStorage.name;
const result = await this.features.browserStorage.store.getItem(store_id);
if ((result && result.length === 0) || result === null) {
if (result && result.length === 0 || result === null) {
this.queryInfo();
} else {
this.features.fetch({
@ -109,9 +114,9 @@ const DiscoEntity = Model.extend({
success: () => {
this.waitUntilFeaturesDiscovered.resolve(this);
this.trigger('featuresDiscovered');
},
}
});
this.identities.fetch({ add: true });
this.identities.fetch({add: true});
}
}
},
@ -130,27 +135,22 @@ const DiscoEntity = Model.extend({
onDiscoItems (stanza) {
sizzle(`query[xmlns="${Strophe.NS.DISCO_ITEMS}"] item`, stanza).forEach(item => {
if (item.getAttribute('node')) {
if (item.getAttribute("node")) {
// XXX: Ignore nodes for now.
// See: https://xmpp.org/extensions/xep-0030.html#items-nodes
return;
}
const jid = item.getAttribute('jid');
const entity = _converse.disco_entities.get(jid);
if (entity) {
entity.set({ parent_jids: [this.get('jid')] });
} else {
api.disco.entities.create({
jid,
'parent_jids': [this.get('jid')],
'name': item.getAttribute('name'),
});
if (this.items.get(jid) === undefined) {
const entities = _converse.disco_entities;
const entity = entities.get(jid) || entities.create({ jid, name: item.getAttribute('name') });
this.items.create(entity);
}
});
},
async queryForItems () {
if (this.identities.where({ 'category': 'server' }).length === 0) {
if (this.identities.where({'category': 'server'}).length === 0) {
// Don't fetch features and items if this is not a
// server or a conference component.
return;
@ -159,12 +159,12 @@ const DiscoEntity = Model.extend({
this.onDiscoItems(stanza);
},
async onInfo (stanza) {
onInfo (stanza) {
Array.from(stanza.querySelectorAll('identity')).forEach(identity => {
this.identities.create({
'category': identity.getAttribute('category'),
'type': identity.getAttribute('type'),
'name': identity.getAttribute('name'),
'name': identity.getAttribute('name')
});
});
@ -173,19 +173,19 @@ const DiscoEntity = Model.extend({
sizzle('field', form).forEach(field => {
data[field.getAttribute('var')] = {
'value': field.querySelector('value')?.textContent,
'type': field.getAttribute('type'),
'type': field.getAttribute('type')
};
});
this.dataforms.create(data);
});
if (stanza.querySelector(`feature[var="${Strophe.NS.DISCO_ITEMS}"]`)) {
await this.queryForItems();
this.queryForItems();
}
Array.from(stanza.querySelectorAll('feature')).forEach(feature => {
this.features.create({
'var': feature.getAttribute('var'),
'from': stanza.getAttribute('from'),
'from': stanza.getAttribute('from')
});
});
@ -194,13 +194,13 @@ const DiscoEntity = Model.extend({
this.fields.create({
'var': field.getAttribute('var'),
'value': field.querySelector('value')?.textContent,
'from': stanza.getAttribute('from'),
'from': stanza.getAttribute('from')
});
});
this.waitUntilFeaturesDiscovered.resolve(this);
this.trigger('featuresDiscovered');
},
}
});
export default DiscoEntity;

View File

@ -2,7 +2,7 @@
describe("Service Discovery", function () {
describe("Whenever a server is queried for its features", function () {
describe("Whenever converse.js queries a server for its features", function () {
it("stores the features it receives",
mock.initConverse(
@ -76,12 +76,23 @@ describe("Service Discovery", function () {
'var': 'jabber:iq:version'});
_converse.connection._dataRecv(mock.createRequest(stanza));
let entities = await _converse.api.disco.entities.get()
expect(entities.length).toBe(2); // We have an extra entity, which is the user's JID
expect(entities.get(_converse.domain).features.length).toBe(5);
expect(entities.get(_converse.domain).identities.length).toBe(3);
expect(entities.get('montague.lit').features.where({'var': 'jabber:iq:version'}).length).toBe(1);
expect(entities.get('montague.lit').features.where({'var': 'jabber:iq:time'}).length).toBe(1);
expect(entities.get('montague.lit').features.where({'var': 'jabber:iq:register'}).length).toBe(1);
expect(entities.get('montague.lit').features.where(
{'var': 'http://jabber.org/protocol/disco#items'}).length).toBe(1);
expect(entities.get('montague.lit').features.where(
{'var': 'http://jabber.org/protocol/disco#info'}).length).toBe(1);
await u.waitUntil(function () {
// Converse.js sees that the entity has a disco#items feature,
// so it will make a query for it.
return IQ_stanzas.filter(iq => iq.querySelector('query[xmlns="http://jabber.org/protocol/disco#items"]')).length > 0;
});
/* <iq type='result'
* from='catalog.shakespeare.lit'
* to='romeo@montague.net/orchard'
@ -108,8 +119,9 @@ describe("Service Discovery", function () {
* </query>
* </iq>
*/
stanza = IQ_stanzas.find(iq => iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#items"]'));
stanza = IQ_stanzas.find(function (iq) {
return iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#items"]');
});
const items_IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)];
stanza = $iq({
'type': 'result',
@ -140,19 +152,9 @@ describe("Service Discovery", function () {
});
_converse.connection._dataRecv(mock.createRequest(stanza));
const entities = await _converse.api.disco.entities.get()
expect(entities.length).toBe(5); // We have an extra entity, which is the user's JID
expect(entities.get(_converse.domain).features.length).toBe(5);
expect(entities.get(_converse.domain).identities.length).toBe(3);
expect(entities.get('montague.lit').features.where({'var': 'jabber:iq:version'}).length).toBe(1);
expect(entities.get('montague.lit').features.where({'var': 'jabber:iq:time'}).length).toBe(1);
expect(entities.get('montague.lit').features.where({'var': 'jabber:iq:register'}).length).toBe(1);
expect(entities.get('montague.lit').features.where(
{'var': 'http://jabber.org/protocol/disco#items'}).length).toBe(1);
expect(entities.get('montague.lit').features.where(
{'var': 'http://jabber.org/protocol/disco#info'}).length).toBe(1);
await u.waitUntil(() => _converse.disco_entities);
entities = _converse.disco_entities;
expect(entities.length).toBe(5);
expect(entities.map(e => e.get('jid'))).toEqual([
'montague.lit',
'romeo@montague.lit',
@ -160,14 +162,11 @@ describe("Service Discovery", function () {
'plays.shakespeare.lit',
'words.shakespeare.lit'
]);
const { api, domain } = _converse;
let entity = entities.get(_converse.domain);
expect(api.disco.entities.items(domain).length).toBe(3);
expect(api.disco.entities.items(domain).map(e => e.get('jid'))).toEqual(
['people.shakespeare.lit', 'plays.shakespeare.lit', 'words.shakespeare.lit']
)
expect(entity.items.length).toBe(3);
expect(entity.items.pluck('jid').includes('people.shakespeare.lit')).toBeTruthy();
expect(entity.items.pluck('jid').includes('plays.shakespeare.lit')).toBeTruthy();
expect(entity.items.pluck('jid').includes('words.shakespeare.lit')).toBeTruthy();
expect(entity.identities.where({'category': 'conference'}).length).toBe(1);
expect(entity.identities.where({'category': 'directory'}).length).toBe(1);

View File

@ -69,7 +69,7 @@ export async function initializeDisco () {
const collection = await _converse.disco_entities.fetchEntities();
if (collection.length === 0 || !collection.get(_converse.domain)) {
// If we don't have an entity for our own XMPP server, create one.
api.disco.entities.create({'jid': _converse.domain}, {'ignore_cache': true});
_converse.disco_entities.create({'jid': _converse.domain});
}
/**
* Triggered once the `converse-disco` plugin has been initialized and the

View File

@ -3,7 +3,6 @@
* @copyright 2022, the Converse.js contributors
* @license Mozilla Public License (MPLv2)
*/
import './utils.js';
import { Model } from '@converse/skeletor/src/model.js';
import { _converse, api, converse } from "../../core.js";
import { getOpenPromise } from '@converse/openpromise';
@ -86,8 +85,8 @@ converse.plugins.add('converse-emoji', {
async initialize () {
if (!converse.emojis.initialized) {
converse.emojis.initialized = true;
const module = await import(/*webpackChunkName: "emojis" */ './emoji.json');
const json = converse.emojis.json = module.default;
const { default: json } = await import(/*webpackChunkName: "emojis" */ './emoji.json');
converse.emojis.json = json;
converse.emojis.by_sn = Object.keys(json).reduce((result, cat) => Object.assign(result, json[cat]), {});
converse.emojis.list = Object.values(converse.emojis.by_sn);
converse.emojis.list.sort((a, b) => a.sn < b.sn ? -1 : (a.sn > b.sn ? 1 : 0));

View File

@ -163,7 +163,7 @@ function shortnamesToUnicode (str) {
* Determines whether the passed in string is just a single emoji shortname;
* @namespace u
* @method u.isOnlyEmojis
* @param { String } text - A string which migh be just an emoji shortname
* @param { String } shortname - A string which migh be just an emoji shortname
* @returns { Boolean }
*/
function isOnlyEmojis (text) {

View File

@ -15,8 +15,8 @@ export default {
*
* @method api.headlines.get
* @param {String|String[]} jids - e.g. 'buddy@example.com' or ['buddy1@example.com', 'buddy2@example.com']
* @param { Object } [attrs] - Attributes to be set on the _converse.ChatBox model.
* @param { Boolean } [create=false] - Whether the chat should be created if it's not found.
* @param {Object} [attrs] - Attributes to be set on the _converse.ChatBox model.
* @param {Boolean} [create=false] - Whether the chat should be created if it's not found.
* @returns { Promise<_converse.HeadlinesFeed> }
*/
async get (jids, attrs={}, create=false) {

View File

@ -4,7 +4,7 @@
*/
import HeadlinesFeed from './feed.js';
import headlines_api from './api.js';
import { _converse, api, converse } from "../../core.js";
import { _converse, api, converse } from "@converse/headless/core";
import { onHeadlineMessage } from './utils.js';

View File

@ -4,7 +4,7 @@ import { parseMessage } from '@converse/headless/plugins/chat/parsers';
/**
* Handler method for all incoming messages of type "headline".
* @param { Element } stanza
* @param { XMLElement } stanza
*/
export async function onHeadlineMessage (stanza) {
if (isHeadline(stanza) || isServerMessage(stanza)) {

View File

@ -1,7 +1,6 @@
import { RSM } from '@converse/headless/shared/rsm';
import log from '@converse/headless/log';
import sizzle from "sizzle";
import { RSM } from '@converse/headless/shared/rsm';
import { TimeoutError } from '../../shared/errors.js';
import { _converse, api, converse } from "@converse/headless/core";
const { Strophe, $iq, dayjs } = converse.env;
@ -271,7 +270,7 @@ export default {
const { __ } = _converse;
const err_msg = __("Timeout while trying to fetch archived messages.");
log.error(err_msg);
error = new TimeoutError(err_msg);
error = new _converse.TimeoutError(err_msg);
return { messages, error };
} else if (u.isErrorStanza(iq_result)) {

View File

@ -100,7 +100,7 @@ export async function handleMAMResult (model, result, query, options, should_pag
/**
* @typedef { Object } MAMOptions
* A map of MAM related options that may be passed to fetchArchivedMessages
* @param { number } [options.max] - The maximum number of items to return.
* @param { integer } [options.max] - The maximum number of items to return.
* Defaults to "archived_messages_page_size"
* @param { string } [options.after] - The XEP-0359 stanza ID of a message
* after which messages should be returned. Implies forward paging.
@ -117,7 +117,7 @@ export async function handleMAMResult (model, result, query, options, should_pag
/**
* Fetch XEP-0313 archived messages based on the passed in criteria.
* @param { ChatBox | ChatRoom } model
* @param { _converse.ChatBox | _converse.ChatRoom } model
* @param { MAMOptions } [options]
* @param { ('forwards'|'backwards'|null)} [should_page=null] - Determines whether
* this function should recursively page through the entire result set if a limited

View File

@ -45,7 +45,7 @@ export async function getAffiliationList (affiliation, muc_jid) {
}
/**
* Given an occupant model, see which affiliations may be assigned by that user
* Given an occupant model, see which affiliations may be assigned to that user.
* @param { Model } occupant
* @returns { Array<('owner'|'admin'|'member'|'outcast'|'none')> } - An array of assignable affiliations
*/
@ -54,9 +54,9 @@ export function getAssignableAffiliations (occupant) {
if (!Array.isArray(disabled)) {
disabled = disabled ? AFFILIATIONS : [];
}
if (occupant?.get('affiliation') === 'owner') {
if (occupant.get('affiliation') === 'owner') {
return AFFILIATIONS.filter(a => !disabled.includes(a));
} else if (occupant?.get('affiliation') === 'admin') {
} else if (occupant.get('affiliation') === 'admin') {
return AFFILIATIONS.filter(a => !['owner', 'admin', ...disabled].includes(a));
} else {
return [];

View File

@ -1,8 +1,7 @@
import log from '../../log';
import u from '../../utils/form';
import { Strophe } from 'strophe.js/src/strophe';
import { _converse, api, converse } from '../../core.js';
const { u } = converse.env;
import { _converse, api } from '../../core.js';
export default {
@ -23,7 +22,7 @@ export default {
* @method api.rooms.create
* @param {(string[]|string)} jid|jids The JID or array of
* JIDs of the chatroom(s) to create
* @param { object } [attrs] attrs The room attributes
* @param {object} [attrs] attrs The room attributes
* @returns {Promise} Promise which resolves with the Model representing the chat.
*/
create (jids, attrs = {}) {
@ -45,24 +44,24 @@ export default {
* Similar to {@link api.chats.open}, but for groupchats.
*
* @method api.rooms.open
* @param { string } jid The room JID or JIDs (if not specified, all
* @param {string} jid The room JID or JIDs (if not specified, all
* currently open rooms will be returned).
* @param { string } attrs A map containing any extra room attributes.
* @param { string } [attrs.nick] The current user's nickname for the MUC
* @param { boolean } [attrs.auto_configure] A boolean, indicating
* @param {string} attrs A map containing any extra room attributes.
* @param {string} [attrs.nick] The current user's nickname for the MUC
* @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
* @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
* @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
* @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.

View File

@ -1,3 +1,4 @@
import log from '../../log';
import { Strophe } from 'strophe.js/src/strophe';
import { _converse, api } from '../../core.js';
@ -19,7 +20,6 @@ const ChatRoomMessageMixin = {
this.on('change:type', () => this.setOccupant());
this.on('change:is_ephemeral', () => this.setTimerForEphemeralMessage());
this.chatbox = this.collection?.chatbox;
this.setTimerForEphemeralMessage();
this.setOccupant();
/**
@ -53,20 +53,24 @@ const ChatRoomMessageMixin = {
return (
['all', 'moderator'].includes(api.settings.get('allow_message_retraction')) &&
this.get(`stanza_id ${this.get('from_muc')}`) &&
this.chatbox.canModerateMessages()
this.collection.chatbox.canModerateMessages()
);
},
checkValidity () {
const result = _converse.Message.prototype.checkValidity.call(this);
!result && this.chatbox.debouncedRejoin();
!result && this.collection.chatbox.debouncedRejoin();
return result;
},
onOccupantRemoved () {
this.stopListening(this.occupant);
delete this.occupant;
this.listenTo(this.chatbox.occupants, 'add', this.onOccupantAdded);
const chatbox = this?.collection?.chatbox;
if (!chatbox) {
return log.error(`Could not get collection.chatbox for message: ${JSON.stringify(this.toJSON())}`);
}
this.listenTo(chatbox.occupants, 'add', this.onOccupantAdded);
},
onOccupantAdded (occupant) {
@ -77,6 +81,10 @@ const ChatRoomMessageMixin = {
} else if (occupant.get('nick') !== Strophe.getResourceFromJid(this.get('from'))) {
return;
}
const chatbox = this?.collection?.chatbox;
if (!chatbox) {
return log.error(`Could not get collection.chatbox for message: ${JSON.stringify(this.toJSON())}`);
}
this.occupant = occupant;
if (occupant.get('jid')) {
@ -85,40 +93,32 @@ const ChatRoomMessageMixin = {
this.trigger('occupantAdded');
this.listenTo(this.occupant, 'destroy', this.onOccupantRemoved);
this.stopListening(this.chatbox.occupants, 'add', this.onOccupantAdded);
},
getOccupant() {
if (this.occupant) return this.occupant;
this.setOccupant();
return this.occupant;
this.stopListening(chatbox.occupants, 'add', this.onOccupantAdded);
},
setOccupant () {
if (this.get('type') !== 'groupchat' || this.isEphemeral() || this.occupant) {
return;
}
const chatbox = this?.collection?.chatbox;
if (!chatbox) {
return log.error(`Could not get collection.chatbox for message: ${JSON.stringify(this.toJSON())}`);
}
const nick = Strophe.getResourceFromJid(this.get('from'));
const occupant_id = this.get('occupant_id');
this.occupant = chatbox.occupants.findOccupant({ nick, occupant_id });
this.occupant = this.chatbox.occupants.findOccupant({ nick, occupant_id });
if (!this.occupant) {
this.occupant = this.chatbox.occupants.create({
nick,
occupant_id,
jid: this.get('from_real_jid'),
});
if (api.settings.get('muc_send_probes')) {
const jid = `${this.chatbox.get('jid')}/${nick}`;
api.user.presence.send('probe', jid);
}
if (!this.occupant && api.settings.get('muc_send_probes')) {
this.occupant = chatbox.occupants.create({ nick, occupant_id, 'type': 'unavailable' });
const jid = `${chatbox.get('jid')}/${nick}`;
api.user.presence.send('probe', jid);
}
this.listenTo(this.occupant, 'destroy', this.onOccupantRemoved);
if (this.occupant) {
this.listenTo(this.occupant, 'destroy', this.onOccupantRemoved);
} else {
this.listenTo(chatbox.occupants, 'add', this.onOccupantAdded);
}
}
};

View File

@ -1,24 +1,23 @@
import debounce from 'lodash-es/debounce';
import invoke from 'lodash-es/invoke';
import isElement from 'lodash-es/isElement';
import log from '../../log';
import p from '../../utils/parse-helpers';
import pick from 'lodash-es/pick';
import sizzle from 'sizzle';
import u from '../../utils/form';
import { Model } from '@converse/skeletor/src/model.js';
import { ROOMSTATUS } from './constants.js';
import { Strophe, $build, $iq, $msg, $pres } from 'strophe.js/src/strophe';
import { TimeoutError } from '../../shared/errors.js';
import { _converse, api, converse } from '../../core.js';
import { computeAffiliationsDelta, setAffiliations, getAffiliationList } from './affiliations/utils.js';
import { handleCorrection } from '@converse/headless/shared/chat/utils.js';
import { getOpenPromise } from '@converse/openpromise';
import { handleCorrection } from '../../shared/chat/utils.js';
import { initStorage } from '../../utils/storage.js';
import { isArchived, getMediaURLsMetadata } from '../../shared/parsers.js';
import { isUniView, getUniqueId, safeSave } from '../../utils/core.js';
import { initStorage } from '@converse/headless/utils/storage.js';
import { isArchived, getMediaURLsMetadata } from '@converse/headless/shared/parsers.js';
import { isUniView, getUniqueId, safeSave } from '@converse/headless/utils/core.js';
import { parseMUCMessage, parseMUCPresence } from './parsers.js';
import { sendMarker } from '../../shared/actions.js';
const { u } = converse.env;
import { sendMarker } from '@converse/headless/shared/actions.js';
import { ROOMSTATUS } from './constants.js';
const OWNER_COMMANDS = ['owner'];
const ADMIN_COMMANDS = ['admin', 'ban', 'deop', 'destroy', 'member', 'op', 'revoke'];
@ -130,15 +129,6 @@ const ChatRoomMixin = {
return this.session.get('connection_status') === ROOMSTATUS.ENTERED;
},
/**
* Checks whether this MUC qualifies for subscribing to XEP-0437 Room Activity Indicators (RAI)
* @method _converse.ChatRoom#isRAICandidate
* @returns { Boolean }
*/
isRAICandidate () {
return this.get('hidden') && api.settings.get('muc_subscribe_to_rai') && this.getOwnAffiliation() !== 'none';
},
/**
* Checks whether we're still joined and if so, restores the MUC state from cache.
* @private
@ -146,23 +136,18 @@ const ChatRoomMixin = {
* @returns { Boolean } Returns `true` if we're still joined, otherwise returns `false`.
*/
async restoreFromCache () {
if (this.isEntered()) {
if (this.isEntered() && (await this.isJoined())) {
// We've restored the room from cache and we're still joined.
await new Promise(r => this.features.fetch({ 'success': r, 'error': r }));
await new Promise(r => this.config.fetch({ 'success': r, 'error': r }));
await this.fetchOccupants().catch(e => log.error(e));
if (this.isRAICandidate()) {
this.session.save('connection_status', ROOMSTATUS.DISCONNECTED);
this.enableRAI();
return true;
} else if (await this.isJoined()) {
await new Promise(r => this.config.fetch({ 'success': r, 'error': r }));
await new Promise(r => this.features.fetch({ 'success': r, 'error': r }));
await this.fetchMessages().catch(e => log.error(e));
return true;
}
await this.fetchMessages().catch(e => log.error(e));
return true;
} else {
this.session.save('connection_status', ROOMSTATUS.DISCONNECTED);
this.clearOccupantsCache();
return false;
}
this.session.save('connection_status', ROOMSTATUS.DISCONNECTED);
this.clearOccupantsCache();
return false;
},
/**
@ -226,7 +211,7 @@ const ChatRoomMixin = {
* *Hook* which allows plugins to update an outgoing MUC join presence stanza
* @event _converse#constructedMUCPresence
* @param { _converse.ChatRoom } - The MUC from which this message stanza is being sent.
* @param { Element } stanza - The stanza which will be sent out
* @param { XMLElement } stanza - The stanza which will be sent out
*/
stanza = await api.hook('constructedMUCPresence', this, stanza);
return stanza;
@ -291,7 +276,10 @@ const ChatRoomMixin = {
const roomstatus = ROOMSTATUS;
const conn_status = this.session.get('connection_status');
if (this.get('hidden')) {
if (conn_status === roomstatus.ENTERED && this.isRAICandidate()) {
if (conn_status === roomstatus.ENTERED &&
api.settings.get('muc_subscribe_to_rai') &&
this.getOwnAffiliation() !== 'none') {
this.sendMarkerForLastMessage('received', true);
await this.leave();
this.enableRAI();
@ -359,7 +347,7 @@ const ChatRoomMixin = {
async onConnectionStatusChanged () {
if (this.isEntered()) {
if (this.isRAICandidate()) {
if (this.get('hidden') && api.settings.get('muc_subscribe_to_rai') && this.getOwnAffiliation() !== 'none') {
try {
await this.leave();
} catch (e) {
@ -456,7 +444,7 @@ const ChatRoomMixin = {
async handleErrorMessageStanza (stanza) {
const { __ } = _converse;
const attrs = await parseMUCMessage(stanza, this);
const attrs = await parseMUCMessage(stanza, this, _converse);
if (!(await this.shouldShowErrorMessage(attrs))) {
return;
}
@ -507,7 +495,7 @@ const ChatRoomMixin = {
* Handles incoming message stanzas from the service that hosts this MUC
* @private
* @method _converse.ChatRoom#handleMessageFromMUCHost
* @param { Element } stanza
* @param { XMLElement } stanza
*/
handleMessageFromMUCHost (stanza) {
if (this.isEntered()) {
@ -528,7 +516,7 @@ const ChatRoomMixin = {
* Handles XEP-0452 MUC Mention Notification messages
* @private
* @method _converse.ChatRoom#handleForwardedMentions
* @param { Element } stanza
* @param { XMLElement } stanza
*/
handleForwardedMentions (stanza) {
if (this.isEntered()) {
@ -547,7 +535,7 @@ const ChatRoomMixin = {
'num_unread': this.get('num_unread') + mentions.length
});
mentions.forEach(async stanza => {
const attrs = await parseMUCMessage(stanza, this);
const attrs = await parseMUCMessage(stanza, this, _converse);
const data = { stanza, attrs, 'chatbox': this };
api.trigger('message', data);
});
@ -558,11 +546,9 @@ const ChatRoomMixin = {
* Parses an incoming message stanza and queues it for processing.
* @private
* @method _converse.ChatRoom#handleMessageStanza
* @param { Element } stanza
* @param { XMLElement } stanza
*/
async handleMessageStanza (stanza) {
stanza = stanza.tree?.() ?? stanza;
const type = stanza.getAttribute('type');
if (type === 'error') {
return this.handleErrorMessageStanza(stanza);
@ -588,7 +574,7 @@ const ChatRoomMixin = {
*/
let attrs;
try {
attrs = await parseMUCMessage(stanza, this);
attrs = await parseMUCMessage(stanza, this, _converse);
} catch (e) {
return log.error(e);
}
@ -709,9 +695,9 @@ const ChatRoomMixin = {
* or error message within a specific timeout period.
* @private
* @method _converse.ChatRoom#sendTimedMessage
* @param { _converse.Message|Element } message
* @returns { Promise<Element>|Promise<TimeoutError> } Returns a promise
* which resolves with the reflected message stanza or with an error stanza or {@link TimeoutError}.
* @param { _converse.Message|XMLElement } message
* @returns { Promise<XMLElement>|Promise<_converse.TimeoutError> } Returns a promise
* which resolves with the reflected message stanza or with an error stanza or {@link _converse.TimeoutError}.
*/
sendTimedMessage (el) {
if (typeof el.tree === 'function') {
@ -724,10 +710,9 @@ const ChatRoomMixin = {
el.setAttribute('id', id);
}
const promise = getOpenPromise();
const timeout = api.settings.get('stanza_timeout');
const timeoutHandler = _converse.connection.addTimedHandler(timeout, () => {
const timeoutHandler = _converse.connection.addTimedHandler(_converse.STANZA_TIMEOUT, () => {
_converse.connection.deleteHandler(handler);
const err = new TimeoutError('Timeout Error: No response from server');
const err = new _converse.TimeoutError('Timeout Error: No response from server');
promise.resolve(err);
return false;
});
@ -770,14 +755,14 @@ const ChatRoomMixin = {
message.set({
'retracted': new Date().toISOString(),
'retracted_id': origin_id,
'retraction_id': stanza.tree().getAttribute('id'),
'retraction_id': stanza.nodeTree.getAttribute('id'),
'editable': false
});
const result = await this.sendTimedMessage(stanza);
if (u.isErrorStanza(result)) {
log.error(result);
} else if (result instanceof TimeoutError) {
} else if (result instanceof _converse.TimeoutError) {
log.error(result);
message.save({
editable,
@ -794,7 +779,7 @@ const ChatRoomMixin = {
* Retract someone else's message in this groupchat.
* @private
* @method _converse.ChatRoom#retractOtherMessage
* @param { _converse.ChatRoomMessage } message - The message which we're retracting.
* @param { _converse.Message } message - The message which we're retracting.
* @param { string } [reason] - The reason for retracting the message.
* @example
* const room = await api.rooms.get(jid);
@ -829,7 +814,7 @@ const ChatRoomMixin = {
* Sends an IQ stanza to the XMPP server to retract a message in this groupchat.
* @private
* @method _converse.ChatRoom#sendRetractionIQ
* @param { _converse.ChatRoomMessage } message - The message which we're retracting.
* @param { _converse.Message } message - The message which we're retracting.
* @param { string } [reason] - The reason for retracting the message.
*/
sendRetractionIQ (message, reason) {
@ -1244,7 +1229,7 @@ const ChatRoomMixin = {
* 'roomconfig' data.
* @private
* @method _converse.ChatRoom#autoConfigureChatRoom
* @returns { Promise<Element> }
* @returns { Promise<XMLElement> }
* Returns a promise which resolves once a response IQ has
* been received.
*/
@ -1263,7 +1248,7 @@ const ChatRoomMixin = {
* has been received.
* @private
* @method _converse.ChatRoom#fetchRoomConfiguration
* @returns { Promise<Element> }
* @returns { Promise<XMLElement> }
*/
fetchRoomConfiguration () {
return api.sendIQ($iq({ 'to': this.get('jid'), 'type': 'get' }).c('query', { xmlns: Strophe.NS.MUC_OWNER }));
@ -1274,7 +1259,7 @@ const ChatRoomMixin = {
* @private
* @method _converse.ChatRoom#sendConfiguration
* @param { Array } config - The groupchat configuration
* @returns { Promise<Element> } - A promise which resolves with
* @returns { Promise<XMLElement> } - A promise which resolves with
* the `result` stanza received from the XMPP server.
*/
sendConfiguration (config = []) {
@ -1716,7 +1701,7 @@ const ChatRoomMixin = {
* Given a presence stanza, update the occupant model based on its contents.
* @private
* @method _converse.ChatRoom#updateOccupantsOnPresence
* @param { Element } pres - The presence stanza
* @param { XMLElement } pres - The presence stanza
*/
updateOccupantsOnPresence (pres) {
const data = parseMUCPresence(pres, this);
@ -1903,7 +1888,7 @@ const ChatRoomMixin = {
* the `from` attribute. Doesn't check the `type` attribute.
* @private
* @method _converse.ChatRoom#isOwnMessage
* @param { Object|Element|_converse.Message } msg
* @param { Object|XMLElement|_converse.Message } msg
* @returns { boolean }
*/
isOwnMessage (msg) {
@ -1943,10 +1928,6 @@ const ChatRoomMixin = {
* @returns {Promise<boolean>}
*/
async isJoined () {
if (!this.isEntered()) {
log.info(`isJoined: not pinging MUC ${this.get('jid')} since we're not entered`);
return false;
}
if (!api.connection.connected()) {
await new Promise(resolve => api.listen.once('reconnected', resolve));
}
@ -1971,14 +1952,10 @@ const ChatRoomMixin = {
/**
* Check whether we're still joined and re-join if not
* @async
* @method _converse.ChatRoom#rejoinIfNecessary
*/
async rejoinIfNecessary () {
if (this.isRAICandidate()) {
log.debug(`rejoinIfNecessary: not rejoining hidden MUC "${this.get('jid')}" since we're using RAI`);
return true;
}
if (!(await this.isJoined())) {
this.rejoin();
return true;
@ -2151,7 +2128,7 @@ const ChatRoomMixin = {
},
/**
* @param { String } actor - The nickname of the actor that caused the notification
* @param {String} actor - The nickname of the actor that caused the notification
* @param {String|Array<String>} states - The state or states representing the type of notificcation
*/
removeNotification (actor, states) {
@ -2175,8 +2152,8 @@ const ChatRoomMixin = {
*
* The state can be a XEP-0085 Chat State or a XEP-0045 join/leave
* state.
* @param { String } actor - The nickname of the actor that causes the notification
* @param { String } state - The state representing the type of notificcation
* @param {String} actor - The nickname of the actor that causes the notification
* @param {String} state - The state representing the type of notificcation
*/
updateNotifications (actor, state) {
const actors_per_state = this.notifications.toJSON();
@ -2224,7 +2201,7 @@ const ChatRoomMixin = {
/**
* Given {@link MessageAttributes} look for XEP-0316 Room Notifications and create info
* messages for them.
* @param { Element } stanza
* @param { XMLElement } stanza
*/
handleMEPNotification (attrs) {
if (attrs.from !== this.get('jid') || !attrs.activities) {
@ -2321,7 +2298,7 @@ const ChatRoomMixin = {
/**
* Handle a presence stanza that disconnects the user from the MUC
* @param { Element } stanza
* @param { XMLElement } stanza
*/
handleDisconnection (stanza) {
const is_self = stanza.querySelector("status[code='110']") !== null;
@ -2343,7 +2320,7 @@ const ChatRoomMixin = {
// each <x/> element pertains to a single user.
const item = x.querySelector('item');
const reason = item ? item.querySelector('reason')?.textContent : undefined;
const actor = item ? item.querySelector('actor')?.getAttribute('nick') : undefined;
const actor = item ? invoke(item.querySelector('actor'), 'getAttribute', 'nick') : undefined;
const message = _converse.muc.disconnect_messages[codes[0]];
const status = codes.includes('301') ? ROOMSTATUS.BANNED : ROOMSTATUS.DISCONNECTED;
this.setDisconnectionState(message, reason, actor, status);
@ -2456,7 +2433,7 @@ const ChatRoomMixin = {
* @private
* @method _converse.ChatRoom#createInfoMessage
* @param { string } code - The MUC status code
* @param { Element } stanza - The original stanza that contains the code
* @param { XMLElement } stanza - The original stanza that contains the code
* @param { Boolean } is_self - Whether this stanza refers to our own presence
*/
createInfoMessage (code, stanza, is_self) {
@ -2478,15 +2455,14 @@ const ChatRoomMixin = {
} else if (is_self && code in _converse.muc.new_nickname_messages) {
// XXX: Side-effect of setting the nick. Should ideally be refactored out of this method
let nick;
if (code === '210') {
if (is_self && code === '210') {
nick = Strophe.getResourceFromJid(stanza.getAttribute('from'));
} else if (code === '303') {
} else if (is_self && code === '303') {
nick = sizzle(`x[xmlns="${Strophe.NS.MUC_USER}"] item`, stanza).pop().getAttribute('nick');
}
this.save('nick', nick);
data.message = __(_converse.muc.new_nickname_messages[code], nick);
}
if (data.message) {
if (code === '201' && this.messages.findWhere(data)) {
return;
@ -2499,7 +2475,7 @@ const ChatRoomMixin = {
* Create info messages based on a received presence or message stanza
* @private
* @method _converse.ChatRoom#createInfoMessages
* @param { Element } stanza
* @param { XMLElement } stanza
*/
createInfoMessages (stanza) {
const codes = sizzle(`x[xmlns="${Strophe.NS.MUC_USER}"] status`, stanza).map(s => s.getAttribute('code'));
@ -2518,7 +2494,7 @@ const ChatRoomMixin = {
* implied by) the server.
* @param { String } reason - The reason provided for the disconnection
* @param { String } actor - The person (if any) responsible for this disconnection
* @param { number } status - The status code (see `ROOMSTATUS`)
* @param { Integer } status - The status code (see `ROOMSTATUS`)
*/
setDisconnectionState (message, reason, actor, status=ROOMSTATUS.DISCONNECTED) {
this.session.save({
@ -2555,7 +2531,7 @@ const ChatRoomMixin = {
* `connection_status` value for this {@link _converse.ChatRoom} as
* well as any additional output that can be shown to the user.
* @private
* @param { Element } stanza - The presence stanza
* @param { XMLElement } stanza - The presence stanza
*/
onErrorPresence (stanza) {
const __ = _converse.__;
@ -2620,7 +2596,7 @@ const ChatRoomMixin = {
* Listens for incoming presence stanzas from the service that hosts this MUC
* @private
* @method _converse.ChatRoom#onPresenceFromMUCHost
* @param { Element } stanza - The presence stanza
* @param { XMLElement } stanza - The presence stanza
*/
onPresenceFromMUCHost (stanza) {
if (stanza.getAttribute('type') === 'error') {
@ -2639,7 +2615,7 @@ const ChatRoomMixin = {
* Handles incoming presence stanzas coming from the MUC
* @private
* @method _converse.ChatRoom#onPresence
* @param { Element } stanza
* @param { XMLElement } stanza
*/
onPresence (stanza) {
if (stanza.getAttribute('type') === 'error') {
@ -2672,7 +2648,7 @@ const ChatRoomMixin = {
* user is the groupchat's owner.
* @private
* @method _converse.ChatRoom#onOwnPresence
* @param { Element } pres - The stanza
* @param { XMLElement } pres - The stanza
*/
async onOwnPresence (stanza) {
await this.occupants.fetched;

View File

@ -10,9 +10,9 @@ class ChatRoomOccupant extends Model {
defaults () { // eslint-disable-line class-methods-use-this
return {
hats: [],
show: 'offline',
states: []
'hats': [],
'show': 'offline',
'states': []
}
}

View File

@ -1,15 +1,14 @@
import ChatRoomOccupant from './occupant.js';
import u from '../../utils/form';
import { Collection } from '@converse/skeletor/src/collection.js';
import { MUC_ROLE_WEIGHTS } from './constants.js';
import { Model } from '@converse/skeletor/src/model.js';
import { Strophe } from 'strophe.js/src/strophe.js';
import { _converse, api, converse } from '../../core.js';
import { _converse, api } from '../../core.js';
import { getAffiliationList } from './affiliations/utils.js';
import { getAutoFetchedAffiliationLists } from './utils.js';
import { getUniqueId } from '@converse/headless/utils/core.js';
const { u } = converse.env;
/**
* A list of {@link _converse.ChatRoomOccupant} instances, representing participants in a MUC.

View File

@ -27,7 +27,7 @@ const { NS } = Strophe;
/**
* Parses a message stanza for XEP-0317 MEP notification data
* @param { Element } stanza - The message stanza
* @param { XMLElement } stanza - The message stanza
* @returns { Array } Returns an array of objects representing <activity> elements.
*/
export function getMEPActivities (stanza) {
@ -61,7 +61,7 @@ export function getMEPActivities (stanza) {
* Note, this function doesn't check whether this is actually a MAM archived stanza.
*
* @private
* @param { Element } stanza - The message stanza
* @param { XMLElement } stanza - The message stanza
* @returns { Object }
*/
function getJIDFromMUCUserData (stanza) {
@ -71,8 +71,8 @@ function getJIDFromMUCUserData (stanza) {
/**
* @private
* @param { Element } stanza - The message stanza
* @param { Element } original_stanza - The original stanza, that contains the
* @param { XMLElement } stanza - The message stanza
* @param { XMLElement } original_stanza - The original stanza, that contains the
* message stanza, if it was contained, otherwise it's the message stanza itself.
* @returns { Object }
*/
@ -140,8 +140,8 @@ function getSender (attrs, chatbox) {
/**
* Parses a passed in message stanza and returns an object of attributes.
* @param { Element } stanza - The message stanza
* @param { Element } original_stanza - The original stanza, that contains the
* @param { XMLElement } stanza - The message stanza
* @param { XMLElement } original_stanza - The original stanza, that contains the
* message stanza, if it was contained, otherwise it's the message stanza itself.
* @param { _converse.ChatRoom } chatbox
* @param { _converse } _converse
@ -342,7 +342,7 @@ export function parseMemberListIQ (iq) {
/**
* Parses a passed in MUC presence stanza and returns an object of attributes.
* @method parseMUCPresence
* @param { Element } stanza - The presence stanza
* @param { XMLElement } stanza - The presence stanza
* @param { _converse.ChatRoom } chatbox
* @returns { MUCPresenceAttributes }
*/

View File

@ -124,19 +124,19 @@ describe("A MUC message", function () {
const muc_jid = 'lounge@montague.lit';
const model = await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const received_stanza = u.toStanza(`
<message to='${_converse.jid}' from='${muc_jid}/mallory' type='groupchat' id='${_converse.connection.getUniqueId()}' >
<reply xmlns='urn:xmpp:reply:0' id='${_converse.connection.getUniqueId()}' to='${_converse.jid}'/>
<fallback xmlns='urn:xmpp:feature-fallback:0' for='urn:xmpp:reply:0'>
<body start='0' end='10'/>
</fallback>
<active xmlns='http://jabber.org/protocol/chatstates'/>
<body>&gt; ping
pong</body>
<request xmlns='urn:xmpp:receipts'/>
</message>
`);
<message to='${_converse.jid}' from='${muc_jid}/mallory' type='groupchat' id='${_converse.connection.getUniqueId()}' >
<reply xmlns='urn:xmpp:reply:0' id='${_converse.connection.getUniqueId()}' to='${_converse.jid}'/>
<fallback xmlns='urn:xmpp:feature-fallback:0' for='urn:xmpp:reply:0'>
<body start='0' end='10'/>
</fallback>
<active xmlns='http://jabber.org/protocol/chatstates'/>
<body>&gt; ping
pong</body>
<request xmlns='urn:xmpp:receipts'/>
</message>
`);
await model.handleMessageStanza(received_stanza);
await u.waitUntil(() => model.messages.last());
expect(model.messages.last().get('body')).toBe('> ping\n pong');
expect(model.messages.last().get('body')).toBe('> ping\npong');
}));
});

View File

@ -83,14 +83,7 @@ describe("A MUC occupant", function () {
await u.waitUntil(() => model.messages.length);
let message = model.messages.at(0);
expect(message.get('occupant_id')).toBe("dd72603deec90a38ba552f7c68cbcc61bca202cd");
expect(message.occupant).not.toBeUndefined();
let occupant = message.occupant;
expect(occupant.getDisplayName()).toBe('3rdwitch');
expect(occupant.get('nick')).toBe('3rdwitch');
expect(occupant.get('jid')).toBe(undefined);
expect(occupant.get('occupant_id')).toBe("dd72603deec90a38ba552f7c68cbcc61bca202cd");
expect(message.occupant).toBeUndefined();
expect(message.getDisplayName()).toBe('3rdwitch');
const presence = u.toStanza(`
@ -105,7 +98,7 @@ describe("A MUC occupant", function () {
</presence>`);
_converse.connection._dataRecv(mock.createRequest(presence));
occupant = await u.waitUntil(() => model.getOccupantByNickname('thirdwitch'));
const occupant = await u.waitUntil(() => model.getOccupantByNickname('thirdwitch'));
expect(occupant.get('occupant_id')).toBe('dd72603deec90a38ba552f7c68cbcc61bca202cd');
expect(model.occupants.findWhere({'occupant_id': "dd72603deec90a38ba552f7c68cbcc61bca202cd"})).toBe(occupant);

View File

@ -85,9 +85,10 @@ export async function openChatRoom (jid, settings) {
* See XEP-0249: Direct MUC invitations.
* @private
* @method _converse.ChatRoom#onDirectMUCInvitation
* @param { Element } message - The message stanza containing the invitation.
* @param { XMLElement } message - The message stanza containing the invitation.
*/
export async function onDirectMUCInvitation (message) {
const { __ } = _converse;
const x_el = sizzle('x[xmlns="jabber:x:conference"]', message).pop(),
from = Strophe.getBareJidFromJid(message.getAttribute('from')),
room_jid = x_el.getAttribute('jid'),
@ -98,20 +99,21 @@ export async function onDirectMUCInvitation (message) {
result = true;
} else {
// Invite request might come from someone not your roster list
const contact = _converse.roster.get(from)?.getDisplayName() ?? from;
/**
* *Hook* which is used to gather confirmation whether a direct MUC
* invitation should be accepted or not.
*
* It's meant for consumers of `@converse/headless` to subscribe to
* this hook and then ask the user to confirm.
*
* @event _converse#confirmDirectMUCInvitation
*/
result = await api.hook('confirmDirectMUCInvitation', { contact, reason, jid: room_jid }, false);
let contact = _converse.roster.get(from);
contact = contact ? contact.getDisplayName() : from;
if (!reason) {
result = await api.confirm(__('%1$s has invited you to join a groupchat: %2$s', contact, room_jid));
} else {
result = await api.confirm(
__(
'%1$s has invited you to join a groupchat: %2$s, and left the following reason: "%3$s"',
contact,
room_jid,
reason
)
);
}
}
if (result) {
const chatroom = await openChatRoom(room_jid, { 'password': x_el.getAttribute('password') });
if (chatroom.session.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED) {

View File

@ -10,7 +10,7 @@ export default {
* @method api.ping
* @param { String } [jid] - The JID of the service to ping
* If the ping is sent out to the user's bare JID and no response is received it will attempt to reconnect.
* @param { number } [timeout] - The amount of time in
* @param { Integer } [timeout] - The amount of time in
* milliseconds to wait for a response. The default is 10000;
* @returns { Boolean | null }
* Whether the pinged entity responded with a non-error IQ stanza.

View File

@ -32,12 +32,12 @@ converse.plugins.add('converse-pubsub', {
* Publshes an item to a PubSub node
*
* @method _converse.api.pubsub.publish
* @param { string } jid The JID of the pubsub service where the node resides.
* @param { string } node The node being published to
* @param {string} jid The JID of the pubsub service where the node resides.
* @param {string} node The node being published to
* @param {Strophe.Builder} item The Strophe.Builder representation of the XML element being published
* @param { object } options An object representing the publisher options
* @param {object} options An object representing the publisher options
* (see https://xmpp.org/extensions/xep-0060.html#publisher-publish-options)
* @param { boolean } strict_options Indicates whether the publisher
* @param {boolean} strict_options Indicates whether the publisher
* options are a strict requirement or not. If they're NOT
* strict, then Converse will publish to the node even if
* the publish options precondication cannot be met.
@ -74,7 +74,7 @@ converse.plugins.add('converse-pubsub', {
// The publish-options precondition couldn't be
// met. We re-publish but without publish-options.
const el = stanza.tree();
const el = stanza.nodeTree;
el.querySelector('publish-options').outerHTML = '';
log.warn(`PubSub: Republishing without publish options. ${el.outerHTML}`);
await api.sendIQ(el);

View File

@ -55,8 +55,8 @@ export default {
* Add a contact.
*
* @method _converse.api.contacts.add
* @param { string } jid The JID of the contact to be added
* @param { string } [name] A custom name to show the user by in the roster
* @param {string} jid The JID of the contact to be added
* @param {string} [name] A custom name to show the user by in the roster
* @example
* _converse.api.contacts.add('buddy@example.com')
* @example

View File

@ -1,4 +1,3 @@
import '@converse/headless/plugins/status/api.js';
import { Model } from '@converse/skeletor/src/model.js';
import { _converse, api, converse } from "@converse/headless/core";
import { getOpenPromise } from '@converse/openpromise';
@ -94,12 +93,21 @@ const RosterContact = Model.extend({
/**
* Send a presence subscription request to this roster contact
* @private
* @method _converse.RosterContacts#subscribe
* @param { String } message - An optional message to explain the
* reason for the subscription request.
*/
subscribe (message) {
api.user.presence.send('subscribe', this.get('jid'), message);
const pres = $pres({to: this.get('jid'), type: "subscribe"});
if (message && message !== "") {
pres.c("status").t(message).up();
}
const nick = _converse.xmppstatus.getNickname() || _converse.xmppstatus.getFullname();
if (nick) {
pres.c('nick', {'xmlns': Strophe.NS.NICK}).t(nick).up();
}
api.send(pres);
this.save('ask', "subscribe"); // ask === 'subscribe' Means we have asked to subscribe to them.
return this;
},
@ -127,6 +135,7 @@ const RosterContact = Model.extend({
* send notification of the subscription state change to the user.
* @private
* @method _converse.RosterContacts#ackUnsubscribe
* @param { String } jid - The Jabber ID of the user who is unsubscribing
*/
ackUnsubscribe () {
api.send($pres({'type': 'unsubscribe', 'to': this.get('jid')}));

View File

@ -112,7 +112,7 @@ const RosterContacts = Collection.extend({
* @method _converse.RosterContacts#addAndSubscribe
* @param { String } jid - The Jabber ID of the user being added and subscribed to.
* @param { String } name - The name of that user
* @param { Array<String> } groups - Any roster groups the user might belong to
* @param { Array.String } groups - Any roster groups the user might belong to
* @param { String } message - An optional message to explain the reason for the subscription request.
* @param { Object } attributes - Any additional attributes to be stored on the user's model.
*/
@ -128,7 +128,9 @@ const RosterContacts = Collection.extend({
* @method _converse.RosterContacts#sendContactAddIQ
* @param { String } jid - The Jabber ID of the user being added
* @param { String } name - The name of that user
* @param { Array<String> } groups - Any roster groups the user might belong to
* @param { Array.String } groups - Any roster groups the user might belong to
* @param { Function } callback - A function to call once the IQ is returned
* @param { Function } errback - A function to call if an error occurred
*/
sendContactAddIQ (jid, name, groups) {
name = name ? name : null;
@ -146,7 +148,7 @@ const RosterContacts = Collection.extend({
* @method _converse.RosterContacts#addContactToRoster
* @param { String } jid - The Jabber ID of the user being added and subscribed to.
* @param { String } name - The name of that user
* @param { Array<String> } groups - Any roster groups the user might belong to
* @param { Array.String } groups - Any roster groups the user might belong to
* @param { Object } attributes - Any additional attributes to be stored on the user's model.
*/
async addContactToRoster (jid, name, groups, attributes) {
@ -188,7 +190,7 @@ const RosterContacts = Collection.extend({
* Handle roster updates from the XMPP server.
* See: https://xmpp.org/rfcs/rfc6121.html#roster-syntax-actions-push
* @method _converse.RosterContacts#onRosterPush
* @param { Element } iq - The IQ stanza received from the XMPP server.
* @param { XMLElement } IQ - The IQ stanza received from the XMPP server.
*/
onRosterPush (iq) {
const id = iq.getAttribute('id');
@ -224,7 +226,7 @@ const RosterContacts = Collection.extend({
/**
* When the roster receives a push event from server (i.e. new entry in your contacts roster).
* @event _converse#rosterPush
* @type { Element }
* @type { XMLElement }
* @example _converse.api.listen.on('rosterPush', iq => { ... });
*/
api.trigger('rosterPush', iq);
@ -277,7 +279,7 @@ const RosterContacts = Collection.extend({
* See also the `cachedRoster` event further up, which gets called instead of
* `roster` if its already in `sessionStorage`.
* @event _converse#roster
* @type { Element }
* @type { XMLElement }
* @example _converse.api.listen.on('roster', iq => { ... });
* @example _converse.api.waitUntil('roster').then(iq => { ... });
*/
@ -287,7 +289,7 @@ const RosterContacts = Collection.extend({
/**
* Update or create RosterContact models based on the given `item` XML
* node received in the resulting IQ stanza from the server.
* @param { Element } item
* @param { XMLElement } item
*/
updateContact (item) {
const jid = item.getAttribute('jid');
@ -377,7 +379,9 @@ const RosterContacts = Collection.extend({
_converse.xmppstatus.save({'status': show}, {'silent': true});
const status_message = presence.querySelector('status')?.textContent;
if (status_message) _converse.xmppstatus.save({ status_message });
if (status_message) {
_converse.xmppstatus.save({'status_message': status_message});
}
}
if (_converse.jid === jid && presence_type === 'unavailable') {
// XXX: We've received an "unavailable" presence from our
@ -410,11 +414,11 @@ const RosterContacts = Collection.extend({
return; // Ignore MUC
}
const status_message = presence.querySelector('status')?.textContent;
const contact = this.get(bare_jid);
if (contact) {
const status = presence.querySelector('status')?.textContent;
if (contact.get('status') !== status) contact.save({status});
if (contact && (status_message !== contact.get('status'))) {
contact.save({'status': status_message});
}
if (presence_type === 'subscribed' && contact) {

View File

@ -30,7 +30,7 @@ export const Presence = Model.extend({
const hpr = this.getHighestPriorityResource();
const show = hpr?.attributes?.show || 'offline';
if (this.get('show') !== show) {
this.save({ show });
this.save({'show': show});
}
},
@ -48,20 +48,20 @@ export const Presence = Model.extend({
* from the passed in presence stanza.
* Also updates the presence if the resource has higher priority (and is newer).
* @private
* @param { Element } presence: The presence stanza
* @param { XMLElement } presence: The presence stanza
*/
addResource (presence) {
const jid = presence.getAttribute('from');
const name = Strophe.getResourceFromJid(jid);
const delay = sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, presence).pop();
const priority = presence.querySelector('priority')?.textContent;
const resource = this.resources.get(name);
const settings = {
name,
'priority': isNaN(parseInt(priority, 10)) ? 0 : parseInt(priority, 10),
'show': presence.querySelector('show')?.textContent ?? 'online',
'timestamp': delay ? dayjs(delay.getAttribute('stamp')).toISOString() : (new Date()).toISOString()
};
const jid = presence.getAttribute('from'),
name = Strophe.getResourceFromJid(jid),
delay = sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, presence).pop(),
priority = presence.querySelector('priority')?.textContent ?? 0,
resource = this.resources.get(name),
settings = {
'name': name,
'priority': isNaN(parseInt(priority, 10)) ? 0 : parseInt(priority, 10),
'show': presence.querySelector('show')?.textContent ?? 'online',
'timestamp': delay ? dayjs(delay.getAttribute('stamp')).toISOString() : (new Date()).toISOString()
};
if (resource) {
resource.save(settings);
} else {
@ -78,7 +78,9 @@ export const Presence = Model.extend({
*/
removeResource (name) {
const resource = this.resources.get(name);
resource?.destroy();
if (resource) {
resource.destroy();
}
}
});

View File

@ -1,10 +1,8 @@
import log from "@converse/headless/log";
import { Model } from '@converse/skeletor/src/model.js';
import { RosterFilter } from '@converse/headless/plugins/roster/filter.js';
import { STATUS_WEIGHTS } from "../../shared/constants";
import { _converse, api, converse } from "@converse/headless/core";
import { initStorage } from '@converse/headless/utils/storage.js';
import { shouldClearCache } from '@converse/headless/utils/core.js';
const { $pres } = converse.env;
@ -90,7 +88,7 @@ async function clearPresences () {
*/
export async function onClearSession () {
await clearPresences();
if (shouldClearCache()) {
if (_converse.shouldClearCache()) {
if (_converse.rostergroups) {
await _converse.rostergroups.clearStore();
delete _converse.rostergroups;
@ -199,12 +197,12 @@ export function rejectPresenceSubscription (jid, message) {
export function contactsComparator (contact1, contact2) {
const status1 = contact1.presence.get('show') || 'offline';
const status2 = contact2.presence.get('show') || 'offline';
if (STATUS_WEIGHTS[status1] === STATUS_WEIGHTS[status2]) {
if (_converse.STATUS_WEIGHTS[status1] === _converse.STATUS_WEIGHTS[status2]) {
const name1 = (contact1.getDisplayName()).toLowerCase();
const name2 = (contact2.getDisplayName()).toLowerCase();
return name1 < name2 ? -1 : (name1 > name2? 1 : 0);
} else {
return STATUS_WEIGHTS[status1] < STATUS_WEIGHTS[status2] ? -1 : 1;
return _converse.STATUS_WEIGHTS[status1] < _converse.STATUS_WEIGHTS[status2] ? -1 : 1;
}
}

View File

@ -49,13 +49,14 @@ describe("XEP-0198 Stream Management", function () {
`<iq id="${IQ_stanzas[1].getAttribute('id')}" type="get" xmlns="jabber:client"><query xmlns="jabber:iq:roster"/></iq>`);
await mock.waitForRoster(_converse, 'current', 1);
expect(Strophe.serialize(IQ_stanzas[2])).toBe(
`<iq from="romeo@montague.lit/orchard" id="${IQ_stanzas[2].getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/disco#info"/></iq>`);
const omemo_iq = IQ_stanzas[2];
expect(Strophe.serialize(omemo_iq)).toBe(
`<iq from="romeo@montague.lit" id="${omemo_iq.getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
`<pubsub xmlns="http://jabber.org/protocol/pubsub"><items node="eu.siacs.conversations.axolotl.devicelist"/></pubsub></iq>`);
expect(Strophe.serialize(IQ_stanzas[3])).toBe(
`<iq from="romeo@montague.lit" id="${IQ_stanzas[3].getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
`<pubsub xmlns="http://jabber.org/protocol/pubsub"><items node="eu.siacs.conversations.axolotl.devicelist"/></pubsub></iq>`);
`<iq from="romeo@montague.lit/orchard" id="${IQ_stanzas[3].getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/disco#info"/></iq>`);
expect(Strophe.serialize(IQ_stanzas[4])).toBe(
`<iq from="romeo@montague.lit/orchard" id="${IQ_stanzas[4].getAttribute('id')}" type="set" xmlns="jabber:client"><enable xmlns="urn:xmpp:carbons:2"/></iq>`);
@ -136,13 +137,13 @@ describe("XEP-0198 Stream Management", function () {
iq = IQ_stanzas.pop();
expect(Strophe.serialize(iq)).toBe(
`<iq from="romeo@montague.lit" id="${iq.getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
`<pubsub xmlns="http://jabber.org/protocol/pubsub"><items node="eu.siacs.conversations.axolotl.devicelist"/></pubsub></iq>`);
`<iq from="romeo@montague.lit/orchard" id="${iq.getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/disco#info"/></iq>`);
iq = IQ_stanzas.pop();
expect(Strophe.serialize(iq)).toBe(
`<iq from="romeo@montague.lit/orchard" id="${iq.getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/disco#info"/></iq>`);
`<iq from="romeo@montague.lit" id="${iq.getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
`<pubsub xmlns="http://jabber.org/protocol/pubsub"><items node="eu.siacs.conversations.axolotl.devicelist"/></pubsub></iq>`);
expect(IQ_stanzas.filter(iq => sizzle('query[xmlns="jabber:iq:roster"]', iq).pop()).length).toBe(0);
}));

View File

@ -1,8 +1,38 @@
import { _converse, api } from '../../core';
import { STATUS_WEIGHTS } from '../../shared/constants';
import { _converse, api } from '@converse/headless/core';
export default {
/**
* @namespace _converse.api.user.presence
* @memberOf _converse.api.user
*/
presence: {
/**
* Send out a presence stanza
* @method _converse.api.user.presence.send
* @param { String } type
* @param { String } to
* @param { String } [status] - An optional status message
* @param { Element[]|Strophe.Builder[]|Element|Strophe.Builder } [child_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];
}
const model = _converse.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);
if (['away', 'chat', 'dnd', 'online', 'xa', undefined].includes(type)) {
const mucs = await api.rooms.get();
mucs.forEach(muc => muc.sendStatusPresence(type, status, child_nodes));
}
}
},
/**
* Set and get the user's chat status, also called their *availability*.
* @namespace _converse.api.user.status
@ -25,15 +55,15 @@ export default {
*
* @async
* @method _converse.api.user.status.set
* @param { string } value The user's chat status (e.g. 'away', 'dnd', 'offline', 'online', 'unavailable' or 'xa')
* @param { string } [message] A custom status message
* @param {string} value The user's chat status (e.g. 'away', 'dnd', 'offline', 'online', 'unavailable' or 'xa')
* @param {string} [message] A custom status message
*
* @example _converse.api.user.status.set('dnd');
* @example _converse.api.user.status.set('dnd', 'In a meeting');
*/
async set (value, message) {
const data = {'status': value};
if (!Object.keys(STATUS_WEIGHTS).includes(value)) {
if (!Object.keys(_converse.STATUS_WEIGHTS).includes(value)) {
throw new Error(
'Invalid availability value. See https://xmpp.org/rfcs/rfc3921.html#rfc.section.2.2.2.1'
);
@ -55,7 +85,7 @@ export default {
/**
* @async
* @method _converse.api.user.status.message.get
* @returns { Promise<string> } The status message
* @returns {string} The status message
* @example const message = _converse.api.user.status.message.get()
*/
async get () {
@ -65,7 +95,7 @@ export default {
/**
* @async
* @method _converse.api.user.status.message.set
* @param { string } status The status message
* @param {string} status The status message
* @example _converse.api.user.status.message.set('In a meeting');
*/
async set (status) {

View File

@ -5,7 +5,6 @@
import XMPPStatus from './status.js';
import status_api from './api.js';
import { _converse, api, converse } from '@converse/headless/core';
import { shouldClearCache } from '@converse/headless/utils/core.js';
import {
addStatusToMUCJoinPresence,
initStatus,
@ -53,7 +52,7 @@ converse.plugins.add('converse-status', {
});
api.listen.on('clearSession', () => {
if (shouldClearCache() && _converse.xmppstatus) {
if (_converse.shouldClearCache() && _converse.xmppstatus) {
_converse.xmppstatus.destroy();
delete _converse.xmppstatus;
api.promises.add(['statusInitialized']);

View File

@ -5,11 +5,10 @@ import { _converse, api, converse } from '@converse/headless/core';
const { Strophe, $pres } = converse.env;
export default class XMPPStatus extends Model {
defaults () { // eslint-disable-line class-methods-use-this
const XMPPStatus = Model.extend({
defaults () {
return { "status": api.settings.get("default_state") }
}
},
initialize () {
this.on('change', item => {
@ -20,72 +19,54 @@ export default class XMPPStatus extends Model {
api.user.presence.send(this.get('status'), null, this.get('status_message'));
}
});
}
},
getDisplayName () {
return this.getFullname() || this.getNickname() || _converse.bare_jid;
}
getNickname () { // eslint-disable-line class-methods-use-this
getNickname () {
return api.settings.get('nickname');
}
},
getFullname () { // eslint-disable-line class-methods-use-this
return ''; // Gets overridden in converse-vcard
}
getFullname () {
// Gets overridden in converse-vcard
return '';
},
/** Constructs a presence stanza
* @param { string } [type]
* @param { string } [to] - The JID to which this presence should be sent
* @param { string } [status_message]
*/
async constructPresence (type, to=null, status_message) {
type = typeof type === 'string' ? type : (this.get('status') || api.settings.get("default_state"));
status_message = typeof status_message === 'string' ? status_message : this.get('status_message');
let presence;
if (type === 'subscribe') {
presence = $pres({ to, type });
const { xmppstatus } = _converse;
const nick = xmppstatus.getNickname();
if (nick) presence.c('nick', {'xmlns': Strophe.NS.NICK}).t(nick).up();
} else if ((type === 'unavailable') ||
const attrs = {to};
if ((type === 'unavailable') ||
(type === 'probe') ||
(type === 'error') ||
(type === 'unsubscribe') ||
(type === 'unsubscribed') ||
(type === 'subscribe') ||
(type === 'subscribed')) {
presence = $pres({ to, type });
attrs['type'] = type;
presence = $pres(attrs);
} else if (type === 'offline') {
presence = $pres({ to, type: 'unavailable' });
attrs['type'] = 'unavailable';
presence = $pres(attrs);
} else if (type === 'online') {
presence = $pres({ to });
presence = $pres(attrs);
} else {
presence = $pres({ to }).c('show').t(type).up();
presence = $pres(attrs).c('show').t(type).up();
}
if (status_message) presence.c('status').t(status_message).up();
if (status_message) {
presence.c('status').t(status_message).up();
}
const priority = api.settings.get("priority");
presence.c('priority').t(isNaN(Number(priority)) ? 0 : priority).up();
const { idle, idle_seconds } = _converse;
if (idle) {
if (_converse.idle) {
const idle_since = new Date();
idle_since.setSeconds(idle_since.getSeconds() - idle_seconds);
presence.c('idle', { xmlns: Strophe.NS.IDLE, since: idle_since.toISOString() });
idle_since.setSeconds(idle_since.getSeconds() - _converse.idle_seconds);
presence.c('idle', {xmlns: Strophe.NS.IDLE, since: idle_since.toISOString()});
}
/**
* *Hook* which allows plugins to modify a presence stanza
* @event _converse#constructedPresence
*/
presence = await api.hook('constructedPresence', null, presence);
return presence;
}
}
});
export default XMPPStatus;

View File

@ -22,8 +22,8 @@ export default {
* for the passed in JID.
*
* @method _converse.api.vcard.set
* @param { string } jid The JID for which the VCard should be set
* @param { object } data A map of VCard keys and values
* @param {string} jid The JID for which the VCard should be set
* @param {object} data A map of VCard keys and values
* @example
* let jid = _converse.bare_jid;
* _converse.api.vcard.set( jid, {
@ -67,7 +67,7 @@ export default {
* @param {Model|string} model Either a `Model` instance, or a string JID.
* If a `Model` instance is passed in, then it must have either a `jid`
* attribute or a `muc_jid` attribute.
* @param { boolean } [force] A boolean indicating whether the vcard should be
* @param {boolean} [force] A boolean indicating whether the vcard should be
* fetched from the server even if it's been fetched before.
* @returns {promise} A Promise which resolves with the VCard data for a particular JID or for
* a `Model` instance which represents an entity with a JID (such as a roster contact,
@ -106,8 +106,8 @@ export default {
* returned VCard data.
*
* @method _converse.api.vcard.update
* @param { Model } model A `Model` instance
* @param { boolean } [force] A boolean indicating whether the vcard should be
* @param {Model} model A `Model` instance
* @param {boolean} [force] A boolean indicating whether the vcard should be
* fetched again even if it's been fetched before.
* @returns {promise} A promise which resolves once the update has completed.
* @example

View File

@ -1,7 +1,6 @@
import log from "@converse/headless/log";
import { _converse, api, converse } from "../../core.js";
import { initStorage } from '@converse/headless/utils/storage.js';
import { shouldClearCache } from '@converse/headless/utils/core.js';
const { Strophe, $iq, u } = converse.env;
@ -164,7 +163,7 @@ export async function initVCardCollection () {
export function clearVCardsSession () {
if (shouldClearCache()) {
if (_converse.shouldClearCache()) {
api.promises.add('VCardsInitialized');
if (_converse.vcards) {
_converse.vcards.clearStore();

View File

@ -1,36 +1,11 @@
import i18n from './i18n.js';
import log from '../log.js';
import pluggable from 'pluggable.js/src/pluggable.js';
import { Events } from '@converse/skeletor/src/events.js';
import i18n from '@converse/headless/shared/i18n';
import log from '@converse/headless/log';
import { CONNECTION_STATUS } from '@converse/headless/shared/constants';
import { Router } from '@converse/skeletor/src/router.js';
import { createStore, getDefaultStore } from '../utils/storage.js';
import { getInitSettings } from './settings/utils.js';
import { TimeoutError } from '@converse/headless/shared/errors';
import { createStore, getDefaultStore } from '@converse/headless/utils/storage.js';
import { getInitSettings } from '@converse/headless/shared/settings/utils.js';
import { getOpenPromise } from '@converse/openpromise';
import { shouldClearCache } from '../utils/core.js';
import {
ACTIVE,
ANONYMOUS,
CHATROOMS_TYPE,
CLOSED,
COMPOSING,
CONTROLBOX_TYPE,
DEFAULT_IMAGE,
DEFAULT_IMAGE_TYPE,
EXTERNAL,
FAILURE,
GONE,
HEADLINES_TYPE,
INACTIVE,
LOGIN,
LOGOUT,
OPENED,
PAUSED,
PREBIND,
PRIVATE_CHAT_TYPE,
SUCCESS,
VERSION_NAME
} from './constants';
/**
@ -41,51 +16,67 @@ import {
*/
const _converse = {
log,
shouldClearCache, // TODO: Should be moved to utils with next major release
VERSION_NAME,
CONNECTION_STATUS,
templates: {},
promises: {
'initialized': getOpenPromise()
},
// TODO: remove constants in next major release
ANONYMOUS,
CLOSED,
EXTERNAL,
LOGIN,
LOGOUT,
OPENED,
PREBIND,
STATUS_WEIGHTS: {
'offline': 6,
'unavailable': 5,
'xa': 4,
'away': 3,
'dnd': 2,
'chat': 1, // We currently don't differentiate between "chat" and "online"
'online': 1
},
ANONYMOUS: 'anonymous',
CLOSED: 'closed',
EXTERNAL: 'external',
LOGIN: 'login',
LOGOUT: 'logout',
OPENED: 'opened',
PREBIND: 'prebind',
SUCCESS,
FAILURE,
/**
* @constant
* @type { integer }
*/
STANZA_TIMEOUT: 20000,
DEFAULT_IMAGE_TYPE,
DEFAULT_IMAGE,
SUCCESS: 'success',
FAILURE: 'failure',
INACTIVE,
ACTIVE,
COMPOSING,
PAUSED,
GONE,
// Generated from css/images/user.svg
DEFAULT_IMAGE_TYPE: 'image/svg+xml',
DEFAULT_IMAGE: "PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMTI4IiBoZWlnaHQ9IjEyOCI+CiA8cmVjdCB3aWR0aD0iMTI4IiBoZWlnaHQ9IjEyOCIgZmlsbD0iIzU1NSIvPgogPGNpcmNsZSBjeD0iNjQiIGN5PSI0MSIgcj0iMjQiIGZpbGw9IiNmZmYiLz4KIDxwYXRoIGQ9Im0yOC41IDExMiB2LTEyIGMwLTEyIDEwLTI0IDI0LTI0IGgyMyBjMTQgMCAyNCAxMiAyNCAyNCB2MTIiIGZpbGw9IiNmZmYiLz4KPC9zdmc+Cg==",
PRIVATE_CHAT_TYPE,
CHATROOMS_TYPE,
HEADLINES_TYPE,
CONTROLBOX_TYPE,
// Set as module attr so that we can override in tests.
// TODO: replace with config settings
TIMEOUTS: {
// Set as module attr so that we can override in tests.
PAUSED: 10000,
INACTIVE: 90000
},
// XEP-0085 Chat states
// https://xmpp.org/extensions/xep-0085.html
INACTIVE: 'inactive',
ACTIVE: 'active',
COMPOSING: 'composing',
PAUSED: 'paused',
GONE: 'gone',
// Chat types
PRIVATE_CHAT_TYPE: 'chatbox',
CHATROOMS_TYPE: 'chatroom',
HEADLINES_TYPE: 'headline',
CONTROLBOX_TYPE: 'controlbox',
default_connection_options: {'explicitResourceBinding': true},
router: new Router(),
TimeoutError: TimeoutError,
isTestEnv: () => {
return getInitSettings()['bosh_service_url'] === 'montague.lit/http-bind';
},
@ -121,10 +112,4 @@ const _converse = {
'___': str => str
}
// Make _converse an event emitter
Object.assign(_converse, Events);
// Make _converse pluggable
pluggable.enable(_converse, '_converse', 'pluggable');
export default _converse;

View File

@ -1,132 +0,0 @@
import _converse from '../_converse.js';
import isFunction from '../../utils/core.js';
export default {
/**
* Lets you trigger events, which can be listened to via
* {@link _converse.api.listen.on} or {@link _converse.api.listen.once}
* (see [_converse.api.listen](http://localhost:8000/docs/html/api/-_converse.api.listen.html)).
*
* Some events also double as promises and can be waited on via {@link _converse.api.waitUntil}.
*
* @method _converse.api.trigger
* @param { string } name - The event name
* @param {...any} [argument] - Argument to be passed to the event handler
* @param { object } [options]
* @param { boolean } [options.synchronous] - Whether the event is synchronous or not.
* When a synchronous event is fired, a promise will be returned
* by {@link _converse.api.trigger} which resolves once all the
* event handlers' promises have been resolved.
*/
async trigger (name) {
if (!_converse._events) {
return;
}
const args = Array.from(arguments);
const options = args.pop();
if (options && options.synchronous) {
const events = _converse._events[name] || [];
const event_args = args.splice(1);
await Promise.all(events.map(e => e.callback.apply(e.ctx, event_args)));
} else {
_converse.trigger.apply(_converse, arguments);
}
const promise = _converse.promises[name];
if (promise !== undefined) {
promise.resolve();
}
},
/**
* Triggers a hook which can be intercepted by registered listeners via
* {@link _converse.api.listen.on} or {@link _converse.api.listen.once}.
* (see [_converse.api.listen](http://localhost:8000/docs/html/api/-_converse.api.listen.html)).
* A hook is a special kind of event which allows you to intercept a data
* structure in order to modify it, before passing it back.
* @async
* @param { string } name - The hook name
* @param {...any} context - The context to which the hook applies (could be for example, a {@link _converse.ChatBox})).
* @param {...any} data - The data structure to be intercepted and modified by the hook listeners.
* @returns {Promise<any>} - A promise that resolves with the modified data structure.
*/
hook (name, context, data) {
const events = _converse._events[name] || [];
if (events.length) {
// Create a chain of promises, with each one feeding its output to
// the next. The first input is a promise with the original data
// sent to this hook.
return events.reduce((o, e) => o.then(d => e.callback(context, d)), Promise.resolve(data));
} else {
return data;
}
},
/**
* Converse emits events to which you can subscribe to.
*
* The `listen` namespace exposes methods for creating event listeners
* (aka handlers) for these events.
*
* @namespace _converse.api.listen
* @memberOf _converse
*/
listen: {
/**
* Lets you listen to an event exactly once.
* @method _converse.api.listen.once
* @param { string } name The event's name
* @param { function } callback The callback method to be called when the event is emitted.
* @param { object } [context] The value of the `this` parameter for the callback.
* @example _converse.api.listen.once('message', function (messageXML) { ... });
*/
once: _converse.once.bind(_converse),
/**
* Lets you subscribe to an event.
* Every time the event fires, the callback method specified by `callback` will be called.
* @method _converse.api.listen.on
* @param { string } name The event's name
* @param { function } callback The callback method to be called when the event is emitted.
* @param { object } [context] The value of the `this` parameter for the callback.
* @example _converse.api.listen.on('message', function (messageXML) { ... });
*/
on: _converse.on.bind(_converse),
/**
* To stop listening to an event, you can use the `not` method.
* @method _converse.api.listen.not
* @param { string } name The event's name
* @param { function } callback The callback method that is to no longer be called when the event fires
* @example _converse.api.listen.not('message', function (messageXML);
*/
not: _converse.off.bind(_converse),
/**
* Subscribe to an incoming stanza
* Every a matched stanza is received, the callback method specified by
* `callback` will be called.
* @method _converse.api.listen.stanza
* @param { string } name The stanza's name
* @param { object } options Matching options (e.g. 'ns' for namespace, 'type' for stanza type, also 'id' and 'from');
* @param { function } handler The callback method to be called when the stanza appears
*/
stanza (name, options, handler) {
if (isFunction(options)) {
handler = options;
options = {};
} else {
options = options || {};
}
_converse.connection.addHandler(
handler,
options.ns,
name,
options.type,
options.id,
options.from,
options
);
}
},
}

View File

@ -1,31 +0,0 @@
import _converse from '../_converse.js';
import connection_api from '../connection/api.js';
import events_api from '../api/events.js';
import promise_api from '../api/promise.js';
import send_api from '../api/send.js';
import user_api from '../api/user.js';
import { settings_api } from '../settings/api.js';
/**
* ### The private API
*
* The private API methods are only accessible via the closured {@link _converse}
* object, which is only available to plugins.
*
* These methods are kept private (i.e. not global) because they may return
* sensitive data which should be kept off-limits to other 3rd-party scripts
* that might be running in the page.
*
* @namespace _converse.api
* @memberOf _converse
*/
const api = _converse.api = {
connection: connection_api,
settings: settings_api,
...send_api,
...user_api,
...events_api,
...promise_api,
};
export default api;

View File

@ -1,35 +0,0 @@
import { _converse, api } from '../../core.js';
export default {
/**
* @namespace _converse.api.user.presence
* @memberOf _converse.api.user
*/
presence: {
/**
* Send out a presence stanza
* @method _converse.api.user.presence.send
* @param { String } [type]
* @param { String } [to]
* @param { String } [status] - An optional status message
* @param { Array<Element>|Array<Strophe.Builder>|Element|Strophe.Builder } [child_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];
}
const model = _converse.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);
if (['away', 'chat', 'dnd', 'online', 'xa', undefined].includes(type)) {
const mucs = await api.rooms.get();
mucs.forEach(muc => muc.sendStatusPresence(type, status, child_nodes));
}
}
}
}

View File

@ -1,78 +0,0 @@
import _converse from '@converse/headless/shared/_converse.js';
import { getOpenPromise } from '@converse/openpromise';
import { waitUntil, isFunction } from '../../utils/core.js';
export default {
/**
* Converse and its plugins trigger various events which you can listen to via the
* {@link _converse.api.listen} namespace.
*
* Some of these events are also available as [ES2015 Promises](http://es6-features.org/#PromiseUsage)
* although not all of them could logically act as promises, since some events
* might be fired multpile times whereas promises are to be resolved (or
* rejected) only once.
*
* Events which are also promises include:
*
* * [cachedRoster](/docs/html/events.html#cachedroster)
* * [chatBoxesFetched](/docs/html/events.html#chatBoxesFetched)
* * [pluginsInitialized](/docs/html/events.html#pluginsInitialized)
* * [roster](/docs/html/events.html#roster)
* * [rosterContactsFetched](/docs/html/events.html#rosterContactsFetched)
* * [rosterGroupsFetched](/docs/html/events.html#rosterGroupsFetched)
* * [rosterInitialized](/docs/html/events.html#rosterInitialized)
*
* The various plugins might also provide promises, and they do this by using the
* `promises.add` api method.
*
* @namespace _converse.api.promises
* @memberOf _converse.api
*/
promises: {
/**
* By calling `promises.add`, a new [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
* is made available for other code or plugins to depend on via the
* {@link _converse.api.waitUntil} method.
*
* Generally, it's the responsibility of the plugin which adds the promise to
* also resolve it.
*
* This is done by calling {@link _converse.api.trigger}, which not only resolves the
* promise, but also emits an event with the same name (which can be listened to
* via {@link _converse.api.listen}).
*
* @method _converse.api.promises.add
* @param {string|array} [name|names] The name or an array of names for the promise(s) to be added
* @param { boolean } [replace=true] Whether this promise should be replaced with a new one when the user logs out.
* @example _converse.api.promises.add('foo-completed');
*/
add (promises, replace=true) {
promises = Array.isArray(promises) ? promises : [promises];
promises.forEach(name => {
const promise = getOpenPromise();
promise.replace = replace;
_converse.promises[name] = promise;
});
}
},
/**
* Wait until a promise is resolved or until the passed in function returns
* a truthy value.
* @method _converse.api.waitUntil
* @param {string|function} condition - The name of the promise to wait for,
* or a function which should eventually return a truthy value.
* @returns {Promise}
*/
waitUntil (condition) {
if (isFunction(condition)) {
return waitUntil(condition);
} else {
const promise = _converse.promises[condition];
if (promise === undefined) {
return null;
}
return promise;
}
},
}

View File

@ -1,212 +0,0 @@
import ConnectionFeedback from './../connection/feedback.js';
import URI from 'urijs';
import _converse from '../_converse.js';
import dayjs from 'dayjs';
import i18n from '../i18n';
import log from '../../log.js';
import sizzle from 'sizzle';
import u, { setUnloadEvent } from '../../utils/core.js';
import { ANONYMOUS, CHAT_STATES, KEYCODES, VERSION_NAME } from '../constants.js';
import { Collection } from "@converse/skeletor/src/collection";
import { Model } from '@converse/skeletor/src/model.js';
import { Strophe, $build, $iq, $msg, $pres } from 'strophe.js/src/strophe';
import { TimeoutError } from '../errors.js';
import { filesize } from 'filesize';
import { html } from 'lit';
import { initAppSettings } from '../settings/utils.js';
import { sprintf } from 'sprintf-js';
import { stx } from '../../utils/stanza.js';
import {
cleanup,
initClientConfig,
initPlugins,
initSessionStorage,
registerGlobalEventHandlers,
} from '../../utils/init.js';
/**
* ### The Public API
*
* This namespace contains public API methods which are are
* accessible on the global `converse` object.
* They are public, because any JavaScript in the
* page can call them. Public methods therefore dont expose any sensitive
* or closured data. To do that, youll need to create a plugin, which has
* access to the private API method.
*
* @global
* @namespace converse
*/
export const converse = Object.assign(window.converse || {}, {
CHAT_STATES,
keycodes: KEYCODES,
/**
* Public API method which initializes Converse.
* This method must always be called when using Converse.
* @async
* @memberOf converse
* @method initialize
* @param { object } config A map of [configuration-settings](https://conversejs.org/docs/html/configuration.html#configuration-settings).
* @example
* converse.initialize({
* auto_list_rooms: false,
* auto_subscribe: false,
* bosh_service_url: 'https://bind.example.com',
* hide_muc_server: false,
* i18n: 'en',
* play_sounds: true,
* show_controlbox_by_default: true,
* debug: false,
* roster_groups: true
* });
*/
async initialize (settings) {
const { api } = _converse;
await cleanup(_converse);
setUnloadEvent();
initAppSettings(settings);
_converse.strict_plugin_dependencies = settings.strict_plugin_dependencies; // Needed by pluggable.js
log.setLogLevel(api.settings.get("loglevel"));
if (api.settings.get("authentication") === ANONYMOUS) {
if (api.settings.get("auto_login") && !api.settings.get('jid')) {
throw new Error("Config Error: you need to provide the server's " +
"domain via the 'jid' option when using anonymous " +
"authentication with auto_login.");
}
}
_converse.router.route(
/^converse\?loglevel=(debug|info|warn|error|fatal)$/, 'loglevel',
l => log.setLogLevel(l)
);
_converse.connfeedback = new ConnectionFeedback();
/* When reloading the page:
* For new sessions, we need to send out a presence stanza to notify
* the server/network that we're online.
* When re-attaching to an existing session we don't need to again send out a presence stanza,
* because it's as if "we never left" (see onConnectStatusChanged).
* https://github.com/conversejs/converse.js/issues/521
*/
_converse.send_initial_presence = true;
await initSessionStorage(_converse);
await initClientConfig(_converse);
await i18n.initialize();
initPlugins(_converse);
// Register all custom elements
// XXX: api.elements is defined in the UI part of Converse, outside of @converse/headless.
// This line should probably be moved to the UI code as part of a larger refactoring.
api.elements?.register();
registerGlobalEventHandlers(_converse);
try {
!History.started && _converse.router.history.start();
} catch (e) {
log.error(e);
}
const plugins = _converse.pluggable.plugins
if (api.settings.get("auto_login") || api.settings.get("keepalive") && plugins['converse-bosh']?.enabled()) {
await api.user.login(null, null, true);
}
/**
* Triggered once converse.initialize has finished.
* @event _converse#initialized
*/
api.trigger('initialized');
if (_converse.isTestEnv()) {
return _converse;
}
},
/**
* Exposes methods for adding and removing plugins. You'll need to write a plugin
* if you want to have access to the private API methods defined further down below.
*
* For more information on plugins, read the documentation on [writing a plugin](/docs/html/plugin_development.html).
* @namespace plugins
* @memberOf converse
*/
plugins: {
/**
* Registers a new plugin.
* @method converse.plugins.add
* @param { string } name The name of the plugin
* @param { object } plugin The plugin object
* @example
* const plugin = {
* initialize: function () {
* // Gets called as soon as the plugin has been loaded.
*
* // Inside this method, you have access to the private
* // API via `_covnerse.api`.
*
* // The private _converse object contains the core logic
* // and data-structures of Converse.
* }
* }
* converse.plugins.add('myplugin', plugin);
*/
add (name, plugin) {
plugin.__name__ = name;
if (_converse.pluggable.plugins[name] !== undefined) {
throw new TypeError(
`Error: plugin with name "${name}" has already been ` + 'registered!'
);
} else {
_converse.pluggable.plugins[name] = plugin;
}
}
},
/**
* Utility methods and globals from bundled 3rd party libraries.
* @typedef ConverseEnv
* @property { Error } converse.env.TimeoutError
* @property { function } converse.env.$build - Creates a Strophe.Builder, for creating stanza objects.
* @property { function } converse.env.$iq - Creates a Strophe.Builder with an <iq/> element as the root.
* @property { function } converse.env.$msg - Creates a Strophe.Builder with an <message/> element as the root.
* @property { function } converse.env.$pres - Creates a Strophe.Builder with an <presence/> element as the root.
* @property { function } converse.env.Promise - The Promise implementation used by Converse.
* @property { function } converse.env.Strophe - The [Strophe](http://strophe.im/strophejs) XMPP library used by Converse.
* @property { function } converse.env.f - And instance of Lodash with its methods wrapped to produce immutable auto-curried iteratee-first data-last methods.
* @property { function } converse.env.sizzle - [Sizzle](https://sizzlejs.com) CSS selector engine.
* @property { function } converse.env.sprintf
* @property { object } converse.env._ - The instance of [lodash-es](http://lodash.com) used by Converse.
* @property { object } converse.env.dayjs - [DayJS](https://github.com/iamkun/dayjs) date manipulation library.
* @property { object } converse.env.utils - Module containing common utility methods used by Converse.
* @memberOf converse
*/
'env': {
$build,
$iq,
$msg,
$pres,
'utils': u,
Collection,
Model,
Promise,
Strophe,
TimeoutError,
URI,
VERSION_NAME,
dayjs,
filesize,
html,
log,
sizzle,
sprintf,
stx,
u,
}
});

View File

@ -1,81 +0,0 @@
import _converse from '../../shared/_converse.js';
import log from '../../log.js';
import { Strophe } from 'strophe.js/src/strophe';
import { TimeoutError } from '../errors.js';
import { toStanza } from '../../utils/stanza.js';
export default {
/**
* Allows you to send XML stanzas.
* @method _converse.api.send
* @param { Element | Stanza } stanza
* @return { void }
* @example
* const msg = converse.env.$msg({
* 'from': 'juliet@example.com/balcony',
* 'to': 'romeo@example.net',
* 'type':'chat'
* });
* _converse.api.send(msg);
*/
send (stanza) {
const { api } = _converse;
if (!api.connection.connected()) {
log.warn("Not sending stanza because we're not connected!");
log.warn(Strophe.serialize(stanza));
return;
}
if (typeof stanza === 'string') {
stanza = toStanza(stanza);
} else if (stanza?.tree) {
stanza = stanza.tree();
}
if (stanza.tagName === 'iq') {
return api.sendIQ(stanza);
} else {
_converse.connection.send(stanza);
api.trigger('send', stanza);
}
},
/**
* Send an IQ stanza
* @method _converse.api.sendIQ
* @param { Element } stanza
* @param { number } [timeout] - The default timeout value is taken from
* the `stanza_timeout` configuration setting.
* @param { Boolean } [reject=true] - Whether an error IQ should cause the promise
* to be rejected. If `false`, the promise will resolve instead of being rejected.
* @returns { Promise } A promise which resolves (or potentially rejected) once we
* receive a `result` or `error` stanza or once a timeout is reached.
* If the IQ stanza being sent is of type `result` or `error`, there's
* nothing to wait for, so an already resolved promise is returned.
*/
sendIQ (stanza, timeout, reject=true) {
const { api, connection } = _converse;
let promise;
stanza = stanza.tree?.() ?? stanza;
if (['get', 'set'].includes(stanza.getAttribute('type'))) {
timeout = timeout || api.settings.get('stanza_timeout');
if (reject) {
promise = new Promise((resolve, reject) => connection.sendIQ(stanza, resolve, reject, timeout));
promise.catch((e) => {
if (e === null) {
throw new TimeoutError(
`Timeout error after ${timeout}ms for the following IQ stanza: ${Strophe.serialize(stanza)}`
);
}
});
} else {
promise = new Promise((resolve) => connection.sendIQ(stanza, resolve, resolve, timeout));
}
} else {
_converse.connection.sendIQ(stanza);
promise = Promise.resolve();
}
api.trigger('send', stanza);
return promise;
}
}

View File

@ -1,114 +0,0 @@
import _converse from '../_converse.js';
import presence_api from './presence.js';
import u, { replacePromise } from '../../utils/core.js';
import { attemptNonPreboundSession, initConnection, setUserJID } from '../../utils/init.js';
import { getOpenPromise } from '@converse/openpromise';
import { user_settings_api } from '../settings/api.js';
import { LOGOUT, PREBIND } from '../constants.js';
export default {
/**
* This grouping collects API functions related to the current logged in user.
*
* @namespace _converse.api.user
* @memberOf _converse.api
*/
user: {
settings: user_settings_api,
...presence_api,
/**
* @method _converse.api.user.jid
* @returns {string} The current user's full JID (Jabber ID)
* @example _converse.api.user.jid())
*/
jid () {
return _converse.connection.jid;
},
/**
* Logs the user in.
*
* If called without any parameters, Converse will try
* to log the user in by calling the `prebind_url` or `credentials_url` depending
* on whether prebinding is used or not.
*
* @method _converse.api.user.login
* @param { string } [jid]
* @param { string } [password]
* @param { boolean } [automatic=false] - An internally used flag that indicates whether
* this method was called automatically once the connection has been
* initialized. It's used together with the `auto_login` configuration flag
* to determine whether Converse should try to log the user in if it
* fails to restore a previous auth'd session.
* @returns { Promise<void> }
*/
async login (jid, password, automatic=false) {
const { api } = _converse;
jid = jid || api.settings.get('jid');
if (!_converse.connection?.jid || (jid && !u.isSameDomain(_converse.connection.jid, jid))) {
initConnection();
}
if (api.settings.get("connection_options")?.worker && (await _converse.connection.restoreWorkerSession())) {
return;
}
if (jid) {
jid = await setUserJID(jid);
}
// See whether there is a BOSH session to re-attach to
const bosh_plugin = _converse.pluggable.plugins['converse-bosh'];
if (bosh_plugin?.enabled()) {
if (await _converse.restoreBOSHSession()) {
return;
} else if (api.settings.get("authentication") === PREBIND && (!automatic || api.settings.get("auto_login"))) {
return _converse.startNewPreboundBOSHSession();
}
}
password = password || api.settings.get("password");
const credentials = (jid && password) ? { jid, password } : null;
attemptNonPreboundSession(credentials, automatic);
},
/**
* Logs the user out of the current XMPP session.
* @method _converse.api.user.logout
* @example _converse.api.user.logout();
*/
async logout () {
const { api } = _converse;
/**
* Triggered before the user is logged out
* @event _converse#beforeLogout
*/
await api.trigger('beforeLogout', {'synchronous': true});
const promise = getOpenPromise();
const complete = () => {
// Recreate all the promises
Object.keys(_converse.promises).forEach(replacePromise);
delete _converse.jid
// Remove the session JID, otherwise the user would just be logged
// in again upon reload. See #2759
localStorage.removeItem('conversejs-session-jid');
/**
* Triggered once the user has logged out.
* @event _converse#logout
*/
api.trigger('logout');
promise.resolve();
}
_converse.connection.setDisconnectionCause(LOGOUT, undefined, true);
if (_converse.connection !== undefined) {
api.listen.once('disconnected', () => complete());
_converse.connection.disconnect();
} else {
complete();
}
return promise;
}
}
}

View File

@ -1,15 +0,0 @@
import _converse from '../_converse';
import { Model } from '@converse/skeletor/src/model.js';
import { Strophe } from 'strophe.js/src/strophe';
export default Model.extend({
defaults: {
'connection_status': Strophe.Status.DISCONNECTED,
'message': ''
},
initialize () {
const { api } = _converse;
this.on('change', () => api.trigger('connfeedback', _converse.connfeedback));
}
});

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