modified lib-jitsi-meet dev repo
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Caps.js 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. /* global $, b64_sha1, Strophe */
  2. import XMPPEvents from "../../service/xmpp/XMPPEvents";
  3. /**
  4. * The property
  5. */
  6. const IDENTITY_PROPERTIES = ["category", "type", "lang", "name"];
  7. const IDENTITY_PROPERTIES_FOR_COMPARE = ["category", "type", "lang"];
  8. const HASH = "sha-1";
  9. function compareIdentities(a, b) {
  10. let res = 0;
  11. IDENTITY_PROPERTIES_FOR_COMPARE.some(key =>
  12. (res = ((a[key] > b[key]) && 1) || ((a[key] < b[key]) && -1)) !== 0
  13. );
  14. return res;
  15. }
  16. /**
  17. * Implements xep-0115 ( http://xmpp.org/extensions/xep-0115.html )
  18. */
  19. export default class Caps {
  20. /**
  21. * Constructs new Caps instance.
  22. * @param {Strophe.Connection} connection the strophe connection object
  23. * @param {String} node the value of the node attribute of the "c" xml node
  24. * that will be sent to the other participants
  25. */
  26. constructor(connection = {}, node = "http://jitsi.org/jitsimeet") {
  27. this.node = node;
  28. this.disco = connection.disco;
  29. if(!this.disco) {
  30. throw new Error(
  31. "Missing strophe-plugins "
  32. + "(disco and caps plugins are required)!");
  33. }
  34. this.versionToCapabilities = Object.create(null);
  35. this.jidToVersion = Object.create(null);
  36. this.version = "";
  37. this.rooms = new Set();
  38. const emuc = connection.emuc;
  39. emuc.addListener(XMPPEvents.EMUC_ROOM_ADDED,
  40. room => this._addChatRoom(room));
  41. emuc.addListener(XMPPEvents.EMUC_ROOM_REMOVED,
  42. room => this._removeChatRoom(room));
  43. for(let jid in emuc.rooms) {
  44. this._addChatRoom(this.emuc.rooms[jid]);
  45. }
  46. Strophe.addNamespace("CAPS", "http://jabber.org/protocol/caps");
  47. this.disco.addFeature(Strophe.NS.CAPS);
  48. connection.addHandler(this._handleCaps.bind(this), Strophe.NS.CAPS);
  49. this._onMucMemberLeft = this._removeJidToVersionEntry.bind(this);
  50. }
  51. /**
  52. * Adds new feature to the list of supported features for the local
  53. * participant
  54. * @param {String} feature the name of the feature.
  55. * @param {boolean} submit if true - new presence with updated "c" node
  56. * will be sent.
  57. */
  58. addFeature(feature, submit = false) {
  59. this.disco.addFeature(feature);
  60. this._generateVersion();
  61. if(submit) {
  62. this.submit();
  63. }
  64. }
  65. /**
  66. * Removes a feature from the list of supported features for the local
  67. * participant
  68. * @param {String} feature the name of the feature.
  69. * @param {boolean} submit if true - new presence with updated "c" node
  70. * will be sent.
  71. */
  72. removeFeature(feature, submit = false) {
  73. this.disco.removeFeature(feature);
  74. this._generateVersion();
  75. if(submit) {
  76. this.submit();
  77. }
  78. }
  79. /**
  80. * Sends new presence stanza for every room from the list of rooms.
  81. */
  82. submit() {
  83. this.rooms.forEach(room => room.sendPresence());
  84. }
  85. /**
  86. * Returns a set with the features for a participant.
  87. * @param {String} jid the jid of the participant
  88. * @param {int} timeout the timeout in ms for reply from the participant.
  89. * @returns {Promise<Set<String>, Error>}
  90. */
  91. getFeatures(jid, timeout = 5000) {
  92. let user
  93. = (jid in this.jidToVersion) ? this.jidToVersion[jid] : null;
  94. if(!user || !(user.version in this.versionToCapabilities))
  95. {
  96. const node = (user)? user.node + "#" + user.version : null;
  97. return new Promise ( (resolve, reject) =>
  98. this.disco.info(jid, node, response => {
  99. const features = new Set();
  100. $(response).find(">query>feature").each((idx, el) =>
  101. features.add(el.getAttribute("var")));
  102. if(user) {
  103. this.versionToCapabilities[user.version]
  104. = features;
  105. }
  106. resolve(features);
  107. }, reject , timeout)
  108. );
  109. }
  110. return Promise.resolve(this.versionToCapabilities[user.version]);
  111. }
  112. /**
  113. * Adds ChatRoom instance to the list of rooms. Adds listeners to the room
  114. * and adds "c" element to the presences of the room.
  115. * @param {ChatRoom} room the room.
  116. */
  117. _addChatRoom(room) {
  118. this.rooms.add(room);
  119. room.addListener(XMPPEvents.MUC_MEMBER_LEFT, this._onMucMemberLeft);
  120. this._fixChatRoomPresenceMap(room);
  121. }
  122. /**
  123. * Removes ChatRoom instance from the list of rooms. Removes listeners
  124. * added from the Caps class.
  125. * @param {ChatRoom} room the room.
  126. */
  127. _removeChatRoom(room) {
  128. this.rooms.delete(room);
  129. room.removeListener(XMPPEvents.MUC_MEMBER_LEFT, this._onMucMemberLeft);
  130. }
  131. /**
  132. * Creates/updates the "c" xml node into the presence of the passed room.
  133. * @param {ChatRoom} room the room.
  134. */
  135. _fixChatRoomPresenceMap(room) {
  136. room.addToPresence("c", {
  137. attributes: {
  138. xmlns: Strophe.NS.CAPS,
  139. hash: HASH,
  140. node: this.node,
  141. ver: this.version
  142. }
  143. });
  144. }
  145. /**
  146. * Handles this.version changes.
  147. */
  148. _notifyVersionChanged() {
  149. //update the version for all rooms
  150. this.rooms.forEach(room => this._fixChatRoomPresenceMap(room));
  151. this.submit();
  152. }
  153. /**
  154. * Generates the value for the "ver" attribute.
  155. */
  156. _generateVersion() {
  157. const identities = this.disco._identities.sort(compareIdentities);
  158. const features = this.disco._features.sort();
  159. this.version = b64_sha1(
  160. identities.reduce(
  161. (accumulatedValue, identity) => {
  162. return IDENTITY_PROPERTIES.reduce((tmp, key, idx) => {
  163. return (idx === 0 ? "" : "/") + identity[key];
  164. }, "") + "<";
  165. }, ""
  166. ) + features.reduce((tmp, feature) => feature + "<", "")
  167. );
  168. this._notifyVersionChanged();
  169. }
  170. /**
  171. * Parses the "c" xml node from presence.
  172. * @param {DOMElement} stanza the presence packet
  173. */
  174. _handleCaps(stanza) {
  175. const from = stanza.getAttribute("from");
  176. const caps = stanza.querySelector("c");
  177. const version = caps.getAttribute("ver");
  178. const node = caps.getAttribute("node");
  179. this.jidToVersion[from] = {version, node};
  180. // return true to not remove the handler from Strophe
  181. return true;
  182. }
  183. /**
  184. * Removes entry from this.jidToVersion map.
  185. * @param {String} jid the jid to be removed.
  186. */
  187. _removeJidToVersionEntry(jid) {
  188. if(jid in this.jidToVersion) {
  189. delete this.jidToVersion[jid];
  190. }
  191. }
  192. }