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": { "parserOptions": {
"ecmaVersion": 2020, "ecmaVersion": 2017,
"sourceType": "module", "sourceType": "module",
"allowImportExportEverywhere": true "allowImportExportEverywhere": true
}, },
@ -10,12 +10,8 @@
"jasmine": true, "jasmine": true,
"es6": true "es6": true
}, },
"plugins": ["@typescript-eslint"], "plugins": [],
"extends": [ "extends": ["eslint:recommended"],
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"globals": { "globals": {
"Uint8Array": true, "Uint8Array": true,
"Promise": 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 ] branches: [ master ]
pull_request: pull_request:
branches: [ master ] branches: [ master ]
workflow_dispatch:
jobs: jobs:
build: build:
@ -18,7 +17,7 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [18.x] node-version: [16.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/ # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps: steps:

3
.gitignore vendored
View File

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

2
.nvmrc
View File

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

View File

@ -1,4 +1,5 @@
{ {
"arrowParens": "avoid",
"printWidth": 120, "printWidth": 120,
"quoteProps": "preserve", "quoteProps": "preserve",
"singleQuote": true, "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 # 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) ## 10.0.0 (2022-10-30)
- Update to Strophe.js 1.6.0 which adds support for SCRAM-SHA-256 and SCRAM-SHA-512 - 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. * An XMPP chat client that runs in the browser.
* *
* Version: 10.1.6 * Version: 10.0.0
* *
* Copyright: JC Brand 2013-2018 * Copyright: JC Brand 2013-2018
* Except for 3rd party dependencies. * Except for 3rd party dependencies.

View File

@ -3,11 +3,13 @@ BOOTSTRAP = ./node_modules/
BUILDDIR = ./docs BUILDDIR = ./docs
KARMA ?= ./node_modules/.bin/karma KARMA ?= ./node_modules/.bin/karma
CLEANCSS ?= ./node_modules/clean-css-cli/bin/cleancss CLEANCSS ?= ./node_modules/clean-css-cli/bin/cleancss
ESLINT ?= ./node_modules/.bin/eslint
HTTPSERVE ?= ./node_modules/.bin/http-server HTTPSERVE ?= ./node_modules/.bin/http-server
HTTPSERVE_PORT ?= 8000 HTTPSERVE_PORT ?= 8000
INKSCAPE ?= inkscape INKSCAPE ?= inkscape
INSTALL ?= install INSTALL ?= install
JSDOC ?= ./node_modules/.bin/jsdoc JSDOC ?= ./node_modules/.bin/jsdoc
LERNA ?= ./node_modules/.bin/lerna
OXIPNG ?= oxipng OXIPNG ?= oxipng
PAPER = PAPER =
RJS ?= ./node_modules/.bin/r.js RJS ?= ./node_modules/.bin/r.js
@ -58,14 +60,13 @@ serve: node_modules dist
serve_bg: node_modules serve_bg: node_modules
$(HTTPSERVE) -p $(HTTPSERVE_PORT) -c-1 -s & $(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 ## 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 src/i18n/converse.pot: dist/converse-no-dependencies.js
$(GETTEXT) 2>&1 > /dev/null; exit $$?; $(GETTEXT) 2>&1 > /dev/null; exit $$?;
@ -82,9 +83,10 @@ po:
######################################################################## ########################################################################
## Release management ## Release management
.PHONY: version .PHONY: release
version: release:
$(SED) -i '/^export const VERSION_NAME =/s/=.*/= "v$(VERSION)";/' src/headless/shared/constants.js 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 '/Version:/s/:.*/: $(VERSION)/' COPYRIGHT
$(SED) -i '/Project-Id-Version:/s/:.*/: Converse.js $(VERSION)\n"/' src/i18n/converse.pot $(SED) -i '/Project-Id-Version:/s/:.*/: Converse.js $(VERSION)\n"/' src/i18n/converse.pot
$(SED) -i '/"version":/s/:.*/: "$(VERSION)",/' manifest.json $(SED) -i '/"version":/s/:.*/: "$(VERSION)",/' manifest.json
@ -99,33 +101,19 @@ version:
make pot make pot
make po make po
make dist make dist
npm pack
release-checkout: cd src/headless && npm pack
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)
.PHONY: postrelease .PHONY: postrelease
postrelease: postrelease:
$(SED) -i '/^export const VERSION_NAME =/s/=.*/= "v$(VERSION)dev";/' src/headless/shared/constants.js $(SED) -i '/^_converse.VERSION_NAME =/s/=.*/= "v$(VERSION)dev";/' src/headless/core.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
######################################################################## ########################################################################
## Install dependencies ## Install dependencies
$(LERNA):
npm install lerna
${NVM_DIR}/nvm.sh: ${NVM_DIR}/nvm.sh:
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
source ~/.bashrc source ~/.bashrc
@ -137,9 +125,12 @@ nvm: ${NVM_DIR}/nvm.sh
node: .nvmrc node: .nvmrc
. $(HOME)/.nvm/nvm.sh && nvm install . $(HOME)/.nvm/nvm.sh && nvm install
node_modules: package.json src/headless/package.json package-lock.json: package.json
npm install npm install
node_modules: $(LERNA) package.json package-lock.json src/headless/package.json src/headless/package-lock.json
npm run lerna
.PHONY: clean .PHONY: clean
clean: clean:
npm run clean npm run clean
@ -156,9 +147,6 @@ devserver: node_modules
######################################################################## ########################################################################
## Builds ## 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 dist/converse.js:: node_modules
npm run build 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 dist:: node_modules src/* | dist/website.css dist/website.min.css
npm run headless npm run headless
# Ideally this should just be `npm run build`. 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)
.PHONY: install .PHONY: install
install:: dist install:: dist
@ -223,24 +202,20 @@ install:: dist
cdn:: node_modules cdn:: node_modules
npm run cdn npm run cdn
.PHONY: types
types:: node_modules
npm run types
######################################################################## ########################################################################
## Tests ## Tests
.PHONY: eslint .PHONY: eslint
eslint: node_modules eslint: node_modules
npm run lint $(ESLINT) src/**/*.js
.PHONY: check .PHONY: check
check: eslint | dist/converse.js dist/converse.css check: eslint | dist/converse.js dist/converse.css
npm run test -- $(ARGS) $(KARMA) start karma.conf.js $(ARGS)
.PHONY: test .PHONY: test
test: test:
npm run test -- $(ARGS) $(KARMA) start karma.conf.js $(ARGS)
######################################################################## ########################################################################
## Documentation ## 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 ## 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> <p>
<a href="https://blokt.com?utm_source=conversejs" target="_blank" rel="noopener"> <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"> <img alt="Blokt Crypto & Privacy" src="https://raw.githubusercontent.com/conversejs/converse.js/541613d1fea8aef364af00180f60e959162e5e4b/logo/blokt.png" width="200">
</a> </a>
</p> </p>
<p> <p>
<a href="https://primesound.org/?utm_source=conversejs" target="_blank" rel="noopener"> <a href="https://www.codefirst.co.uk?utm_source=conversejs" target="_blank" rel="noopener">
<img alt="Prime Sound" src="https://raw.githubusercontent.com/conversejs/media/main/logos/primesound.png" width="200"> <img alt="Codefirst" src="https://raw.githubusercontent.com/conversejs/converse.js/541613d1fea8aef364af00180f60e959162e5e4b/logo/codefirst.png" width="200">
</a> </a>
</p> </p>
<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"> <img alt="KeyCDN" src="https://raw.githubusercontent.com/conversejs/converse.js/541613d1fea8aef364af00180f60e959162e5e4b/logo/keycdn.png" width="200">
</a> </a>
</p> </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 # 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. 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. 4. Do a `git diff` to check if things look sane.
5. Do a quick manual test with the `dist` files (via `index.html`) 5. Do a quick manual test with the `dist` files (via `index.html`)
6. `git commit -am "Release 10.1.6"` 6. `git commit -am "Release 10.0.1"`
7. `git tag -s v10.1.6 -m "Release 10.1.6"` 7. `git tag -s v10.0.1 -m "Release 10.0.1"`
8. `git push && git push origin v10.1.6` 8. Run `git push && git push origin v10.0.1`
9. `make publish BRANCH=v10.1.6` 9. Update https://conversejs.org
10. Update release page on Github
* Upload tar files
11. Update https://conversejs.org
* `cd /home/conversejs/converse.js` * `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 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"/> <category rdf:resource="https://linkmauve.fr/ns/xmpp-doap#category-client"/>
<programming-language>JavaScript</programming-language> <programming-language>JavaScript</programming-language>
<os>Browser</os> <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"/> <schema:logo rdf:resource="https://raw.githubusercontent.com/conversejs/converse.js/master/logo/conversejs-filled.svg"/>
<repository> <repository>
<GitRepository> <GitRepository>

View File

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

View File

@ -2,21 +2,25 @@
<h4 class="sidebar-title">Sponsored by</h4> <h4 class="sidebar-title">Sponsored by</h4>
</span> </span>
<ul class="sponsors-list"> <ul class="sponsors-list">
<li><a href="https://bairesdev.com/sponsoring-open-source-projects/?utm_source=conversejs" target="_blank" rel="noopener"> <li><a href="https://www.keycdn.com/?utm_source=conversejs" target="_blank" rel="noopener">
<img style="width: 10em" src="/media/logos/bairesdev-primary.png" alt="BairesDev"> <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> </a>
</li> </li>
<li><a href="https://blokt.com/?utm_source=conversejs" target="_blank" rel="noopener"> <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"> <img style="width: 9em" src="/logo/blokt.png" alt="Blokt Crypto & Privacy">
</a> </a>
</li> </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"> <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"> <img style="width: 9em" src="/media/logos/primesound.png" alt="Prime Sound">
</a> </a>
</li> </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> </ul>
<span class="centered-text-container patreon-link-container"><a href="https://conversejs.org#sponsors">Become a sponsor</a></span> <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. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '10.1.6' version = '10.0.0'
# The full version, including alpha/beta/rc tags. # 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 # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # 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 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. `authentication`_ is set to ``login``, then Converse will try to log the user in.
auto_away auto_away
--------- ---------
@ -693,7 +694,7 @@ appear in another.
.. code-block:: javascript .. code-block:: javascript
converse.initialize({ 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. 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 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. 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`:
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 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``. 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 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: 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.0.0/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.css
You can include these two URLs inside the *<head>* element of your website You can include these two URLs inside the *<head>* element of your website
via the *script* and *link* tags: via the *script* and *link* tags:
.. code-block:: html .. code-block:: html
<link rel="stylesheet" type="text/css" media="screen" href="https://cdn.conversejs.org/10.1.6/dist/converse.min.css"> <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.1.6/dist/converse.min.js" charset="utf-8"></script> <script src="https://cdn.conversejs.org/10.0.0/dist/converse.min.js" charset="utf-8"></script>
Option 2: Download the builds from Github 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 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. ``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"> <div class="sponsors">
<h2>Converse is supported by:</h2> <h2>Converse is supported by:</h2>
<ul > <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://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://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> </ul>
</div> </div>

View File

@ -11,17 +11,17 @@
<!-- These files are NOT needed when using converse.js in your own project. --> <!-- 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 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.0.0/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/website.min.css" />
<noscript><p><img src="//stats.opkode.com/piwik.php?idsite=1" style="border:0;" alt="" /></p></noscript> <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="/src/website.js"></script>
<script type="text/javascript" src="analytics.js"></script> <script type="text/javascript" src="analytics.js"></script>
<!-- *********************************************************************** --> <!-- *********************************************************************** -->
<![if gte IE 11]> <![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/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]> <![endif]>
</head> </head>
@ -66,7 +66,7 @@
<table id="jslicense-labels1" style="width: 100%"> <table id="jslicense-labels1" style="width: 100%">
<tr> <tr>
<td> <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>
<td> <td>
<a href="https://www.mozilla.org/en-US/MPL/2.0/">MPL-2.0</a> <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/media-sharing.js", type: 'module' },
{ pattern: "src/plugins/omemo/tests/muc.js", type: 'module' }, { pattern: "src/plugins/omemo/tests/muc.js", type: 'module' },
{ pattern: "src/plugins/omemo/tests/omemo.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/push/tests/push.js", type: 'module' },
{ pattern: "src/plugins/register/tests/register.js", type: 'module' }, { pattern: "src/plugins/register/tests/register.js", type: 'module' },
{ pattern: "src/plugins/roomslist/tests/roomslist.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;" <svg class="converse-svg-logo"
xml:space="preserve" version="1.1" xmlns:svg="http://www.w3.org/2000/svg"
viewBox="0 0 376 311" height="20%" width="10rem" xmlns="http://www.w3.org/2000/svg"
sodipodi:docname="chapril-logo.svg" inkscape:version="0.92.1 r15371"> xmlns:xlink="http://www.w3.org/1999/xlink"
<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"/> viewBox="0 0 364 364">
<metadata id="metadata114"> <title>Converse</title>
<rdf:RDF> <g class="cls-1" id="g904">
<cc:Work rdf:about=""> <g data-name="Layer 2">
<dc:format>image/svg+xml</dc:format> <g data-name="Layer 7">
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> <path
<dc:title/> class="cls-3"
<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"/> 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" />
<dc:creator> <path
<cc:Agent> class="cls-4"
<dc:title>Antoine BARDELLI</dc:title> 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" />
</cc:Agent> </g>
</dc:creator> </g>
</cc:Work> </g>
</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>

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"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg <svg
xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#" xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="chapril-logo" class="converse-svg-logo"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;" viewBox="0 0 364 364"
class="converse-svg-logo" version="1.1"
xml:space="preserve" version="1.1" id="svg13"
viewBox="0 0 376 311" height="20%" width="6rem" sodipodi:docname="conversejs-with-byline.svg"
sodipodi:docname="chapril-logo.svg" inkscape:version="0.92.1 r15371"> inkscape:version="0.92.2 5c3e80d, 2017-08-06">
<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
<metadata id="metadata114"> id="metadata19">
<rdf:RDF> <rdf:RDF>
<cc:Work rdf:about=""> <cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> <dc:type
<dc:title/> rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<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:title>Converse</dc:title>
<dc:creator>
<cc:Agent>
<dc:title>Antoine BARDELLI</dc:title>
</cc:Agent>
</dc:creator>
</cc:Work> </cc:Work>
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
<defs id="defs112"/> <defs
<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)"> id="defs17">
<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"/> <inkscape:perspective
<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"/> sodipodi:type="inkscape:persp3d"
<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"/> inkscape:vp_x="0 : 182 : 1"
<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"/> inkscape:vp_y="0 : 1000 : 0"
<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"/> inkscape:vp_z="364 : 182 : 1"
<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"/> inkscape:persp3d-origin="182 : 121.33333 : 1"
<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"/> id="perspective2147" />
<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"/> </defs>
<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"/> <sodipodi:namedview
<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"/> 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> </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> </svg>

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

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

View File

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

View File

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

View File

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

View File

@ -49,7 +49,7 @@ const converse = {
* *
* @memberOf converse * @memberOf converse
* @method load * @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 * @example
* converse.load({assets_path: '/path/to/assets/'}); * converse.load({assets_path: '/path/to/assets/'});
*/ */
@ -57,7 +57,7 @@ const converse = {
if (settings.assets_path) { if (settings.assets_path) {
__webpack_public_path__ = settings.assets_path; // eslint-disable-line no-undef __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])); Object.keys(plugins).forEach(name => converse.plugins.add(name, plugins[name]));
return converse; 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 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. is used to determine which plugins are included in the build.

View File

@ -2,16 +2,753 @@
* @copyright The Converse.js contributors * @copyright The Converse.js contributors
* @license Mozilla Public License (MPLv2) * @license Mozilla Public License (MPLv2)
*/ */
import './shared/constants.js'; import URI from 'urijs';
import _converse from './shared/_converse'; import _converse from '@converse/headless/shared/_converse';
import advancedFormat from 'dayjs/plugin/advancedFormat'; 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 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 { _converse };
export { i18n }; export { i18n };
export { api };
import {
attemptNonPreboundSession,
cleanup,
getConnectionServiceURL,
initClientConfig,
initPlugins,
initSessionStorage,
registerGlobalEventHandlers,
setUserJID,
} from './utils/init.js';
dayjs.extend(advancedFormat); 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. * 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/bookmarks/index.js"; // XEP-0199 XMPP Ping
import "./plugins/bosh.js"; // XEP-0206 BOSH import "./plugins/bosh.js"; // XEP-0206 BOSH
import "./plugins/caps/index.js"; // XEP-0115 Entity Capabilities import "./plugins/caps/index.js"; // XEP-0115 Entity Capabilities
import "./plugins/chat/index.js"; // RFC-6121 Instant messaging import "./plugins/chat/index.js"; // RFC-6121 Instant messaging
import "./plugins/chatboxes/index.js"; import "./plugins/chatboxes/index.js";
import "./plugins/disco/index.js"; // XEP-0030 Service discovery 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/headlines/index.js"; // Support for headline messages
import "./plugins/mam/index.js"; // XEP-0313 Message Archive Management import "./plugins/mam/index.js"; // XEP-0313 Message Archive Management
import "./plugins/muc/index.js"; // XEP-0045 Multi-user chat 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 = { const LEVELS = {
'debug': 0, 'debug': 0,
@ -8,26 +8,24 @@ const LEVELS = {
'fatal': 4 'fatal': 4
} }
/* eslint-disable @typescript-eslint/no-empty-function */
const logger = Object.assign({ const logger = Object.assign({
'debug': console?.log ? console.log.bind(console) : function noop () {}, 'debug': console?.log ? console.log.bind(console) : function noop () {},
'error': 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 () {}, 'info': console?.log ? console.log.bind(console) : function noop () {},
'warn': console?.log ? console.log.bind(console) : function noop () {} 'warn': console?.log ? console.log.bind(console) : function noop () {}
}, console); }, console);
/* eslint-enable @typescript-eslint/no-empty-function */
/** /**
* The log namespace * The log namespace
* @namespace log * @namespace log
*/ */
export default { const log = {
/** /**
* The the log-level, which determines how verbose the logging is. * The the log-level, which determines how verbose the logging is.
* @method log#setLogLevel * @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) { setLogLevel (level) {
if (!['debug', 'info', 'warn', 'error', 'fatal'].includes(level)) { if (!['debug', 'info', 'warn', 'error', 'fatal'].includes(level)) {
@ -44,7 +42,7 @@ export default {
* logged as well. * logged as well.
* @method log#log * @method log#log
* @param { string | Error } message - The message to be logged * @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='') { log (message, level, style='') {
if (LEVELS[level] < LEVELS[this.loglevel]) { if (LEVELS[level] < LEVELS[this.loglevel]) {
@ -95,3 +93,5 @@ export default {
this.log(message, 'fatal', style); this.log(message, 'fatal', style);
} }
} }
export default log;

View File

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

View File

@ -1,15 +1,12 @@
{ {
"name": "@converse/headless", "name": "@converse/headless",
"version": "10.1.6", "version": "10.0.0",
"description": "Converse.js Headless build", "description": "Converse.js Headless build",
"author": "JC Brand <jc@opkode.com>", "author": "cmrd Senya <senya@riseup.net>",
"contributors": [
"cmrd Senya <senya@riseup.net>"
],
"homepage": "https://conversejs.org", "homepage": "https://conversejs.org",
"license": "MPL-2.0", "license": "MPL-2.0",
"main": "dist/converse-headless.js", "main": "dist/converse-headless.min.js",
"module": "index.js", "module": "headless.js",
"keywords": [ "keywords": [
"converse.js", "converse.js",
"XMPP", "XMPP",
@ -30,19 +27,19 @@
"bugs": { "bugs": {
"url": "https://github.com/conversejs/converse.js/issues" "url": "https://github.com/conversejs/converse.js/issues"
}, },
"gitHead": "9641dcdc820e029b05930479c242d2b707bbe8e2",
"dependencies": { "dependencies": {
"@converse/openpromise": "^0.0.1", "@converse/openpromise": "^0.0.1",
"@converse/skeletor": "^0.0.8", "@converse/skeletor": "0.0.7",
"dayjs": "^1.11.8", "dayjs": "1.11.6",
"dompurify": "^2.3.1", "dompurify": "^2.3.1",
"filesize": "^10.0.7", "filesize": "^7.0.0",
"localforage-webextensionstorage-driver": "^3.0.0", "localforage-webextensionstorage-driver": "^3.0.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"pluggable.js": "3.0.1", "pluggable.js": "3.0.1",
"sizzle": "^2.3.5", "sizzle": "^2.3.5",
"sprintf-js": "^1.1.2", "sprintf-js": "^1.1.2",
"strophe.js": "^1.6.2", "strophe.js": "1.6.0",
"urijs": "^1.19.10" "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: { tokens: {
/** /**
* @method api.tokens.get * @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. * @returns 'string' A token, either the RID or SID token depending on what's asked for.
* @example _converse.api.tokens.get('rid'); * @example _converse.api.tokens.get('rid');
*/ */

View File

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

View File

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

View File

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

View File

@ -1,13 +1,12 @@
import ModelWithContact from './model-with-contact.js'; import ModelWithContact from './model-with-contact.js';
import filesize from "filesize";
import isMatch from "lodash-es/isMatch"; import isMatch from "lodash-es/isMatch";
import isObject from "lodash-es/isObject"; import isObject from "lodash-es/isObject";
import log from '@converse/headless/log'; import log from '@converse/headless/log';
import pick from "lodash-es/pick"; import pick from "lodash-es/pick";
import { Model } from '@converse/skeletor/src/model.js'; import { Model } from '@converse/skeletor/src/model.js';
import { TimeoutError } from '../../shared/errors.js';
import { _converse, api, converse } from "../../core.js"; import { _converse, api, converse } from "../../core.js";
import { debouncedPruneHistory, handleCorrection } from '@converse/headless/shared/chat/utils.js'; import { debouncedPruneHistory, handleCorrection } from '@converse/headless/shared/chat/utils.js';
import { filesize } from "filesize";
import { getMediaURLsMetadata } from '@converse/headless/shared/parsers.js'; import { getMediaURLsMetadata } from '@converse/headless/shared/parsers.js';
import { getOpenPromise } from '@converse/openpromise'; import { getOpenPromise } from '@converse/openpromise';
import { initStorage } from '@converse/headless/utils/storage.js'; import { initStorage } from '@converse/headless/utils/storage.js';
@ -369,7 +368,7 @@ const ChatBox = ModelWithContact.extend({
}, },
async createMessageFromError (error) { async createMessageFromError (error) {
if (error instanceof TimeoutError) { if (error instanceof _converse.TimeoutError) {
const msg = await this.createMessage({ const msg = await this.createMessage({
'type': 'error', 'type': 'error',
'message': error.message, '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. * Parses a passed in message stanza and returns an object of attributes.
* @method st#parseMessage * @method st#parseMessage
* @param { Element } stanza - The message stanza * @param { XMLElement } stanza - The message stanza
* @param { _converse } _converse * @param { _converse } _converse
* @returns { (MessageAttributes|Error) } * @returns { (MessageAttributes|Error) }
*/ */
@ -178,7 +178,7 @@ export async function parseMessage (stanza) {
'thread': stanza.querySelector('thread')?.textContent, 'thread': stanza.querySelector('thread')?.textContent,
'time': delay ? dayjs(delay.getAttribute('stamp')).toISOString() : now, 'time': delay ? dayjs(delay.getAttribute('stamp')).toISOString() : now,
'to': stanza.getAttribute('to'), 'to': stanza.getAttribute('to'),
'type': stanza.getAttribute('type') || 'normal' 'type': stanza.getAttribute('type')
}, },
getErrorAttributes(stanza), getErrorAttributes(stanza),
getOutOfBandAttributes(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 { _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 { 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) { export function openChat (jid) {
if (!u.isValidJID(jid)) { if (!u.isValidJID(jid)) {
@ -14,7 +13,7 @@ export function openChat (jid) {
} }
export async function onClearSession () { export async function onClearSession () {
if (shouldClearCache()) { if (_converse.shouldClearCache()) {
await Promise.all( await Promise.all(
_converse.chatboxes.map(c => c.messages && c.messages.clearStore({ 'silent': true })) _converse.chatboxes.map(c => c.messages && c.messages.clearStore({ 'silent': true }))
); );
@ -61,22 +60,43 @@ export function autoJoinChats () {
export function registerMessageHandlers () { export function registerMessageHandlers () {
_converse.connection.addHandler( _converse.connection.addHandler(
stanza => { stanza => {
if ( if (sizzle(`message > result[xmlns="${Strophe.NS.MAM}"]`, stanza).pop()) {
['groupchat', 'error'].includes(stanza.getAttribute('type')) || // MAM messages are handled in converse-mam.
isHeadline(stanza) || // We shouldn't get MAM messages here because
isServerMessage(stanza) || // they shouldn't have a `type` attribute.
isArchived(stanza) log.warn(`Received a MAM message with type "chat".`);
) {
return true; return true;
} }
return _converse.handleMessageStanza(stanza) || true; _converse.handleMessageStanza(stanza);
return true;
}, },
null, null,
'message', 'message',
'chat'
); );
_converse.connection.addHandler( _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, null,
'message', 'message',
'error' 'error'
@ -89,8 +109,6 @@ export function registerMessageHandlers () {
* @param { MessageAttributes } attrs - The message attributes * @param { MessageAttributes } attrs - The message attributes
*/ */
export async function handleMessageStanza (stanza) { export async function handleMessageStanza (stanza) {
stanza = stanza.tree?.() ?? stanza;
if (isServerMessage(stanza)) { if (isServerMessage(stanza)) {
// Prosody sends headline messages with type `chat`, so we need to filter them out here. // Prosody sends headline messages with type `chat`, so we need to filter them out here.
const from = stanza.getAttribute('from'); const from = stanza.getAttribute('from');
@ -98,7 +116,7 @@ export async function handleMessageStanza (stanza) {
} }
let attrs; let attrs;
try { try {
attrs = await parseMessage(stanza); attrs = await parseMessage(stanza, _converse);
} catch (e) { } catch (e) {
return log.error(e); return log.error(e);
} }
@ -114,7 +132,7 @@ export async function handleMessageStanza (stanza) {
* @typedef { Object } MessageData * @typedef { Object } MessageData
* An object containing the original message stanza, as well as the * An object containing the original message stanza, as well as the
* parsed attributes. * parsed attributes.
* @property { Element } stanza * @property { XMLElement } stanza
* @property { MessageAttributes } stanza * @property { MessageAttributes } stanza
* @property { ChatBox } chatbox * @property { ChatBox } chatbox
*/ */

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,6 @@
* @copyright 2022, the Converse.js contributors * @copyright 2022, the Converse.js contributors
* @license Mozilla Public License (MPLv2) * @license Mozilla Public License (MPLv2)
*/ */
import './utils.js';
import { Model } from '@converse/skeletor/src/model.js'; import { Model } from '@converse/skeletor/src/model.js';
import { _converse, api, converse } from "../../core.js"; import { _converse, api, converse } from "../../core.js";
import { getOpenPromise } from '@converse/openpromise'; import { getOpenPromise } from '@converse/openpromise';
@ -86,8 +85,8 @@ converse.plugins.add('converse-emoji', {
async initialize () { async initialize () {
if (!converse.emojis.initialized) { if (!converse.emojis.initialized) {
converse.emojis.initialized = true; converse.emojis.initialized = true;
const module = await import(/*webpackChunkName: "emojis" */ './emoji.json'); const { default: json } = await import(/*webpackChunkName: "emojis" */ './emoji.json');
const json = converse.emojis.json = module.default; converse.emojis.json = json;
converse.emojis.by_sn = Object.keys(json).reduce((result, cat) => Object.assign(result, json[cat]), {}); 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 = Object.values(converse.emojis.by_sn);
converse.emojis.list.sort((a, b) => a.sn < b.sn ? -1 : (a.sn > b.sn ? 1 : 0)); 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; * Determines whether the passed in string is just a single emoji shortname;
* @namespace u * @namespace u
* @method u.isOnlyEmojis * @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 } * @returns { Boolean }
*/ */
function isOnlyEmojis (text) { function isOnlyEmojis (text) {

View File

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

View File

@ -4,7 +4,7 @@
*/ */
import HeadlinesFeed from './feed.js'; import HeadlinesFeed from './feed.js';
import headlines_api from './api.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'; 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". * Handler method for all incoming messages of type "headline".
* @param { Element } stanza * @param { XMLElement } stanza
*/ */
export async function onHeadlineMessage (stanza) { export async function onHeadlineMessage (stanza) {
if (isHeadline(stanza) || isServerMessage(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 log from '@converse/headless/log';
import sizzle from "sizzle"; 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"; import { _converse, api, converse } from "@converse/headless/core";
const { Strophe, $iq, dayjs } = converse.env; const { Strophe, $iq, dayjs } = converse.env;
@ -271,7 +270,7 @@ export default {
const { __ } = _converse; const { __ } = _converse;
const err_msg = __("Timeout while trying to fetch archived messages."); const err_msg = __("Timeout while trying to fetch archived messages.");
log.error(err_msg); log.error(err_msg);
error = new TimeoutError(err_msg); error = new _converse.TimeoutError(err_msg);
return { messages, error }; return { messages, error };
} else if (u.isErrorStanza(iq_result)) { } 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 * @typedef { Object } MAMOptions
* A map of MAM related options that may be passed to fetchArchivedMessages * 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" * Defaults to "archived_messages_page_size"
* @param { string } [options.after] - The XEP-0359 stanza ID of a message * @param { string } [options.after] - The XEP-0359 stanza ID of a message
* after which messages should be returned. Implies forward paging. * 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. * 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 { MAMOptions } [options]
* @param { ('forwards'|'backwards'|null)} [should_page=null] - Determines whether * @param { ('forwards'|'backwards'|null)} [should_page=null] - Determines whether
* this function should recursively page through the entire result set if a limited * 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 * @param { Model } occupant
* @returns { Array<('owner'|'admin'|'member'|'outcast'|'none')> } - An array of assignable affiliations * @returns { Array<('owner'|'admin'|'member'|'outcast'|'none')> } - An array of assignable affiliations
*/ */
@ -54,9 +54,9 @@ export function getAssignableAffiliations (occupant) {
if (!Array.isArray(disabled)) { if (!Array.isArray(disabled)) {
disabled = disabled ? AFFILIATIONS : []; disabled = disabled ? AFFILIATIONS : [];
} }
if (occupant?.get('affiliation') === 'owner') { if (occupant.get('affiliation') === 'owner') {
return AFFILIATIONS.filter(a => !disabled.includes(a)); 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)); return AFFILIATIONS.filter(a => !['owner', 'admin', ...disabled].includes(a));
} else { } else {
return []; return [];

View File

@ -1,8 +1,7 @@
import log from '../../log'; import log from '../../log';
import u from '../../utils/form';
import { Strophe } from 'strophe.js/src/strophe'; import { Strophe } from 'strophe.js/src/strophe';
import { _converse, api, converse } from '../../core.js'; import { _converse, api } from '../../core.js';
const { u } = converse.env;
export default { export default {
@ -23,7 +22,7 @@ export default {
* @method api.rooms.create * @method api.rooms.create
* @param {(string[]|string)} jid|jids The JID or array of * @param {(string[]|string)} jid|jids The JID or array of
* JIDs of the chatroom(s) to create * 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. * @returns {Promise} Promise which resolves with the Model representing the chat.
*/ */
create (jids, attrs = {}) { create (jids, attrs = {}) {
@ -45,24 +44,24 @@ export default {
* Similar to {@link api.chats.open}, but for groupchats. * Similar to {@link api.chats.open}, but for groupchats.
* *
* @method api.rooms.open * @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). * currently open rooms will be returned).
* @param { string } attrs A map containing any extra room attributes. * @param {string} attrs A map containing any extra room attributes.
* @param { string } [attrs.nick] The current user's nickname for the MUC * @param {string} [attrs.nick] The current user's nickname for the MUC
* @param { boolean } [attrs.auto_configure] A boolean, indicating * @param {boolean} [attrs.auto_configure] A boolean, indicating
* whether the room should be configured automatically or not. * whether the room should be configured automatically or not.
* If set to `true`, then it makes sense to pass in configuration settings. * 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 * configured automatically. Currently it doesn't make sense to specify
* `roomconfig` values if `auto_configure` is set to `false`. * `roomconfig` values if `auto_configure` is set to `false`.
* For a list of configuration values that can be passed in, refer to these values * 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). * 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. * 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.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.bring_to_foreground] A boolean indicating whether the room should be
* brought to the foreground and therefore replace the currently shown chat. * brought to the foreground and therefore replace the currently shown chat.
* If there is no chat currently open, then this option is ineffective. * 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 * room won't be maximized (in `overlayed` view mode) and in
* `fullscreen` view mode a newly opened room won't replace * `fullscreen` view mode a newly opened room won't replace
* another chat already in the foreground. * another chat already in the foreground.

View File

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

View File

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

View File

@ -1,15 +1,14 @@
import ChatRoomOccupant from './occupant.js'; import ChatRoomOccupant from './occupant.js';
import u from '../../utils/form';
import { Collection } from '@converse/skeletor/src/collection.js'; import { Collection } from '@converse/skeletor/src/collection.js';
import { MUC_ROLE_WEIGHTS } from './constants.js'; import { MUC_ROLE_WEIGHTS } from './constants.js';
import { Model } from '@converse/skeletor/src/model.js'; import { Model } from '@converse/skeletor/src/model.js';
import { Strophe } from 'strophe.js/src/strophe.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 { getAffiliationList } from './affiliations/utils.js';
import { getAutoFetchedAffiliationLists } from './utils.js'; import { getAutoFetchedAffiliationLists } from './utils.js';
import { getUniqueId } from '@converse/headless/utils/core.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. * 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 * 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. * @returns { Array } Returns an array of objects representing <activity> elements.
*/ */
export function getMEPActivities (stanza) { 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. * Note, this function doesn't check whether this is actually a MAM archived stanza.
* *
* @private * @private
* @param { Element } stanza - The message stanza * @param { XMLElement } stanza - The message stanza
* @returns { Object } * @returns { Object }
*/ */
function getJIDFromMUCUserData (stanza) { function getJIDFromMUCUserData (stanza) {
@ -71,8 +71,8 @@ function getJIDFromMUCUserData (stanza) {
/** /**
* @private * @private
* @param { Element } stanza - The message stanza * @param { XMLElement } stanza - The message stanza
* @param { Element } original_stanza - The original stanza, that contains the * @param { XMLElement } original_stanza - The original stanza, that contains the
* message stanza, if it was contained, otherwise it's the message stanza itself. * message stanza, if it was contained, otherwise it's the message stanza itself.
* @returns { Object } * @returns { Object }
*/ */
@ -140,8 +140,8 @@ function getSender (attrs, chatbox) {
/** /**
* Parses a passed in message stanza and returns an object of attributes. * Parses a passed in message stanza and returns an object of attributes.
* @param { Element } stanza - The message stanza * @param { XMLElement } stanza - The message stanza
* @param { Element } original_stanza - The original stanza, that contains the * @param { XMLElement } original_stanza - The original stanza, that contains the
* message stanza, if it was contained, otherwise it's the message stanza itself. * message stanza, if it was contained, otherwise it's the message stanza itself.
* @param { _converse.ChatRoom } chatbox * @param { _converse.ChatRoom } chatbox
* @param { _converse } _converse * @param { _converse } _converse
@ -342,7 +342,7 @@ export function parseMemberListIQ (iq) {
/** /**
* Parses a passed in MUC presence stanza and returns an object of attributes. * Parses a passed in MUC presence stanza and returns an object of attributes.
* @method parseMUCPresence * @method parseMUCPresence
* @param { Element } stanza - The presence stanza * @param { XMLElement } stanza - The presence stanza
* @param { _converse.ChatRoom } chatbox * @param { _converse.ChatRoom } chatbox
* @returns { MUCPresenceAttributes } * @returns { MUCPresenceAttributes }
*/ */

View File

@ -124,19 +124,19 @@ describe("A MUC message", function () {
const muc_jid = 'lounge@montague.lit'; const muc_jid = 'lounge@montague.lit';
const model = await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const model = await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const received_stanza = u.toStanza(` const received_stanza = u.toStanza(`
<message to='${_converse.jid}' from='${muc_jid}/mallory' type='groupchat' id='${_converse.connection.getUniqueId()}' > <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}'/> <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'> <fallback xmlns='urn:xmpp:feature-fallback:0' for='urn:xmpp:reply:0'>
<body start='0' end='10'/> <body start='0' end='10'/>
</fallback> </fallback>
<active xmlns='http://jabber.org/protocol/chatstates'/> <active xmlns='http://jabber.org/protocol/chatstates'/>
<body>&gt; ping <body>&gt; ping
pong</body> pong</body>
<request xmlns='urn:xmpp:receipts'/> <request xmlns='urn:xmpp:receipts'/>
</message> </message>
`); `);
await model.handleMessageStanza(received_stanza); await model.handleMessageStanza(received_stanza);
await u.waitUntil(() => model.messages.last()); 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); await u.waitUntil(() => model.messages.length);
let message = model.messages.at(0); let message = model.messages.at(0);
expect(message.get('occupant_id')).toBe("dd72603deec90a38ba552f7c68cbcc61bca202cd"); expect(message.get('occupant_id')).toBe("dd72603deec90a38ba552f7c68cbcc61bca202cd");
expect(message.occupant).not.toBeUndefined(); expect(message.occupant).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.getDisplayName()).toBe('3rdwitch'); expect(message.getDisplayName()).toBe('3rdwitch');
const presence = u.toStanza(` const presence = u.toStanza(`
@ -105,7 +98,7 @@ describe("A MUC occupant", function () {
</presence>`); </presence>`);
_converse.connection._dataRecv(mock.createRequest(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(occupant.get('occupant_id')).toBe('dd72603deec90a38ba552f7c68cbcc61bca202cd');
expect(model.occupants.findWhere({'occupant_id': "dd72603deec90a38ba552f7c68cbcc61bca202cd"})).toBe(occupant); 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. * See XEP-0249: Direct MUC invitations.
* @private * @private
* @method _converse.ChatRoom#onDirectMUCInvitation * @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) { export async function onDirectMUCInvitation (message) {
const { __ } = _converse;
const x_el = sizzle('x[xmlns="jabber:x:conference"]', message).pop(), const x_el = sizzle('x[xmlns="jabber:x:conference"]', message).pop(),
from = Strophe.getBareJidFromJid(message.getAttribute('from')), from = Strophe.getBareJidFromJid(message.getAttribute('from')),
room_jid = x_el.getAttribute('jid'), room_jid = x_el.getAttribute('jid'),
@ -98,20 +99,21 @@ export async function onDirectMUCInvitation (message) {
result = true; result = true;
} else { } else {
// Invite request might come from someone not your roster list // Invite request might come from someone not your roster list
const contact = _converse.roster.get(from)?.getDisplayName() ?? from; let contact = _converse.roster.get(from);
contact = contact ? contact.getDisplayName() : from;
/** if (!reason) {
* *Hook* which is used to gather confirmation whether a direct MUC result = await api.confirm(__('%1$s has invited you to join a groupchat: %2$s', contact, room_jid));
* invitation should be accepted or not. } else {
* result = await api.confirm(
* It's meant for consumers of `@converse/headless` to subscribe to __(
* this hook and then ask the user to confirm. '%1$s has invited you to join a groupchat: %2$s, and left the following reason: "%3$s"',
* contact,
* @event _converse#confirmDirectMUCInvitation room_jid,
*/ reason
result = await api.hook('confirmDirectMUCInvitation', { contact, reason, jid: room_jid }, false); )
);
}
} }
if (result) { if (result) {
const chatroom = await openChatRoom(room_jid, { 'password': x_el.getAttribute('password') }); const chatroom = await openChatRoom(room_jid, { 'password': x_el.getAttribute('password') });
if (chatroom.session.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED) { if (chatroom.session.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED) {

View File

@ -10,7 +10,7 @@ export default {
* @method api.ping * @method api.ping
* @param { String } [jid] - The JID of the service to 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. * 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; * milliseconds to wait for a response. The default is 10000;
* @returns { Boolean | null } * @returns { Boolean | null }
* Whether the pinged entity responded with a non-error IQ stanza. * 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 * Publshes an item to a PubSub node
* *
* @method _converse.api.pubsub.publish * @method _converse.api.pubsub.publish
* @param { string } jid The JID of the pubsub service where the node resides. * @param {string} jid The JID of the pubsub service where the node resides.
* @param { string } node The node being published to * @param {string} node The node being published to
* @param {Strophe.Builder} item The Strophe.Builder representation of the XML element being published * @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) * (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 * options are a strict requirement or not. If they're NOT
* strict, then Converse will publish to the node even if * strict, then Converse will publish to the node even if
* the publish options precondication cannot be met. * the publish options precondication cannot be met.
@ -74,7 +74,7 @@ converse.plugins.add('converse-pubsub', {
// The publish-options precondition couldn't be // The publish-options precondition couldn't be
// met. We re-publish but without publish-options. // met. We re-publish but without publish-options.
const el = stanza.tree(); const el = stanza.nodeTree;
el.querySelector('publish-options').outerHTML = ''; el.querySelector('publish-options').outerHTML = '';
log.warn(`PubSub: Republishing without publish options. ${el.outerHTML}`); log.warn(`PubSub: Republishing without publish options. ${el.outerHTML}`);
await api.sendIQ(el); await api.sendIQ(el);

View File

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

View File

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

View File

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

View File

@ -30,7 +30,7 @@ export const Presence = Model.extend({
const hpr = this.getHighestPriorityResource(); const hpr = this.getHighestPriorityResource();
const show = hpr?.attributes?.show || 'offline'; const show = hpr?.attributes?.show || 'offline';
if (this.get('show') !== show) { 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. * from the passed in presence stanza.
* Also updates the presence if the resource has higher priority (and is newer). * Also updates the presence if the resource has higher priority (and is newer).
* @private * @private
* @param { Element } presence: The presence stanza * @param { XMLElement } presence: The presence stanza
*/ */
addResource (presence) { addResource (presence) {
const jid = presence.getAttribute('from'); const jid = presence.getAttribute('from'),
const name = Strophe.getResourceFromJid(jid); name = Strophe.getResourceFromJid(jid),
const delay = sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, presence).pop(); delay = sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, presence).pop(),
const priority = presence.querySelector('priority')?.textContent; priority = presence.querySelector('priority')?.textContent ?? 0,
const resource = this.resources.get(name); resource = this.resources.get(name),
const settings = { settings = {
name, 'name': name,
'priority': isNaN(parseInt(priority, 10)) ? 0 : parseInt(priority, 10), 'priority': isNaN(parseInt(priority, 10)) ? 0 : parseInt(priority, 10),
'show': presence.querySelector('show')?.textContent ?? 'online', 'show': presence.querySelector('show')?.textContent ?? 'online',
'timestamp': delay ? dayjs(delay.getAttribute('stamp')).toISOString() : (new Date()).toISOString() 'timestamp': delay ? dayjs(delay.getAttribute('stamp')).toISOString() : (new Date()).toISOString()
}; };
if (resource) { if (resource) {
resource.save(settings); resource.save(settings);
} else { } else {
@ -78,7 +78,9 @@ export const Presence = Model.extend({
*/ */
removeResource (name) { removeResource (name) {
const resource = this.resources.get(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 log from "@converse/headless/log";
import { Model } from '@converse/skeletor/src/model.js'; import { Model } from '@converse/skeletor/src/model.js';
import { RosterFilter } from '@converse/headless/plugins/roster/filter.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 { _converse, api, converse } from "@converse/headless/core";
import { initStorage } from '@converse/headless/utils/storage.js'; import { initStorage } from '@converse/headless/utils/storage.js';
import { shouldClearCache } from '@converse/headless/utils/core.js';
const { $pres } = converse.env; const { $pres } = converse.env;
@ -90,7 +88,7 @@ async function clearPresences () {
*/ */
export async function onClearSession () { export async function onClearSession () {
await clearPresences(); await clearPresences();
if (shouldClearCache()) { if (_converse.shouldClearCache()) {
if (_converse.rostergroups) { if (_converse.rostergroups) {
await _converse.rostergroups.clearStore(); await _converse.rostergroups.clearStore();
delete _converse.rostergroups; delete _converse.rostergroups;
@ -199,12 +197,12 @@ export function rejectPresenceSubscription (jid, message) {
export function contactsComparator (contact1, contact2) { export function contactsComparator (contact1, contact2) {
const status1 = contact1.presence.get('show') || 'offline'; const status1 = contact1.presence.get('show') || 'offline';
const status2 = contact2.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 name1 = (contact1.getDisplayName()).toLowerCase();
const name2 = (contact2.getDisplayName()).toLowerCase(); const name2 = (contact2.getDisplayName()).toLowerCase();
return name1 < name2 ? -1 : (name1 > name2? 1 : 0); return name1 < name2 ? -1 : (name1 > name2? 1 : 0);
} else { } 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>`); `<iq id="${IQ_stanzas[1].getAttribute('id')}" type="get" xmlns="jabber:client"><query xmlns="jabber:iq:roster"/></iq>`);
await mock.waitForRoster(_converse, 'current', 1); await mock.waitForRoster(_converse, 'current', 1);
expect(Strophe.serialize(IQ_stanzas[2])).toBe( const omemo_iq = IQ_stanzas[2];
`<iq from="romeo@montague.lit/orchard" id="${IQ_stanzas[2].getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+ expect(Strophe.serialize(omemo_iq)).toBe(
`<query xmlns="http://jabber.org/protocol/disco#info"/></iq>`); `<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( 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">`+ `<iq from="romeo@montague.lit/orchard" 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>`); `<query xmlns="http://jabber.org/protocol/disco#info"/></iq>`);
expect(Strophe.serialize(IQ_stanzas[4])).toBe( 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>`); `<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(); iq = IQ_stanzas.pop();
expect(Strophe.serialize(iq)).toBe( expect(Strophe.serialize(iq)).toBe(
`<iq from="romeo@montague.lit" id="${iq.getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+ `<iq from="romeo@montague.lit/orchard" 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>`); `<query xmlns="http://jabber.org/protocol/disco#info"/></iq>`);
iq = IQ_stanzas.pop(); iq = IQ_stanzas.pop();
expect(Strophe.serialize(iq)).toBe( expect(Strophe.serialize(iq)).toBe(
`<iq from="romeo@montague.lit/orchard" id="${iq.getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+ `<iq from="romeo@montague.lit" id="${iq.getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/disco#info"/></iq>`); `<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); 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 { _converse, api } from '@converse/headless/core';
import { STATUS_WEIGHTS } from '../../shared/constants';
export default { 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*. * Set and get the user's chat status, also called their *availability*.
* @namespace _converse.api.user.status * @namespace _converse.api.user.status
@ -25,15 +55,15 @@ export default {
* *
* @async * @async
* @method _converse.api.user.status.set * @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} value The user's chat status (e.g. 'away', 'dnd', 'offline', 'online', 'unavailable' or 'xa')
* @param { string } [message] A custom status message * @param {string} [message] A custom status message
* *
* @example _converse.api.user.status.set('dnd'); * @example _converse.api.user.status.set('dnd');
* @example _converse.api.user.status.set('dnd', 'In a meeting'); * @example _converse.api.user.status.set('dnd', 'In a meeting');
*/ */
async set (value, message) { async set (value, message) {
const data = {'status': value}; const data = {'status': value};
if (!Object.keys(STATUS_WEIGHTS).includes(value)) { if (!Object.keys(_converse.STATUS_WEIGHTS).includes(value)) {
throw new Error( throw new Error(
'Invalid availability value. See https://xmpp.org/rfcs/rfc3921.html#rfc.section.2.2.2.1' 'Invalid availability value. See https://xmpp.org/rfcs/rfc3921.html#rfc.section.2.2.2.1'
); );
@ -55,7 +85,7 @@ export default {
/** /**
* @async * @async
* @method _converse.api.user.status.message.get * @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() * @example const message = _converse.api.user.status.message.get()
*/ */
async get () { async get () {
@ -65,7 +95,7 @@ export default {
/** /**
* @async * @async
* @method _converse.api.user.status.message.set * @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'); * @example _converse.api.user.status.message.set('In a meeting');
*/ */
async set (status) { async set (status) {

View File

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

View File

@ -5,11 +5,10 @@ import { _converse, api, converse } from '@converse/headless/core';
const { Strophe, $pres } = converse.env; const { Strophe, $pres } = converse.env;
export default class XMPPStatus extends Model { const XMPPStatus = Model.extend({
defaults () {
defaults () { // eslint-disable-line class-methods-use-this
return { "status": api.settings.get("default_state") } return { "status": api.settings.get("default_state") }
} },
initialize () { initialize () {
this.on('change', item => { 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')); api.user.presence.send(this.get('status'), null, this.get('status_message'));
} }
}); });
} },
getDisplayName () { getNickname () {
return this.getFullname() || this.getNickname() || _converse.bare_jid;
}
getNickname () { // eslint-disable-line class-methods-use-this
return api.settings.get('nickname'); return api.settings.get('nickname');
} },
getFullname () { // eslint-disable-line class-methods-use-this getFullname () {
return ''; // Gets overridden in converse-vcard // 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) { async constructPresence (type, to=null, status_message) {
type = typeof type === 'string' ? type : (this.get('status') || api.settings.get("default_state")); 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'); status_message = typeof status_message === 'string' ? status_message : this.get('status_message');
let presence; let presence;
const attrs = {to};
if (type === 'subscribe') { if ((type === 'unavailable') ||
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') ||
(type === 'probe') || (type === 'probe') ||
(type === 'error') || (type === 'error') ||
(type === 'unsubscribe') || (type === 'unsubscribe') ||
(type === 'unsubscribed') || (type === 'unsubscribed') ||
(type === 'subscribe') ||
(type === 'subscribed')) { (type === 'subscribed')) {
presence = $pres({ to, type }); attrs['type'] = type;
presence = $pres(attrs);
} else if (type === 'offline') { } else if (type === 'offline') {
presence = $pres({ to, type: 'unavailable' }); attrs['type'] = 'unavailable';
presence = $pres(attrs);
} else if (type === 'online') { } else if (type === 'online') {
presence = $pres({ to }); presence = $pres(attrs);
} else { } 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"); const priority = api.settings.get("priority");
presence.c('priority').t(isNaN(Number(priority)) ? 0 : priority).up(); presence.c('priority').t(isNaN(Number(priority)) ? 0 : priority).up();
if (_converse.idle) {
const { idle, idle_seconds } = _converse;
if (idle) {
const idle_since = new Date(); const idle_since = new Date();
idle_since.setSeconds(idle_since.getSeconds() - idle_seconds); idle_since.setSeconds(idle_since.getSeconds() - _converse.idle_seconds);
presence.c('idle', { xmlns: Strophe.NS.IDLE, since: idle_since.toISOString() }); 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); presence = await api.hook('constructedPresence', null, presence);
return presence; return presence;
} }
} });
export default XMPPStatus;

View File

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

View File

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

View File

@ -1,36 +1,11 @@
import i18n from './i18n.js'; import i18n from '@converse/headless/shared/i18n';
import log from '../log.js'; import log from '@converse/headless/log';
import pluggable from 'pluggable.js/src/pluggable.js'; import { CONNECTION_STATUS } from '@converse/headless/shared/constants';
import { Events } from '@converse/skeletor/src/events.js';
import { Router } from '@converse/skeletor/src/router.js'; import { Router } from '@converse/skeletor/src/router.js';
import { createStore, getDefaultStore } from '../utils/storage.js'; import { TimeoutError } from '@converse/headless/shared/errors';
import { getInitSettings } from './settings/utils.js'; import { createStore, getDefaultStore } from '@converse/headless/utils/storage.js';
import { getInitSettings } from '@converse/headless/shared/settings/utils.js';
import { getOpenPromise } from '@converse/openpromise'; 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 = { const _converse = {
log, log,
CONNECTION_STATUS,
shouldClearCache, // TODO: Should be moved to utils with next major release
VERSION_NAME,
templates: {}, templates: {},
promises: { promises: {
'initialized': getOpenPromise() 'initialized': getOpenPromise()
}, },
// TODO: remove constants in next major release STATUS_WEIGHTS: {
ANONYMOUS, 'offline': 6,
CLOSED, 'unavailable': 5,
EXTERNAL, 'xa': 4,
LOGIN, 'away': 3,
LOGOUT, 'dnd': 2,
OPENED, 'chat': 1, // We currently don't differentiate between "chat" and "online"
PREBIND, '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, SUCCESS: 'success',
DEFAULT_IMAGE, FAILURE: 'failure',
INACTIVE, // Generated from css/images/user.svg
ACTIVE, DEFAULT_IMAGE_TYPE: 'image/svg+xml',
COMPOSING, DEFAULT_IMAGE: "PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMTI4IiBoZWlnaHQ9IjEyOCI+CiA8cmVjdCB3aWR0aD0iMTI4IiBoZWlnaHQ9IjEyOCIgZmlsbD0iIzU1NSIvPgogPGNpcmNsZSBjeD0iNjQiIGN5PSI0MSIgcj0iMjQiIGZpbGw9IiNmZmYiLz4KIDxwYXRoIGQ9Im0yOC41IDExMiB2LTEyIGMwLTEyIDEwLTI0IDI0LTI0IGgyMyBjMTQgMCAyNCAxMiAyNCAyNCB2MTIiIGZpbGw9IiNmZmYiLz4KPC9zdmc+Cg==",
PAUSED,
GONE,
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: { TIMEOUTS: {
// Set as module attr so that we can override in tests.
PAUSED: 10000, PAUSED: 10000,
INACTIVE: 90000 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}, default_connection_options: {'explicitResourceBinding': true},
router: new Router(), router: new Router(),
TimeoutError: TimeoutError,
isTestEnv: () => { isTestEnv: () => {
return getInitSettings()['bosh_service_url'] === 'montague.lit/http-bind'; return getInitSettings()['bosh_service_url'] === 'montague.lit/http-bind';
}, },
@ -121,10 +112,4 @@ const _converse = {
'___': str => str '___': str => str
} }
// Make _converse an event emitter
Object.assign(_converse, Events);
// Make _converse pluggable
pluggable.enable(_converse, '_converse', 'pluggable');
export default _converse; 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