mock.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760
  1. const mock = {};
  2. window.mock = mock;
  3. let _converse, initConverse;
  4. const converseLoaded = new Promise(resolve => window.addEventListener('converse-loaded', resolve));
  5. mock.initConverse = function (promise_names=[], settings=null, func) {
  6. if (typeof promise_names === "function") {
  7. func = promise_names;
  8. promise_names = []
  9. settings = null;
  10. }
  11. return async done => {
  12. if (_converse && _converse.api.connection.connected()) {
  13. await _converse.api.user.logout();
  14. }
  15. const el = document.querySelector('#conversejs');
  16. if (el) {
  17. el.parentElement.removeChild(el);
  18. }
  19. document.title = "Converse Tests";
  20. await converseLoaded;
  21. await initConverse(settings);
  22. await Promise.all((promise_names || []).map(_converse.api.waitUntil));
  23. try {
  24. await func(done, _converse);
  25. } catch(e) {
  26. console.error(e);
  27. fail(e);
  28. await done();
  29. }
  30. }
  31. };
  32. window.addEventListener('converse-loaded', () => {
  33. const { _, u, sizzle, Strophe, dayjs, $iq, $msg, $pres } = converse.env;
  34. mock.waitUntilDiscoConfirmed = async function (_converse, entity_jid, identities, features=[], items=[], type='info') {
  35. const iq = await u.waitUntil(() => {
  36. return _.filter(
  37. _converse.connection.IQ_stanzas,
  38. (iq) => sizzle(`iq[to="${entity_jid}"] query[xmlns="http://jabber.org/protocol/disco#${type}"]`, iq).length
  39. ).pop();
  40. }, 300);
  41. const stanza = $iq({
  42. 'type': 'result',
  43. 'from': entity_jid,
  44. 'to': 'romeo@montague.lit/orchard',
  45. 'id': iq.getAttribute('id'),
  46. }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#'+type});
  47. _.forEach(identities, function (identity) {
  48. stanza.c('identity', {'category': identity.category, 'type': identity.type}).up()
  49. });
  50. _.forEach(features, function (feature) {
  51. stanza.c('feature', {'var': feature}).up();
  52. });
  53. _.forEach(items, function (item) {
  54. stanza.c('item', {'jid': item}).up();
  55. });
  56. _converse.connection._dataRecv(mock.createRequest(stanza));
  57. }
  58. mock.createRequest = function (iq) {
  59. iq = typeof iq.tree == "function" ? iq.tree() : iq;
  60. var req = new Strophe.Request(iq, function() {});
  61. req.getResponse = function () {
  62. var env = new Strophe.Builder('env', {type: 'mock'}).tree();
  63. env.appendChild(iq);
  64. return env;
  65. };
  66. return req;
  67. };
  68. mock.closeAllChatBoxes = function (_converse) {
  69. return Promise.all(_converse.chatboxviews.map(view => view.close()));
  70. };
  71. mock.openControlBox = async function (_converse) {
  72. const model = await _converse.api.controlbox.open();
  73. await u.waitUntil(() => model.get('connected'));
  74. var toggle = document.querySelector(".toggle-controlbox");
  75. if (!u.isVisible(document.querySelector("#controlbox"))) {
  76. if (!u.isVisible(toggle)) {
  77. u.removeClass('hidden', toggle);
  78. }
  79. toggle.click();
  80. }
  81. return this;
  82. };
  83. mock.closeControlBox = function () {
  84. const controlbox = document.querySelector("#controlbox");
  85. if (u.isVisible(controlbox)) {
  86. const button = controlbox.querySelector(".close-chatbox-button");
  87. if (!_.isNull(button)) {
  88. button.click();
  89. }
  90. }
  91. return this;
  92. };
  93. mock.waitUntilBookmarksReturned = async function (_converse, bookmarks=[]) {
  94. await mock.waitUntilDiscoConfirmed(
  95. _converse, _converse.bare_jid,
  96. [{'category': 'pubsub', 'type': 'pep'}],
  97. ['http://jabber.org/protocol/pubsub#publish-options']
  98. );
  99. const IQ_stanzas = _converse.connection.IQ_stanzas;
  100. const sent_stanza = await u.waitUntil(
  101. () => IQ_stanzas.filter(s => sizzle('items[node="storage:bookmarks"]', s).length).pop()
  102. );
  103. const stanza = $iq({
  104. 'to': _converse.connection.jid,
  105. 'type':'result',
  106. 'id':sent_stanza.getAttribute('id')
  107. }).c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
  108. .c('items', {'node': 'storage:bookmarks'})
  109. .c('item', {'id': 'current'})
  110. .c('storage', {'xmlns': 'storage:bookmarks'});
  111. bookmarks.forEach(bookmark => {
  112. stanza.c('conference', {
  113. 'name': bookmark.name,
  114. 'autojoin': bookmark.autojoin,
  115. 'jid': bookmark.jid
  116. }).c('nick').t(bookmark.nick).up().up()
  117. });
  118. _converse.connection._dataRecv(mock.createRequest(stanza));
  119. await _converse.api.waitUntil('bookmarksInitialized');
  120. };
  121. mock.openChatBoxes = function (converse, amount) {
  122. for (let i=0; i<amount; i++) {
  123. const jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit';
  124. converse.roster.get(jid).openChat();
  125. }
  126. };
  127. mock.openChatBoxFor = async function (_converse, jid) {
  128. await _converse.api.waitUntil('rosterContactsFetched');
  129. _converse.roster.get(jid).openChat();
  130. return u.waitUntil(() => _converse.chatboxviews.get(jid), 1000);
  131. };
  132. mock.openChatRoomViaModal = async function (_converse, jid, nick='') {
  133. // Opens a new chatroom
  134. const model = await _converse.api.controlbox.open('controlbox');
  135. await u.waitUntil(() => model.get('connected'));
  136. await mock.openControlBox(_converse);
  137. const view = await _converse.chatboxviews.get('controlbox');
  138. const roomspanel = view.roomspanel;
  139. roomspanel.el.querySelector('.show-add-muc-modal').click();
  140. mock.closeControlBox(_converse);
  141. const modal = roomspanel.add_room_modal;
  142. await u.waitUntil(() => u.isVisible(modal.el), 1500)
  143. modal.el.querySelector('input[name="chatroom"]').value = jid;
  144. if (nick) {
  145. modal.el.querySelector('input[name="nickname"]').value = nick;
  146. }
  147. modal.el.querySelector('form input[type="submit"]').click();
  148. await u.waitUntil(() => _converse.chatboxviews.get(jid), 1000);
  149. return _converse.chatboxviews.get(jid);
  150. };
  151. mock.openChatRoom = function (_converse, room, server) {
  152. return _converse.api.rooms.open(`${room}@${server}`);
  153. };
  154. mock.getRoomFeatures = async function (_converse, muc_jid, features=[]) {
  155. const room = Strophe.getNodeFromJid(muc_jid);
  156. muc_jid = muc_jid.toLowerCase();
  157. const stanzas = _converse.connection.IQ_stanzas;
  158. const stanza = await u.waitUntil(() => stanzas.filter(
  159. iq => iq.querySelector(
  160. `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
  161. )).pop()
  162. );
  163. const features_stanza = $iq({
  164. 'from': muc_jid,
  165. 'id': stanza.getAttribute('id'),
  166. 'to': 'romeo@montague.lit/desktop',
  167. 'type': 'result'
  168. }).c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'})
  169. .c('identity', {
  170. 'category': 'conference',
  171. 'name': room[0].toUpperCase() + room.slice(1),
  172. 'type': 'text'
  173. }).up();
  174. features = features.length ? features : mock.default_muc_features;
  175. features.forEach(f => features_stanza.c('feature', {'var': f}).up());
  176. features_stanza.c('x', { 'xmlns':'jabber:x:data', 'type':'result'})
  177. .c('field', {'var':'FORM_TYPE', 'type':'hidden'})
  178. .c('value').t('http://jabber.org/protocol/muc#roominfo').up().up()
  179. .c('field', {'type':'text-single', 'var':'muc#roominfo_description', 'label':'Description'})
  180. .c('value').t('This is the description').up().up()
  181. .c('field', {'type':'text-single', 'var':'muc#roominfo_occupants', 'label':'Number of occupants'})
  182. .c('value').t(0);
  183. _converse.connection._dataRecv(mock.createRequest(features_stanza));
  184. };
  185. mock.waitForReservedNick = async function (_converse, muc_jid, nick) {
  186. const stanzas = _converse.connection.IQ_stanzas;
  187. const selector = `iq[to="${muc_jid.toLowerCase()}"] query[node="x-roomuser-item"]`;
  188. const iq = await u.waitUntil(() => stanzas.filter(s => sizzle(selector, s).length).pop());
  189. // We remove the stanza, otherwise we might get stale stanzas returned in our filter above.
  190. stanzas.splice(stanzas.indexOf(iq), 1)
  191. // The XMPP server returns the reserved nick for this user.
  192. const IQ_id = iq.getAttribute('id');
  193. const stanza = $iq({
  194. 'type': 'result',
  195. 'id': IQ_id,
  196. 'from': muc_jid,
  197. 'to': _converse.connection.jid
  198. }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info', 'node': 'x-roomuser-item'});
  199. if (nick) {
  200. stanza.c('identity', {'category': 'conference', 'name': nick, 'type': 'text'});
  201. }
  202. _converse.connection._dataRecv(mock.createRequest(stanza));
  203. if (nick) {
  204. return u.waitUntil(() => nick);
  205. }
  206. };
  207. mock.returnMemberLists = async function (_converse, muc_jid, members=[], affiliations=['member', 'owner', 'admin']) {
  208. if (affiliations.length === 0) {
  209. return;
  210. }
  211. const stanzas = _converse.connection.IQ_stanzas;
  212. if (affiliations.includes('member')) {
  213. const member_IQ = await u.waitUntil(() => _.filter(
  214. stanzas,
  215. s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="member"]`, s).length
  216. ).pop());
  217. const member_list_stanza = $iq({
  218. 'from': 'coven@chat.shakespeare.lit',
  219. 'id': member_IQ.getAttribute('id'),
  220. 'to': 'romeo@montague.lit/orchard',
  221. 'type': 'result'
  222. }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN});
  223. members.filter(m => m.affiliation === 'member').forEach(m => {
  224. member_list_stanza.c('item', {
  225. 'affiliation': m.affiliation,
  226. 'jid': m.jid,
  227. 'nick': m.nick
  228. });
  229. });
  230. _converse.connection._dataRecv(mock.createRequest(member_list_stanza));
  231. }
  232. if (affiliations.includes('admin')) {
  233. const admin_IQ = await u.waitUntil(() => _.filter(
  234. stanzas,
  235. s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="admin"]`, s).length
  236. ).pop());
  237. const admin_list_stanza = $iq({
  238. 'from': 'coven@chat.shakespeare.lit',
  239. 'id': admin_IQ.getAttribute('id'),
  240. 'to': 'romeo@montague.lit/orchard',
  241. 'type': 'result'
  242. }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN});
  243. members.filter(m => m.affiliation === 'admin').forEach(m => {
  244. admin_list_stanza.c('item', {
  245. 'affiliation': m.affiliation,
  246. 'jid': m.jid,
  247. 'nick': m.nick
  248. });
  249. });
  250. _converse.connection._dataRecv(mock.createRequest(admin_list_stanza));
  251. }
  252. if (affiliations.includes('owner')) {
  253. const owner_IQ = await u.waitUntil(() => _.filter(
  254. stanzas,
  255. s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="owner"]`, s).length
  256. ).pop());
  257. const owner_list_stanza = $iq({
  258. 'from': 'coven@chat.shakespeare.lit',
  259. 'id': owner_IQ.getAttribute('id'),
  260. 'to': 'romeo@montague.lit/orchard',
  261. 'type': 'result'
  262. }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN});
  263. members.filter(m => m.affiliation === 'owner').forEach(m => {
  264. owner_list_stanza.c('item', {
  265. 'affiliation': m.affiliation,
  266. 'jid': m.jid,
  267. 'nick': m.nick
  268. });
  269. });
  270. _converse.connection._dataRecv(mock.createRequest(owner_list_stanza));
  271. }
  272. return new Promise(resolve => _converse.api.listen.on('membersFetched', resolve));
  273. };
  274. mock.receiveOwnMUCPresence = async function (_converse, muc_jid, nick) {
  275. const sent_stanzas = _converse.connection.sent_stanzas;
  276. await u.waitUntil(() => sent_stanzas.filter(iq => sizzle('presence history', iq).length).pop());
  277. const presence = $pres({
  278. to: _converse.connection.jid,
  279. from: `${muc_jid}/${nick}`,
  280. id: u.getUniqueId()
  281. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  282. .c('item').attrs({
  283. affiliation: 'owner',
  284. jid: _converse.bare_jid,
  285. role: 'moderator'
  286. }).up()
  287. .c('status').attrs({code:'110'});
  288. _converse.connection._dataRecv(mock.createRequest(presence));
  289. };
  290. mock.openAndEnterChatRoom = async function (_converse, muc_jid, nick, features=[], members=[]) {
  291. muc_jid = muc_jid.toLowerCase();
  292. const room_creation_promise = _converse.api.rooms.open(muc_jid);
  293. await mock.getRoomFeatures(_converse, muc_jid, features);
  294. await mock.waitForReservedNick(_converse, muc_jid, nick);
  295. // The user has just entered the room (because join was called)
  296. // and receives their own presence from the server.
  297. // See example 24: https://xmpp.org/extensions/xep-0045.html#enter-pres
  298. await mock.receiveOwnMUCPresence(_converse, muc_jid, nick);
  299. await room_creation_promise;
  300. const view = _converse.chatboxviews.get(muc_jid);
  301. await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED));
  302. const affs = _converse.muc_fetch_members;
  303. const all_affiliations = Array.isArray(affs) ? affs : (affs ? ['member', 'admin', 'owner'] : []);
  304. await mock.returnMemberLists(_converse, muc_jid, members, all_affiliations);
  305. await view.model.messages.fetched;
  306. };
  307. mock.clearChatBoxMessages = function (converse, jid) {
  308. const view = converse.chatboxviews.get(jid);
  309. view.msgs_container.innerHTML = '';
  310. return view.model.messages.clearStore();
  311. };
  312. mock.createContact = async function (_converse, name, ask, requesting, subscription) {
  313. const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
  314. if (_converse.roster.get(jid)) {
  315. return Promise.resolve();
  316. }
  317. const contact = await new Promise((success, error) => {
  318. _converse.roster.create({
  319. 'ask': ask,
  320. 'fullname': name,
  321. 'jid': jid,
  322. 'requesting': requesting,
  323. 'subscription': subscription
  324. }, {success, error});
  325. });
  326. return contact;
  327. };
  328. mock.createContacts = async function (_converse, type, length) {
  329. /* Create current (as opposed to requesting or pending) contacts
  330. * for the user's roster.
  331. *
  332. * These contacts are not grouped. See below.
  333. */
  334. await _converse.api.waitUntil('rosterContactsFetched');
  335. let names, subscription, requesting, ask;
  336. if (type === 'requesting') {
  337. names = mock.req_names;
  338. subscription = 'none';
  339. requesting = true;
  340. ask = null;
  341. } else if (type === 'pending') {
  342. names = mock.pend_names;
  343. subscription = 'none';
  344. requesting = false;
  345. ask = 'subscribe';
  346. } else if (type === 'current') {
  347. names = mock.cur_names;
  348. subscription = 'both';
  349. requesting = false;
  350. ask = null;
  351. } else if (type === 'all') {
  352. await this.createContacts(_converse, 'current');
  353. await this.createContacts(_converse, 'requesting')
  354. await this.createContacts(_converse, 'pending');
  355. return this;
  356. } else {
  357. throw Error("Need to specify the type of contact to create");
  358. }
  359. const promises = names.slice(0, length).map(n => this.createContact(_converse, n, ask, requesting, subscription));
  360. await Promise.all(promises);
  361. };
  362. mock.waitForRoster = async function (_converse, type='current', length=-1, include_nick=true, grouped=true) {
  363. const s = `iq[type="get"] query[xmlns="${Strophe.NS.ROSTER}"]`;
  364. const iq = await u.waitUntil(() => _converse.connection.IQ_stanzas.filter(iq => sizzle(s, iq).length).pop());
  365. const result = $iq({
  366. 'to': _converse.connection.jid,
  367. 'type': 'result',
  368. 'id': iq.getAttribute('id')
  369. }).c('query', {
  370. 'xmlns': 'jabber:iq:roster'
  371. });
  372. if (type === 'pending' || type === 'all') {
  373. const pend_names = (length > -1) ? mock.pend_names.slice(0, length) : mock.pend_names;
  374. pend_names.map(name =>
  375. result.c('item', {
  376. jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit',
  377. name: include_nick ? name : undefined,
  378. subscription: 'none',
  379. ask: 'subscribe'
  380. }).up()
  381. );
  382. }
  383. if (type === 'current' || type === 'all') {
  384. const cur_names = Object.keys(mock.current_contacts_map);
  385. const names = (length > -1) ? cur_names.slice(0, length) : cur_names;
  386. names.forEach(name => {
  387. result.c('item', {
  388. jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit',
  389. name: include_nick ? name : undefined,
  390. subscription: 'both',
  391. ask: null
  392. });
  393. if (grouped) {
  394. mock.current_contacts_map[name].forEach(g => result.c('group').t(g).up());
  395. }
  396. result.up();
  397. });
  398. }
  399. _converse.connection._dataRecv(mock.createRequest(result));
  400. await _converse.api.waitUntil('rosterContactsFetched');
  401. };
  402. mock.createChatMessage = function (_converse, sender_jid, message) {
  403. return $msg({
  404. from: sender_jid,
  405. to: _converse.connection.jid,
  406. type: 'chat',
  407. id: (new Date()).getTime()
  408. })
  409. .c('body').t(message).up()
  410. .c('active', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  411. }
  412. mock.sendMessage = function (view, message) {
  413. const promise = new Promise(resolve => view.once('messageInserted', resolve));
  414. view.el.querySelector('.chat-textarea').value = message;
  415. view.onKeyDown({
  416. target: view.el.querySelector('textarea.chat-textarea'),
  417. preventDefault: _.noop,
  418. keyCode: 13
  419. });
  420. return promise;
  421. };
  422. window.libsignal = {
  423. 'SignalProtocolAddress': function (name, device_id) {
  424. this.name = name;
  425. this.deviceId = device_id;
  426. },
  427. 'SessionCipher': function (storage, remote_address) {
  428. this.remoteAddress = remote_address;
  429. this.storage = storage;
  430. this.encrypt = () => Promise.resolve({
  431. 'type': 1,
  432. 'body': 'c1ph3R73X7',
  433. 'registrationId': '1337'
  434. });
  435. this.decryptPreKeyWhisperMessage = (key_and_tag) => {
  436. return Promise.resolve(key_and_tag);
  437. };
  438. this.decryptWhisperMessage = (key_and_tag) => {
  439. return Promise.resolve(key_and_tag);
  440. }
  441. },
  442. 'SessionBuilder': function (storage, remote_address) { // eslint-disable-line no-unused-vars
  443. this.processPreKey = function () {
  444. return Promise.resolve();
  445. }
  446. },
  447. 'KeyHelper': {
  448. 'generateIdentityKeyPair': function () {
  449. return Promise.resolve({
  450. 'pubKey': new TextEncoder('utf-8').encode('1234'),
  451. 'privKey': new TextEncoder('utf-8').encode('4321')
  452. });
  453. },
  454. 'generateRegistrationId': function () {
  455. return '123456789';
  456. },
  457. 'generatePreKey': function (keyid) {
  458. return Promise.resolve({
  459. 'keyId': keyid,
  460. 'keyPair': {
  461. 'pubKey': new TextEncoder('utf-8').encode('1234'),
  462. 'privKey': new TextEncoder('utf-8').encode('4321')
  463. }
  464. });
  465. },
  466. 'generateSignedPreKey': function (identity_keypair, keyid) {
  467. return Promise.resolve({
  468. 'signature': new TextEncoder('utf-8').encode('11112222333344445555'),
  469. 'keyId': keyid,
  470. 'keyPair': {
  471. 'pubKey': new TextEncoder('utf-8').encode('1234'),
  472. 'privKey': new TextEncoder('utf-8').encode('4321')
  473. }
  474. });
  475. }
  476. }
  477. };
  478. mock.default_muc_features = [
  479. 'http://jabber.org/protocol/muc',
  480. 'jabber:iq:register',
  481. Strophe.NS.SID,
  482. Strophe.NS.MAM,
  483. 'muc_passwordprotected',
  484. 'muc_hidden',
  485. 'muc_temporary',
  486. 'muc_open',
  487. 'muc_unmoderated',
  488. 'muc_anonymous'
  489. ];
  490. mock.view_mode = 'overlayed';
  491. // Names from http://www.fakenamegenerator.com/
  492. mock.req_names = [
  493. 'Escalus, prince of Verona', 'The Nurse', 'Paris'
  494. ];
  495. mock.pend_names = [
  496. 'Lord Capulet', 'Guard', 'Servant'
  497. ];
  498. mock.current_contacts_map = {
  499. 'Mercutio': ['Colleagues', 'friends & acquaintences'],
  500. 'Juliet Capulet': ['friends & acquaintences'],
  501. 'Lady Montague': ['Colleagues', 'Family'],
  502. 'Lord Montague': ['Family'],
  503. 'Friar Laurence': ['friends & acquaintences'],
  504. 'Tybalt': ['friends & acquaintences'],
  505. 'Lady Capulet': ['ænemies'],
  506. 'Benviolo': ['friends & acquaintences'],
  507. 'Balthasar': ['Colleagues'],
  508. 'Peter': ['Colleagues'],
  509. 'Abram': ['Colleagues'],
  510. 'Sampson': ['Colleagues'],
  511. 'Gregory': ['friends & acquaintences'],
  512. 'Potpan': [],
  513. 'Friar John': []
  514. };
  515. const map = mock.current_contacts_map;
  516. const groups_map = {};
  517. Object.keys(map).forEach(k => {
  518. const groups = map[k].length ? map[k] : ["Ungrouped"];
  519. Object.values(groups).forEach(g => {
  520. groups_map[g] = groups_map[g] ? [...groups_map[g], k] : [k]
  521. });
  522. });
  523. mock.groups_map = groups_map;
  524. mock.cur_names = Object.keys(mock.current_contacts_map);
  525. mock.num_contacts = mock.req_names.length + mock.pend_names.length + mock.cur_names.length;
  526. mock.groups = {
  527. 'colleagues': 3,
  528. 'friends & acquaintences': 3,
  529. 'Family': 4,
  530. 'ænemies': 3,
  531. 'Ungrouped': 2
  532. };
  533. mock.chatroom_names = [
  534. 'Dyon van de Wege',
  535. 'Thomas Kalb',
  536. 'Dirk Theissen',
  537. 'Felix Hofmann',
  538. 'Ka Lek',
  539. 'Anne Ebersbacher'
  540. ];
  541. // TODO: need to also test other roles and affiliations
  542. mock.chatroom_roles = {
  543. 'Anne Ebersbacher': { affiliation: "owner", role: "moderator" },
  544. 'Dirk Theissen': { affiliation: "admin", role: "moderator" },
  545. 'Dyon van de Wege': { affiliation: "member", role: "occupant" },
  546. 'Felix Hofmann': { affiliation: "member", role: "occupant" },
  547. 'Ka Lek': { affiliation: "member", role: "occupant" },
  548. 'Thomas Kalb': { affiliation: "member", role: "occupant" }
  549. };
  550. mock.event = {
  551. 'preventDefault': function () {}
  552. };
  553. const OriginalConnection = Strophe.Connection;
  554. function MockConnection (service, options) {
  555. OriginalConnection.call(this, service, options);
  556. Strophe.Bosh.prototype._processRequest = function () {}; // Don't attempt to send out stanzas
  557. const sendIQ = this.sendIQ;
  558. this.IQ_stanzas = [];
  559. this.IQ_ids = [];
  560. this.sendIQ = function (iq, callback, errback) {
  561. if (!_.isElement(iq)) {
  562. iq = iq.nodeTree;
  563. }
  564. this.IQ_stanzas.push(iq);
  565. const id = sendIQ.bind(this)(iq, callback, errback);
  566. this.IQ_ids.push(id);
  567. return id;
  568. }
  569. const send = this.send;
  570. this.sent_stanzas = [];
  571. this.send = function (stanza) {
  572. if (_.isElement(stanza)) {
  573. this.sent_stanzas.push(stanza);
  574. } else {
  575. this.sent_stanzas.push(stanza.nodeTree);
  576. }
  577. return send.apply(this, arguments);
  578. }
  579. this.features = Strophe.xmlHtmlNode(
  580. '<stream:features xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client">'+
  581. '<ver xmlns="urn:xmpp:features:rosterver"/>'+
  582. '<csi xmlns="urn:xmpp:csi:0"/>'+
  583. '<this xmlns="http://jabber.org/protocol/caps" ver="UwBpfJpEt3IoLYfWma/o/p3FFRo=" hash="sha-1" node="http://prosody.im"/>'+
  584. '<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">'+
  585. '<required/>'+
  586. '</bind>'+
  587. `<sm xmlns='urn:xmpp:sm:3'/>`+
  588. '<session xmlns="urn:ietf:params:xml:ns:xmpp-session">'+
  589. '<optional/>'+
  590. '</session>'+
  591. '</stream:features>').firstChild;
  592. this._proto._connect = () => {
  593. this.connected = true;
  594. this.mock = true;
  595. this.jid = 'romeo@montague.lit/orchard';
  596. this._changeConnectStatus(Strophe.Status.BINDREQUIRED);
  597. };
  598. this.bind = () => {
  599. this.authenticated = true;
  600. this.authenticated = true;
  601. if (!_converse.no_connection_on_bind) {
  602. this._changeConnectStatus(Strophe.Status.CONNECTED);
  603. }
  604. };
  605. this._proto._disconnect = () => this._onDisconnectTimeout();
  606. this._proto._onDisconnectTimeout = _.noop;
  607. }
  608. MockConnection.prototype = Object.create(OriginalConnection.prototype);
  609. Strophe.Connection = MockConnection;
  610. function clearIndexedDB () {
  611. const promise = u.getResolveablePromise();
  612. const db_request = window.indexedDB.open("converse-test-persistent");
  613. db_request.onsuccess = function () {
  614. const db = db_request.result;
  615. const bare_jid = "romeo@montague.lit";
  616. let store;
  617. try {
  618. store= db.transaction([bare_jid], "readwrite").objectStore(bare_jid);
  619. } catch (e) {
  620. return promise.resolve();
  621. }
  622. const request = store.clear();
  623. request.onsuccess = promise.resolve();
  624. request.onerror = promise.resolve();
  625. };
  626. db_request.onerror = function (ev) {
  627. return promise.reject(ev.target.error);
  628. }
  629. return promise;
  630. }
  631. function clearStores () {
  632. [localStorage, sessionStorage].forEach(
  633. s => Object.keys(s).forEach(k => k.match(/^converse-test-/) && s.removeItem(k))
  634. );
  635. const cache_key = `converse.room-bookmarksromeo@montague.lit`;
  636. window.sessionStorage.removeItem(cache_key+'fetched');
  637. }
  638. initConverse = async (settings) => {
  639. clearStores();
  640. await clearIndexedDB();
  641. _converse = await converse.initialize(Object.assign({
  642. 'animate': false,
  643. 'auto_subscribe': false,
  644. 'bosh_service_url': 'montague.lit/http-bind',
  645. 'discover_connection_methods': false,
  646. 'enable_smacks': false,
  647. 'i18n': 'en',
  648. // 'persistent_store': 'IndexedDB',
  649. 'loglevel': 'warn',
  650. 'no_trimming': true,
  651. 'play_sounds': false,
  652. 'use_emojione': false,
  653. 'view_mode': mock.view_mode
  654. }, settings || {}));
  655. _converse.ChatBoxViews.prototype.trimChat = function () {};
  656. _converse.api.vcard.get = function (model, force) {
  657. let jid;
  658. if (_.isString(model)) {
  659. jid = model;
  660. } else if (!model.get('vcard_updated') || force) {
  661. jid = model.get('jid') || model.get('muc_jid');
  662. }
  663. let fullname;
  664. if (!jid || jid == 'romeo@montague.lit') {
  665. jid = 'romeo@montague.lit';
  666. fullname = 'Romeo Montague' ;
  667. } else {
  668. const name = jid.split('@')[0].replace(/\./g, ' ').split(' ');
  669. const last = name.length-1;
  670. name[0] = name[0].charAt(0).toUpperCase()+name[0].slice(1);
  671. name[last] = name[last].charAt(0).toUpperCase()+name[last].slice(1);
  672. fullname = name.join(' ');
  673. }
  674. const vcard = $iq().c('vCard').c('FN').t(fullname).nodeTree;
  675. return {
  676. 'vcard': vcard,
  677. 'fullname': _.get(vcard.querySelector('FN'), 'textContent'),
  678. 'image': _.get(vcard.querySelector('PHOTO BINVAL'), 'textContent'),
  679. 'image_type': _.get(vcard.querySelector('PHOTO TYPE'), 'textContent'),
  680. 'url': _.get(vcard.querySelector('URL'), 'textContent'),
  681. 'vcard_updated': dayjs().format(),
  682. 'vcard_error': undefined
  683. };
  684. };
  685. if (_.get(settings, 'auto_login') !== false) {
  686. _converse.api.user.login('romeo@montague.lit/orchard', 'secret');
  687. await _converse.api.waitUntil('afterResourceBinding');
  688. }
  689. window.converse_disable_effects = true;
  690. return _converse;
  691. }
  692. });
  693. converse.load();