Skip to content

Commit

Permalink
stream-management: add @xmpp/stream-management package
Browse files Browse the repository at this point in the history
  • Loading branch information
sonnyp committed Jan 20, 2020
1 parent 54829ae commit cc0aa9d
Show file tree
Hide file tree
Showing 16 changed files with 920 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ server/localhost/
server/prosody.err
server/prosody.log
server/prosody.pid
server/prosody-modules/

!.gitkeep
!.editorconfig
Expand Down
7 changes: 7 additions & 0 deletions packages/client/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const _resolve = require('@xmpp/resolve')
const _sasl = require('@xmpp/sasl')
const _resourceBinding = require('@xmpp/resource-binding')
const _sessionEstablishment = require('@xmpp/session-establishment')
const _streamManagement = require('@xmpp/stream-management')

// SASL mechanisms - order matters and define priority
const anonymous = require('@xmpp/sasl-anonymous')
Expand All @@ -40,6 +41,11 @@ function client(options = {}) {
const resolve = _resolve({entity})
// Stream features - order matters and define priority
const sasl = _sasl({streamFeatures}, credentials || {username, password})
const streamManagement = _streamManagement({
streamFeatures,
entity,
middleware,
})
const resourceBinding = _resourceBinding({iqCaller, streamFeatures}, resource)
const sessionEstablishment = _sessionEstablishment({iqCaller, streamFeatures})
// SASL mechanisms - order matters and define priority
Expand All @@ -59,6 +65,7 @@ function client(options = {}) {
sasl,
resourceBinding,
sessionEstablishment,
streamManagement,
mechanisms,
})
}
Expand Down
7 changes: 7 additions & 0 deletions packages/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const _starttls = require('@xmpp/starttls/client')
const _sasl = require('@xmpp/sasl')
const _resourceBinding = require('@xmpp/resource-binding')
const _sessionEstablishment = require('@xmpp/session-establishment')
const _streamManagement = require('@xmpp/stream-management')

// SASL mechanisms - order matters and define priority
const scramsha1 = require('@xmpp/sasl-scram-sha-1')
Expand Down Expand Up @@ -47,6 +48,11 @@ function client(options = {}) {
// Stream features - order matters and define priority
const starttls = _starttls({streamFeatures})
const sasl = _sasl({streamFeatures}, credentials || {username, password})
const streamManagement = _streamManagement({
streamFeatures,
entity,
middleware,
})
const resourceBinding = _resourceBinding({iqCaller, streamFeatures}, resource)
const sessionEstablishment = _sessionEstablishment({iqCaller, streamFeatures})
// SASL mechanisms - order matters and define priority
Expand All @@ -71,6 +77,7 @@ function client(options = {}) {
sasl,
resourceBinding,
sessionEstablishment,
streamManagement,
mechanisms,
})
}
Expand Down
1 change: 1 addition & 0 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@xmpp/session-establishment": "^0.9.2",
"@xmpp/starttls": "^0.9.2",
"@xmpp/stream-features": "^0.9.0",
"@xmpp/stream-management": "^0.9.0",
"@xmpp/tcp": "^0.9.2",
"@xmpp/tls": "^0.9.2",
"@xmpp/websocket": "^0.9.2"
Expand Down
7 changes: 7 additions & 0 deletions packages/client/react-native.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const _resolve = require('@xmpp/resolve')
const _sasl = require('@xmpp/sasl')
const _resourceBinding = require('@xmpp/resource-binding')
const _sessionEstablishment = require('@xmpp/session-establishment')
const _streamManagement = require('@xmpp/stream-management')

// SASL mechanisms - order matters and define priority
const anonymous = require('@xmpp/sasl-anonymous')
Expand All @@ -40,6 +41,11 @@ function client(options = {}) {
const resolve = _resolve({entity})
// Stream features - order matters and define priority
const sasl = _sasl({streamFeatures}, credentials || {username, password})
const streamManagement = _streamManagement({
streamFeatures,
entity,
middleware,
})
const resourceBinding = _resourceBinding({iqCaller, streamFeatures}, resource)
const sessionEstablishment = _sessionEstablishment({iqCaller, streamFeatures})
// SASL mechanisms - order matters and define priority
Expand All @@ -59,6 +65,7 @@ function client(options = {}) {
sasl,
resourceBinding,
sessionEstablishment,
streamManagement,
mechanisms,
})
}
Expand Down
4 changes: 2 additions & 2 deletions packages/stream-features/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module.exports = function route() {
if (!stanza.is('features', 'http://etherx.jabber.org/streams'))
return next()

await next()
if (entity.jid) entity._status('online', entity.jid)
const prevent = await next()
if (!prevent && entity.jid) entity._status('online', entity.jid)
}
}
11 changes: 11 additions & 0 deletions packages/stream-management/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# stream-management

