UNPKG

@jsxc/jsxc

Version:

Real-time XMPP chat application with video calls, file transfer and encrypted communication

190 lines (153 loc) 6.59 kB
import Account from './Account'; import DiscoInfo from './DiscoInfo'; import PersistentMap from './util/PersistentMap'; import JID from './JID'; import Contact from './Contact'; import Log from '@util/Log'; import Client from './Client'; import Form from './connection/Form'; import { IDiscoInfoRepository } from './DiscoInfoRepository.interface'; import { IJID } from './JID.interface'; export default class implements IDiscoInfoRepository { private jidVersionMap: PersistentMap; constructor(private account: Account) { this.jidVersionMap = new PersistentMap(Client.getStorage(), 'capabilities'); } public addRelation(jid: IJID, version: string): void; public addRelation(jid: IJID, discoInfo: DiscoInfo): void; public addRelation(jid: IJID, value: string | DiscoInfo) { if (value instanceof DiscoInfo) { this.jidVersionMap.set(jid.full, value.getCapsVersion()); } else if (typeof value === 'string') { this.jidVersionMap.set(jid.full, value); } } public getDiscoInfo(jid: IJID) { let version = this.jidVersionMap.get(jid.full); if (!version) { throw new Error('Found no disco version'); } return new DiscoInfo(version); } public getCapableResources(contact: Contact, features: string[]): Promise<string[]>; public getCapableResources(contact: Contact, features: string): Promise<string[]>; public getCapableResources(contact: Contact, features): Promise<string[]> { let resources = contact.getResources(); if (!features) { return Promise.resolve(resources); } let promises = []; for (let resource of resources) { //@REVIEW promises.push( new Promise(resolve => { let jid = new JID(contact.getJid().bare + '/' + resource); this.hasFeature(jid, features).then(hasSupport => { resolve(hasSupport ? resource : undefined); }); //@REVIEW do we need a timer? }) ); } return Promise.all(promises).then(capableResources => { return capableResources.filter(resource => typeof resource === 'string'); }); } public hasFeature(jid: IJID, features: string[]): Promise<boolean>; public hasFeature(jid: IJID, feature: string): Promise<boolean>; public hasFeature(discoInfo: DiscoInfo, features: string[]): Promise<boolean>; public hasFeature(discoInfo: DiscoInfo, feature: string): Promise<boolean>; public hasFeature() { let features = arguments[1] instanceof Array ? arguments[1] : [arguments[1]]; let capabilitiesPromise; if (arguments[0] instanceof JID) { let jid: JID = arguments[0]; capabilitiesPromise = this.getCapabilities(jid); } else if (arguments[0] instanceof DiscoInfo) { capabilitiesPromise = Promise.resolve(arguments[0]); } else if (typeof arguments[0] === 'undefined') { let serverJid = this.account.getConnection().getServerJID(); capabilitiesPromise = this.getCapabilities(serverJid); } else { return Promise.reject('Wrong parameters'); } return capabilitiesPromise.then((capabilities: DiscoInfo) => { return capabilities.hasFeature(features); }); } public getCapabilities(jid: IJID): Promise<DiscoInfo | void> { let jidVersionMap = this.jidVersionMap; let version = jidVersionMap.get(jid.full); if (!version || !DiscoInfo.exists(version)) { return this.requestDiscoInfo(jid).then(discoInfo => { if (version && version !== discoInfo.getCapsVersion()) { Log.warn( `Caps version doesn't match for ${ jid.full }. Expected: ${version}. Actual: ${discoInfo.getCapsVersion()}.` ); } else if (!version) { this.addRelation(jid, discoInfo); } return discoInfo; }); } return new Promise<DiscoInfo>(resolve => { checkCaps(resolve); }); function checkCaps(cb) { let version = jidVersionMap.get(jid.full); if (version && DiscoInfo.exists(version)) { cb(new DiscoInfo(version)); } else { setTimeout(() => { checkCaps(cb); }, 200); } } } public requestDiscoInfo(jid: IJID, node?: string) { let connection = this.account.getConnection(); //@REVIEW why does the request fail if we send a node attribute? return connection.getDiscoService().getDiscoInfo(jid).then(this.processDiscoInfo); } private processDiscoInfo(stanza: Element) { let queryElement = $(stanza).find('query'); // let node = queryElement.attr('node') || ''; // let from = new JID($(stanza).attr('from')); //@TODO verify response is valid: https://xmpp.org/extensions/xep-0115.html#ver-proc let capabilities: { [name: string]: any } = {}; for (let childNode of Array.from(queryElement.get(0).childNodes)) { let nodeName = childNode.nodeName.toLowerCase(); if (typeof capabilities[nodeName] === 'undefined') { capabilities[nodeName] = []; } if (nodeName === 'feature') { capabilities[nodeName].push($(childNode).attr('var')); } else if (nodeName === 'identity') { capabilities[nodeName].push({ category: $(childNode).attr('category') || '', type: $(childNode).attr('type') || '', name: $(childNode).attr('name') || '', lang: $(childNode).attr('xml:lang') || '', }); //@TODO test required arguments } //@TODO handle extended information } if (typeof capabilities.identity === 'undefined' || capabilities.identity.length === 0) { return Promise.reject('Disco info response is invalid. Missing identity.'); } let forms = queryElement .find('x[xmlns="jabber:x:data"]') .get() .map(element => { return Form.fromXML(element); }); // if (typeof capabilities['feature'] === 'undefined' || capabilities['feature'].indexOf('http://jabber.org/protocol/disco#info') < 0) { // return Promise.reject('Disco info response is unvalid. Doesnt support disco.'); // } let discoInfo = new DiscoInfo(capabilities.identity, capabilities.feature, forms); return Promise.resolve(discoInfo); } }