function buildReply({stanza}) { return xml('iq', { to: stanza.attrs.from, from: stanza.attrs.to, id: stanza.attrs.id, }) }
const handler = element => { if (element.attrs.xmlns !== NS) { return } if (element.name === 'challenge') { mech.challenge(decode(element.text())) const resp = mech.response(creds) this.entity.send( xml( 'response', {xmlns: NS, mechanism: mech.name}, typeof resp === 'string' ? encode(resp) : '' ) ) return } if (element.name === 'failure') { reject( new SASLError( element.children[0].name, element.getChildText('text') || '', element ) ) } else if (element.name === 'success') { resolve() this.entity._status('authenticated') } this.entity.removeListener('nonza', handler) }
async function bind(entity, iqCaller, resource) { const result = await iqCaller.request( xml('iq', {type: 'set'}, makeBindElement(resource)) ) const jid = result.getChild('bind', NS).getChildText('jid') entity._jid(jid) return jid }
// https://xmpp.org/extensions/xep-0114.html#example-3 async authenticate(id, password) { const hash = crypto.createHash('sha1') hash.update(id + password, 'binary') const el = await this.sendReceive(xml('handshake', {}, hash.digest('hex'))) if (el.name !== 'handshake') { throw new Error('Unexpected server response') } this._jid(this.options.domain) this._status('online', this.jid) }
test.cb('resolves', t => { t.plan(2) const conn = new Connection() conn.parser = new EventEmitter() conn.footerElement = () => { return xml('hello') } conn.socket = new EventEmitter() conn.socket.write = (data, cb) => { return cb() } conn.on('output', el => { t.is(el, '<hello/>') }) conn.close().then(el => { t.is(el.toString(), `<goodbye/>`) t.end() }) conn.parser.emit('end', xml('goodbye')) })
module.exports = function (p) { const entity = client() entity.socket = { write(data, cb) { cb() }, } const plugin = entity.plugin(p) return { entity, plugin, fake(...args) { const el = xml(...args) const p = entity.promise('send') entity.emit('element', el) return p.then(el => { delete el.attrs.xmlns return el }) }, } }
conn.footerElement = () => { return xml('hello') }
function makeBindElement(resource) { return xml('bind', {xmlns: NS}, resource && xml('resource', {}, resource)) }
module.exports = function context(entity = client()) { debug(entity) entity.socket = mockSocket() entity.jid = new JID('foo@bar/test') const ctx = { entity, sanitize(s) { const stanza = s.clone() const {id} = stanza.attrs delete stanza.attrs.id delete stanza.attrs.xmlns return {stanza, id} }, catch() { return promise(entity, 'send').then(s => this.sanitize(s)) }, catchOutgoing(match = () => true) { return new Promise(resolve => { function onSend(stanza) { if (match(stanza)) { entity.removeListener('send', onSend) resolve(stanza) } } entity.on('send', onSend) }) }, catchOutgoingIq(match = () => true) { return this.catchOutgoing(stanza => stanza.is('iq') && match(stanza)) }, async catchOutgoingGet(match = () => true) { const stanza = await this.catchOutgoingIq( stanza => stanza.attrs.type === 'get' && match(stanza) ) const [child] = stanza.children if (child) { child.parent = null } return child }, async catchOutgoingSet(match = () => true) { const stanza = await this.catchOutgoingIq( stanza => stanza.attrs.type === 'set' && match(stanza) ) const [child] = stanza.children if (child) { child.parent = null } return child }, scheduleIncomingResult(child) { return promise(entity, 'send').then(stanza => { const {id} = stanza.attrs return this.fakeIncomingResult(child, id) }) }, scheduleIncomingError(child) { return promise(entity, 'send').then(stanza => { const {id} = stanza.attrs return this.fakeIncomingError(child, id) }) }, fakeIncomingGet(child, attrs = {}) { attrs.type = 'get' return this.fakeIncomingIq(xml('iq', attrs, child)).then(stanza => { const [child] = stanza.children if (child) { child.parent = null } return child }) }, fakeIncomingSet(child, attrs = {}) { attrs.type = 'set' return this.fakeIncomingIq(xml('iq', attrs, child)).then(stanza => { const [child] = stanza.children if (child) { child.parent = null } return child }) }, fakeIncomingResult(child, id) { return this.fakeIncomingIq(xml('iq', {type: 'result', id}, child)).then( stanza => { const [child] = stanza.children if (child) { child.parent = null } return child } ) }, fakeIncomingError(child, id) { return this.fakeIncomingIq(xml('iq', {type: 'error', id}, child)) .then() .then(stanza => { const [child] = stanza.children if (child) { child.parent = null } return child }) }, fakeIncomingIq(el) { const stanza = el.clone() if (stanza.is('iq') && !stanza.attrs.id) { stanza.attrs.id = 'fake' } return this.fakeIncoming(stanza) }, async fakeIncoming(el) { const p = promise(entity, 'send') const stanza = el.clone() delete stanza.attrs.xmlns await Promise.resolve() this.mockInput(el) await p return this.sanitize(el).stanza }, fakeOutgoing(el) { entity.hookOutgoing(el) }, mockInput(el) { entity.emit('input', el.toString()) entity._onElement(el) }, } return ctx }
function buildError(type, condition) { return xml('error', {type}, xml(condition, NS_STANZA)) }
test('isNonza()', t => { const conn = new Connection() conn.NS = 'bar' t.is(conn.isNonza(xml('foo')), true) t.is(conn.isNonza(xml('foo', {xmlns: 'bar'})), true) t.is(conn.isNonza(xml('presence', {xmlns: 'foo'})), true) t.is(conn.isNonza(xml('iq', {xmlns: 'foo'})), true) t.is(conn.isNonza(xml('message', {xmlns: 'foo'})), true) t.is(conn.isNonza(xml('presence')), false) t.is(conn.isNonza(xml('iq')), false) t.is(conn.isNonza(xml('message')), false) t.is(conn.isNonza(xml('presence', {xmlns: 'bar'})), false) t.is(conn.isNonza(xml('iq', {xmlns: 'bar'})), false) t.is(conn.isNonza(xml('message', {xmlns: 'bar'})), false) // Conn.online = false // // t.is(conn.isNonza(xml`<presence/>`), true) // t.is(conn.isNonza(xml`<iq/>`), true) // t.is(conn.isNonza(xml`<message/>`), true) // t.is(conn.isNonza(xml`<presence xmlns='bar'/>`), true) // t.is(conn.isNonza(xml`<iq xmlns='bar'/>`), true) // t.is(conn.isNonza(xml`<message xmlns='bar'/>`), true) })