[Stream Management](https://xmpp.org/extensions/xep-0198.html) for `@xmpp/client`.

Included and enabled in `@xmpp/client`.

Supports Node.js and browsers.

Responds to ack requests and resumes connection uppon disconnect whenever possible.

Does not support requesting acks yet.
108 changes: 108 additions & 0 deletions packages/stream-management/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
'use strict'

const xml = require('@xmpp/xml')
const StanzaError = require('@xmpp/middleware/lib/StanzaError')

// https://xmpp.org/extensions/xep-0198.html

const NS = 'urn:xmpp:sm:3'

async function enable(entity, resume, max) {
const response = await entity.sendReceive(
xml('enable', {xmlns: NS, max, resume: resume ? 'true' : undefined})
)

if (!response.is('enabled')) {
throw StanzaError.fromElement(response)
}

return response
}

async function resume(entity, h, previd) {
const response = await entity.sendReceive(
xml('resume', {xmlns: NS, h, previd})
)

if (!response.is('resumed')) {
throw StanzaError.fromElement(response)
}

return response
}

module.exports = function({streamFeatures, entity, middleware}) {
let address = null

const sm = {
allowResume: true,
preferredMaximum: null,
enabled: false,
id: '',
outbound: 0,
inbound: 0,
max: null,
}

entity.on('online', jid => {
address = jid
sm.outbound = 0
sm.inbound = 0
sm.enabled = false
})

entity.on('offline', () => {
address = null
sm.outbound = 0
sm.inbound = 0
sm.enabled = false
})

middleware.use((context, next) => {
const {stanza} = context
if (['presence', 'message', 'iq'].includes(stanza.name)) {
sm.inbound += 1
} else if (stanza.is('r', NS)) {
// > When an <r/> element ("request") is received, the recipient MUST acknowledge it by sending an <a/> element to the sender containing a value of 'h' that is equal to the number of stanzas handled by the recipient of the <r/> element.
entity.send(xml('a', {xmlns: NS, h: sm.inbound})).catch(() => {})
} else if (stanza.is('a', NS)) {
// > When a party receives an <a/> element, it SHOULD keep a record of the 'h' value returned as the sequence number of the last handled outbound stanza for the current stream (and discard the previous value).
sm.outbound = stanza.attrs.h
}

return next()
})

streamFeatures.use('sm', NS, async (context, next) => {
// Resuming
if (sm.id && address) {
try {
await resume(entity, sm.inbound, sm.id)
entity.jid = address
entity.status = 'online'
return true
// eslint-disable-next-line no-unused-vars
} catch (err) {
sm.id = ''
address = null
sm.enabled = false
}
}

// Enabling

// Resource binding first
await next()

const promiseEnable = enable(entity, sm.allowResume, sm.preferredMaximum)

// > The counter for an entity's own sent stanzas is set to zero and started after sending either <enable/> or <enabled/>.
sm.outbound = 0

const response = await promiseEnable
sm.inbound = 0
sm.enabled = true
sm.id = response.attrs.id
sm.max = response.attrs.max
})
}
25 changes: 25 additions & 0 deletions packages/stream-management/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@xmpp/stream-management",
"description": "XMPP stream management for JavaScript",
"repository": "github:xmppjs/xmpp.js",
"homepage": "https://github.com/xmppjs/xmpp.js/tree/master/packages/stream-management",
"bugs": "http://github.com/xmppjs/xmpp.js/issues",
"version": "0.9.0",
"license": "ISC",
"keywords": [
"XMPP",
"stream",
"management"
],
"dependencies": {
"@xmpp/middleware": "^0.9.0",
"@xmpp/xml": "^0.9.0"
},
"engines": {
"node": ">= 10.0.0",
"yarn": ">= 1.0.0"
},
"publishConfig": {
"access": "public"
}
}
43 changes: 43 additions & 0 deletions packages/stream-management/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use strict'

const test = require('ava')
const {mockClient, promise, timeout} = require('@xmpp/test')

test('mandatory', async t => {
const {entity} = mockClient()

entity.mockInput(
<features xmlns="http://etherx.jabber.org/streams">
<session xmlns="urn:ietf:params:xml:ns:xmpp-session" />
</features>
)

entity.scheduleIncomingResult()

await entity.catchOutgoingSet().then(child => {
t.deepEqual(child, <session xmlns="urn:ietf:params:xml:ns:xmpp-session" />)
return child
})

await promise(entity, 'online')
})

test('optional', async t => {
const {entity} = mockClient()

entity.mockInput(
<features xmlns="http://etherx.jabber.org/streams">
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
<optional />
</session>
</features>
)

const promiseSend = promise(entity, 'send')

await promise(entity, 'online')

await timeout(promiseSend, 0).catch(err => {
t.is(err.name, 'TimeoutError')
})
})
1 change: 1 addition & 0 deletions packages/time/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ time.datetime(new Date('05 October 2011 14:48 UTC')) // '2011-10-05T14:48:00.000

- [XEP-0082: XMPP Date and Time Profiles](https://xmpp.org/extensions/xep-0082.html)
- [XEP-0202: Entity Time](https://xmpp.org/extensions/xep-0202.html)
- [XEP-0203: Delayed Delivery](https://xmpp.org/extensions/xep-0203.html)
1 change: 1 addition & 0 deletions packages/xmpp.js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@xmpp/session-establishment": "^0.9.2",
"@xmpp/starttls": "^0.9.2",
"@xmpp/stream-features": "^0.9.0",
"@xmpp/stream-management": "^0.9.0",
"@xmpp/tcp": "^0.9.2",
"@xmpp/test": "^0.9.2",
"@xmpp/time": "^0.9.0",
Expand Down
4 changes: 4 additions & 0 deletions protocols.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,7 @@ Included in [`@xmpp/component`](packages/component)
See [`@xmpp/time`](packages/time)

---

[XEP-0198](https://xmpp.org/extensions/xep-0198.html): Stream Management

See [`@xmpp/stream-management`](packages/stream-management)
Loading

0 comments on commit cc0aa9d

Please sign in to comment.