diff --git a/packages/client/README.md b/packages/client/README.md
index 0c4fef356..dd688b6b1 100644
--- a/packages/client/README.md
+++ b/packages/client/README.md
@@ -2,17 +2,33 @@
An XMPP client is an entity that connects to an XMPP server.
-`@xmpp/client` package includes a minimal set of features to connect /authenticate securely and reliably.
+`@xmpp/client` package includes a minimal set of features to connect and authenticate securely and reliably.
+
+It supports Node.js, browser and React Native. See [Connection Method](#connection-methods) for differences.
## Install
`npm install @xmpp/client` or `yarn add @xmpp/client`
-## Example
+## Setup
```js
const {client, xml, jid} = require('@xmpp/client')
+```
+
+or
+
+```html
+
+```
+```js
+const {client, xml, jid} = window.XMPP
+```
+
+## Example
+
+```js
const xmpp = client({
service: 'ws://localhost:5280/xmpp-websocket',
domain: 'localhost',
@@ -63,7 +79,7 @@ See [jid package](/packages/jid)
- `service` `` The service to connect to, accepts an URI or a domain.
- `domain` lookup and connect to the most secure endpoint using [@xmpp/resolve](/packages/resolve)
- - `xmpp://hostname:port` plain TCP, can be upgraded to TLS using [@xmpp/starttls](/packages/starttls)
+ - `xmpp://hostname:port` plain TCP, may be upgraded to TLS by [@xmpp/starttls](/packages/starttls)
- `xmpps://hostname:port` direct TLS
- `ws://hostname:port/path` plain WebSocket
- `wss://hostname:port/path` secure WebSocket
@@ -163,3 +179,28 @@ xmpp.send(xml('presence'))
### xmpp.reconnect
See [@xmpp/reconnect](/packages/reconnect).
+
+## Connection methods
+
+XMPP supports multiple transports, this table list `@xmpp/client` supported and unsupported transport for each environment.
+
+| transport | protocols | Node.js | Browser | React Native |
+| :------------------------------: | :-----------: | :-----: | :-----: | :----------: |
+| [WebSocket](/packages/websocket) | ws://, wss:// | ✔ | ✔ | ✔ |
+| [TCP](/packages/tcp) | xmpp:// | ✔ | ✗ | ✗ |
+| [TLS](/packages/tls) | xmpps:// | ✔ | ✗ | ✗ |
+
+## Authentication methods
+
+Multiple authentication mechanisms are supported.
+PLAIN should only be used over secure WebSocket (`wss://)`, direct TLS (`xmpps:`) or a TCP (`xmpp:`) connection upgraded to TLS via [STARTTLS](/starttls)
+
+| SASL | Node.js | Browser | React Native |
+| :---------------------------------------: | :-----: | :-----: | :----------: |
+| [ANONYMOUS](/packages/sasl-anonymous) | ✔ | ✔ | ✔ |
+| [PLAIN](/packages/sasl-plain) | ✔ | ✔ | ✔ |
+| [SCRAM-SHA-1](/packages/sasl-scram-sha-1) | ✔ | ☐ | ✗ |
+
+- ☐ : Optional
+- ✗ : Unavailable
+- ✔ : Included
diff --git a/packages/client/browser.js b/packages/client/browser.js
new file mode 100644
index 000000000..18d9d1338
--- /dev/null
+++ b/packages/client/browser.js
@@ -0,0 +1,67 @@
+'use strict'
+
+const {xml, jid, Client} = require('@xmpp/client-core')
+const getDomain = require('./lib/getDomain')
+
+const _reconnect = require('@xmpp/reconnect')
+const _websocket = require('@xmpp/websocket')
+const _middleware = require('@xmpp/middleware')
+const _streamFeatures = require('@xmpp/stream-features')
+const _iqCaller = require('@xmpp/iq/caller')
+const _iqCallee = require('@xmpp/iq/callee')
+const _resolve = require('@xmpp/resolve')
+
+// Stream features - order matters and define priority
+const _sasl = require('@xmpp/sasl')
+const _resourceBinding = require('@xmpp/resource-binding')
+const _sessionEstablishment = require('@xmpp/session-establishment')
+
+// SASL mechanisms - order matters and define priority
+const anonymous = require('@xmpp/sasl-anonymous')
+const plain = require('@xmpp/sasl-plain')
+
+function client(options = {}) {
+ const {resource, credentials, username, password, ...params} = options
+
+ const {domain, service} = params
+ if (!domain && service) {
+ params.domain = getDomain(service)
+ }
+
+ const entity = new Client(params)
+
+ const reconnect = _reconnect({entity})
+ const websocket = _websocket({entity})
+
+ const middleware = _middleware({entity})
+ const streamFeatures = _streamFeatures({middleware})
+ const iqCaller = _iqCaller({middleware, entity})
+ const iqCallee = _iqCallee({middleware, entity})
+ const resolve = _resolve({entity})
+ // Stream features - order matters and define priority
+ const sasl = _sasl({streamFeatures}, credentials || {username, password})
+ const resourceBinding = _resourceBinding({iqCaller, streamFeatures}, resource)
+ const sessionEstablishment = _sessionEstablishment({iqCaller, streamFeatures})
+ // SASL mechanisms - order matters and define priority
+ const mechanisms = Object.entries({anonymous, plain})
+ .map(([k, v]) => ({[k]: v(sasl)}))
+
+ return Object.assign(entity, {
+ entity,
+ reconnect,
+ websocket,
+ middleware,
+ streamFeatures,
+ iqCaller,
+ iqCallee,
+ resolve,
+ sasl,
+ resourceBinding,
+ sessionEstablishment,
+ mechanisms,
+ })
+}
+
+module.exports.xml = xml
+module.exports.jid = jid
+module.exports.client = client
diff --git a/packages/client/index.js b/packages/client/index.js
index b0e4875ce..2cf0c94ea 100644
--- a/packages/client/index.js
+++ b/packages/client/index.js
@@ -1,6 +1,7 @@
'use strict'
const {xml, jid, Client} = require('@xmpp/client-core')
+const getDomain = require('./lib/getDomain')
const _reconnect = require('@xmpp/reconnect')
const _websocket = require('@xmpp/websocket')
@@ -23,19 +24,6 @@ const anonymous = require('@xmpp/sasl-anonymous')
const scramsha1 = require('@xmpp/sasl-scram-sha-1')
const plain = require('@xmpp/sasl-plain')
-const URL = global.URL || require('url').URL // eslint-disable-line node/no-unsupported-features/node-builtins
-
-function getDomain(service) {
- // WHATWG URL parser requires a protocol
- if (!service.includes('://')) {
- service = 'http://' + service
- }
- const url = new URL(service)
- // WHATWG URL parser doesn't support non Web protocols in browser
- url.protocol = 'http:'
- return url.hostname
-}
-
function client(options = {}) {
const {resource, credentials, username, password, ...params} = options
@@ -48,8 +36,8 @@ function client(options = {}) {
const reconnect = _reconnect({entity})
const websocket = _websocket({entity})
- const tcp = typeof _tcp === 'function' ? _tcp({entity}) : undefined
- const tls = typeof _tls === 'function' ? _tls({entity}) : undefined
+ const tcp = _tcp({entity})
+ const tls = _tls({entity})
const middleware = _middleware({entity})
const streamFeatures = _streamFeatures({middleware})
@@ -57,15 +45,12 @@ function client(options = {}) {
const iqCallee = _iqCallee({middleware, entity})
const resolve = _resolve({entity})
// Stream features - order matters and define priority
- const starttls =
- typeof _starttls === 'function' ? _starttls({streamFeatures}) : undefined
+ const starttls = _starttls({streamFeatures})
const sasl = _sasl({streamFeatures}, credentials || {username, password})
const resourceBinding = _resourceBinding({iqCaller, streamFeatures}, resource)
const sessionEstablishment = _sessionEstablishment({iqCaller, streamFeatures})
// SASL mechanisms - order matters and define priority
const mechanisms = Object.entries({anonymous, scramsha1, plain})
- // Ignore browserify stubs
- .filter(([, v]) => typeof v === 'function')
.map(([k, v]) => ({[k]: v(sasl)}))
return Object.assign(entity, {
@@ -90,4 +75,3 @@ function client(options = {}) {
module.exports.xml = xml
module.exports.jid = jid
module.exports.client = client
-module.exports.getDomain = getDomain
diff --git a/packages/client/lib/getDomain.js b/packages/client/lib/getDomain.js
new file mode 100644
index 000000000..c41c1a4a0
--- /dev/null
+++ b/packages/client/lib/getDomain.js
@@ -0,0 +1,6 @@
+'use strict'
+
+module.exports = function getDomain(service) {
+ const domain = service.split('://')[1] || service
+ return domain.split(':')[0].split('/')[0]
+}
diff --git a/packages/client/package.json b/packages/client/package.json
index 092cf6409..bd93e96c7 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -23,13 +23,8 @@
"@xmpp/tls": "^0.5.0",
"@xmpp/websocket": "^0.5.0"
},
- "browser": {
- "url": false,
- "@xmpp/tcp": false,
- "@xmpp/tls": false,
- "@xmpp/starttls": false,
- "@xmpp/sasl-scram-sha-1": false
- },
+ "browser": "./browser.js",
+ "react-native": "./browser.js",
"engines": {
"node": ">= 10.0.0",
"yarn": ">= 1.0.0"
diff --git a/packages/client/test/getDomain.js b/packages/client/test/getDomain.js
index 939b7d471..b0cff01f7 100644
--- a/packages/client/test/getDomain.js
+++ b/packages/client/test/getDomain.js
@@ -1,7 +1,7 @@
'use strict'
const test = require('ava')
-const {getDomain} = require('..')
+const getDomain = require('../lib/getDomain')
test('getDomain', t => {
t.is(getDomain('ws://foo:123/foobar'), 'foo')
diff --git a/packages/events/package.json b/packages/events/package.json
index 51b1feda5..05f9bf32e 100644
--- a/packages/events/package.json
+++ b/packages/events/package.json
@@ -18,5 +18,8 @@
},
"publishConfig": {
"access": "public"
+ },
+ "dependencies": {
+ "events": "^3.0.0"
}
}
diff --git a/packages/reconnect/index.js b/packages/reconnect/index.js
index 4db2a38ea..a533b19c5 100644
--- a/packages/reconnect/index.js
+++ b/packages/reconnect/index.js
@@ -1,6 +1,6 @@
'use strict'
-const EventEmitter = require('events')
+const {EventEmitter} = require('@xmpp/events')
class Reconnect extends EventEmitter {
constructor(entity) {
diff --git a/packages/reconnect/package.json b/packages/reconnect/package.json
index 529305639..e6ac17419 100644
--- a/packages/reconnect/package.json
+++ b/packages/reconnect/package.json
@@ -10,6 +10,9 @@
"XMPP",
"reconnect"
],
+ "dependencies": {
+ "@xmpp/events": "^0.5.0"
+ },
"engines": {
"node": ">= 10.0.0",
"yarn": ">= 1.0.0"
diff --git a/packages/sasl/lib/b64.js b/packages/sasl/lib/b64.js
index 00ebac742..8bc911e83 100644
--- a/packages/sasl/lib/b64.js
+++ b/packages/sasl/lib/b64.js
@@ -1,15 +1,27 @@
'use strict'
+const {Base64} = require('js-base64')
+
module.exports.encode = function encode(string) {
- if (!global.Buffer) {
+ if (global.btoa) {
return global.btoa(string)
}
- return Buffer.from(string, 'utf8').toString('base64')
+
+ if (global.Buffer) {
+ return Buffer.from(string, 'utf8').toString('base64')
+ }
+
+ return Base64.btoa(string)
}
module.exports.decode = function decode(string) {
- if (!global.Buffer) {
+ if (global.atob) {
return global.atob(string)
}
- return Buffer.from(string, 'base64').toString('utf8')
+
+ if (global.Buffer) {
+ return Buffer.from(string, 'base64').toString('utf8')
+ }
+
+ return Base64.btoa(string)
}
diff --git a/packages/sasl/package.json b/packages/sasl/package.json
index 49b8da91d..70098dc3d 100644
--- a/packages/sasl/package.json
+++ b/packages/sasl/package.json
@@ -13,10 +13,12 @@
"dependencies": {
"@xmpp/error": "^0.5.0",
"@xmpp/xml": "^0.5.0",
+ "js-base64": "^2.4.9",
"saslmechanisms": "^0.1.1"
},
"browser": {
- "buffer": false
+ "buffer": false,
+ "js-base64": false
},
"engines": {
"node": ">= 10.0.0",
diff --git a/packages/starttls/README.md b/packages/starttls/README.md
index 00add7e84..e4bea4bc6 100644
--- a/packages/starttls/README.md
+++ b/packages/starttls/README.md
@@ -4,7 +4,7 @@ STARTTLS negotiation for `@xmpp/client`.
Included and enabled in `@xmpp/client` for Node.js
-STARTTLS will automatically be negotiated upon TCP connection.
+STARTTLS will automatically upgrade the TCP connection to TLS upon connecton if the server supports it.
## References
diff --git a/packages/tcp/README.md b/packages/tcp/README.md
new file mode 100644
index 000000000..17dae0459
--- /dev/null
+++ b/packages/tcp/README.md
@@ -0,0 +1,5 @@
+# TCP
+
+TCP transport for `@xmpp/client`.
+
+Included and enabled in `@xmpp/client` for Node.js.
diff --git a/packages/tls/README.md b/packages/tls/README.md
new file mode 100644
index 000000000..5063b6abc
--- /dev/null
+++ b/packages/tls/README.md
@@ -0,0 +1,5 @@
+# TLS
+
+TLS transport for `@xmpp/client`.
+
+Included and enabled in `@xmpp/client` for Node.js.
diff --git a/packages/websocket/README.md b/packages/websocket/README.md
new file mode 100644
index 000000000..42842ee96
--- /dev/null
+++ b/packages/websocket/README.md
@@ -0,0 +1,5 @@
+# WebSocket
+
+WebSocket transport for `@xmpp/client`.
+
+Included and enabled in `@xmpp/client`.
diff --git a/yarn.lock b/yarn.lock
index c1f22e6b1..635d0c13a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3675,6 +3675,11 @@ events@^2.0.0:
resolved "https://registry.yarnpkg.com/events/-/events-2.1.0.tgz#2a9a1e18e6106e0e812aa9ebd4a819b3c29c0ba5"
integrity sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg==
+events@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88"
+ integrity sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==
+
evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02"
@@ -5006,6 +5011,11 @@ isstream@~0.1.2:
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
+js-base64@^2.4.9:
+ version "2.4.9"
+ resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.9.tgz#748911fb04f48a60c4771b375cac45a80df11c03"
+ integrity sha512-xcinL3AuDJk7VSzsHgb9DvvIXayBbadtMZ4HFPx8rUszbW1MuNMlwYVC4zzCZ6e1sqZpnNS5ZFYOhXqA39T7LQ==
+
js-levenshtein@^1.1.3:
version "1.1.4"
resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.4.tgz#3a56e3cbf589ca0081eb22cd9ba0b1290a16d26e"