mock.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821
  1. let _converse;
  2. const mock = {};
  3. const converse = window.converse;
  4. converse.load();
  5. const { u, sizzle, Strophe, dayjs, $iq, $msg, $pres } = converse.env;
  6. jasmine.DEFAULT_TIMEOUT_INTERVAL = 7000;
  7. jasmine.toEqualStanza = function toEqualStanza () {
  8. return {
  9. compare (actual, expected) {
  10. const result = { pass: u.isEqualNode(actual, expected) };
  11. if (!result.pass) {
  12. result.message = `Stanzas don't match:\n`+
  13. `Actual:\n${(actual.tree?.() ?? actual).outerHTML}\n`+
  14. `Expected:\n${expected.tree().outerHTML}`;
  15. }
  16. return result;
  17. }
  18. }
  19. }
  20. function initConverse (promise_names=[], settings=null, func) {
  21. if (typeof promise_names === "function") {
  22. func = promise_names;
  23. promise_names = []
  24. settings = null;
  25. }
  26. return async () => {
  27. if (_converse && _converse.api.connection.connected()) {
  28. await _converse.api.user.logout();
  29. }
  30. const el = document.querySelector('#conversejs');
  31. if (el) {
  32. el.parentElement.removeChild(el);
  33. }
  34. document.title = "Converse Tests";
  35. await _initConverse(settings);
  36. await Promise.all((promise_names || []).map(_converse.api.waitUntil));
  37. try {
  38. await func(_converse);
  39. } catch(e) {
  40. console.error(e);
  41. fail(e);
  42. }
  43. }
  44. }
  45. async function waitUntilDiscoConfirmed (_converse, entity_jid, identities, features=[], items=[], type='info') {
  46. const sel = `iq[to="${entity_jid}"] query[xmlns="http://jabber.org/protocol/disco#${type}"]`;
  47. const iq = await u.waitUntil(() => _converse.connection.IQ_stanzas.filter(iq => sizzle(sel, iq).length).pop());
  48. const stanza = $iq({
  49. 'type': 'result',
  50. 'from': entity_jid,
  51. 'to': 'romeo@montague.lit/orchard',
  52. 'id': iq.getAttribute('id'),
  53. }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#'+type});
  54. identities?.forEach(identity => stanza.c('identity', {'category': identity.category, 'type': identity.type}).up());
  55. features?.forEach(feature => stanza.c('feature', {'var': feature}).up());
  56. items?.forEach(item => stanza.c('item', {'jid': item}).up());
  57. _converse.connection._dataRecv(createRequest(stanza));
  58. }
  59. function createRequest (stanza) {
  60. stanza = typeof stanza.tree == "function" ? stanza.tree() : stanza;
  61. const req = new Strophe.Request(stanza, () => {});
  62. req.getResponse = function () {
  63. var env = new Strophe.Builder('env', {type: 'mock'}).tree();
  64. env.appendChild(stanza);
  65. return env;
  66. };
  67. return req;
  68. }
  69. function closeAllChatBoxes (_converse) {
  70. return Promise.all(_converse.chatboxviews.map(view => view.close()));
  71. }
  72. function toggleControlBox () {
  73. const toggle = document.querySelector(".toggle-controlbox");
  74. if (!u.isVisible(document.querySelector("#controlbox"))) {
  75. if (!u.isVisible(toggle)) {
  76. u.removeClass('hidden', toggle);
  77. }
  78. toggle.click();
  79. }
  80. }
  81. async function openControlBox(_converse) {
  82. const model = await _converse.api.controlbox.open();
  83. await u.waitUntil(() => model.get('connected'));
  84. toggleControlBox();
  85. return this;
  86. }
  87. function closeControlBox () {
  88. const controlbox = document.querySelector("#controlbox");
  89. if (u.isVisible(controlbox)) {
  90. const button = controlbox.querySelector(".close-chatbox-button");
  91. (button !== null) && button.click();
  92. }
  93. return this;
  94. }
  95. async function waitUntilBookmarksReturned (_converse, bookmarks=[]) {
  96. await waitUntilDiscoConfirmed(
  97. _converse, _converse.bare_jid,
  98. [{'category': 'pubsub', 'type': 'pep'}],
  99. ['http://jabber.org/protocol/pubsub#publish-options']
  100. );
  101. const IQ_stanzas = _converse.connection.IQ_stanzas;
  102. const sent_stanza = await u.waitUntil(
  103. () => IQ_stanzas.filter(s => sizzle('items[node="storage:bookmarks"]', s).length).pop()
  104. );
  105. const stanza = $iq({
  106. 'to': _converse.connection.jid,
  107. 'type':'result',
  108. 'id':sent_stanza.getAttribute('id')
  109. }).c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
  110. .c('items', {'node': 'storage:bookmarks'})
  111. .c('item', {'id': 'current'})
  112. .c('storage', {'xmlns': 'storage:bookmarks'});
  113. bookmarks.forEach(bookmark => {
  114. stanza.c('conference', {
  115. 'name': bookmark.name,
  116. 'autojoin': bookmark.autojoin,
  117. 'jid': bookmark.jid
  118. }).c('nick').t(bookmark.nick).up().up()
  119. });
  120. _converse.connection._dataRecv(createRequest(stanza));
  121. await _converse.api.waitUntil('bookmarksInitialized');
  122. }
  123. function openChatBoxes (converse, amount) {
  124. for (let i=0; i<amount; i++) {
  125. const jid = cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit';
  126. converse.roster.get(jid).openChat();
  127. }
  128. }
  129. async function openChatBoxFor (_converse, jid) {
  130. await _converse.api.waitUntil('rosterContactsFetched');
  131. _converse.roster.get(jid).openChat();
  132. return u.waitUntil(() => _converse.chatboxviews.get(jid), 1000);
  133. }
  134. async function openChatRoomViaModal (_converse, jid, nick='') {
  135. // Opens a new chatroom
  136. const model = await _converse.api.controlbox.open('controlbox');
  137. await u.waitUntil(() => model.get('connected'));
  138. await openControlBox(_converse);
  139. document.querySelector('converse-rooms-list .show-add-muc-modal').click();
  140. closeControlBox(_converse);
  141. const modal = _converse.api.modal.get('converse-add-muc-modal');
  142. await u.waitUntil(() => u.isVisible(modal), 1500)
  143. modal.querySelector('input[name="chatroom"]').value = jid;
  144. if (nick) {
  145. modal.querySelector('input[name="nickname"]').value = nick;
  146. }
  147. modal.querySelector('form input[type="submit"]').click();
  148. await u.waitUntil(() => _converse.chatboxviews.get(jid), 1000);
  149. return _converse.chatboxviews.get(jid);
  150. }
  151. function openChatRoom (_converse, room, server) {
  152. return _converse.api.rooms.open(`${room}@${server}`);
  153. }
  154. async function getRoomFeatures (_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 : 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(createRequest(features_stanza));
  184. }
  185. async function waitForReservedNick (_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(createRequest(stanza));
  203. if (nick) {
  204. return u.waitUntil(() => nick);
  205. }
  206. }
  207. async function returnMemberLists (_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(() =>
  214. stanzas.filter(s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="member"]`, s).length
  215. ).pop());
  216. const member_list_stanza = $iq({
  217. 'from': 'coven@chat.shakespeare.lit',
  218. 'id': member_IQ.getAttribute('id'),
  219. 'to': 'romeo@montague.lit/orchard',
  220. 'type': 'result'
  221. }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN});
  222. members.filter(m => m.affiliation === 'member').forEach(m => {
  223. member_list_stanza.c('item', {
  224. 'affiliation': m.affiliation,
  225. 'jid': m.jid,
  226. 'nick': m.nick
  227. });
  228. });
  229. _converse.connection._dataRecv(createRequest(member_list_stanza));
  230. }
  231. if (affiliations.includes('admin')) {
  232. const admin_IQ = await u.waitUntil(() => stanzas.filter(
  233. s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="admin"]`, s).length
  234. ).pop());
  235. const admin_list_stanza = $iq({
  236. 'from': 'coven@chat.shakespeare.lit',
  237. 'id': admin_IQ.getAttribute('id'),
  238. 'to': 'romeo@montague.lit/orchard',
  239. 'type': 'result'
  240. }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN});
  241. members.filter(m => m.affiliation === 'admin').forEach(m => {
  242. admin_list_stanza.c('item', {
  243. 'affiliation': m.affiliation,
  244. 'jid': m.jid,
  245. 'nick': m.nick
  246. });
  247. });
  248. _converse.connection._dataRecv(createRequest(admin_list_stanza));
  249. }
  250. if (affiliations.includes('owner')) {
  251. const owner_IQ = await u.waitUntil(() => stanzas.filter(
  252. s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="owner"]`, s).length
  253. ).pop());
  254. const owner_list_stanza = $iq({
  255. 'from': 'coven@chat.shakespeare.lit',
  256. 'id': owner_IQ.getAttribute('id'),
  257. 'to': 'romeo@montague.lit/orchard',
  258. 'type': 'result'
  259. }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN});
  260. members.filter(m => m.affiliation === 'owner').forEach(m => {
  261. owner_list_stanza.c('item', {
  262. 'affiliation': m.affiliation,
  263. 'jid': m.jid,
  264. 'nick': m.nick
  265. });
  266. });
  267. _converse.connection._dataRecv(createRequest(owner_list_stanza));
  268. }
  269. return new Promise(resolve => _converse.api.listen.on('membersFetched', resolve));
  270. }
  271. async function receiveOwnMUCPresence (_converse, muc_jid, nick, affiliation='owner', role='moderator', features=[]) {
  272. const sent_stanzas = _converse.connection.sent_stanzas;
  273. await u.waitUntil(() => sent_stanzas.filter(iq => sizzle('presence history', iq).length).pop());
  274. const presence = $pres({
  275. to: _converse.connection.jid,
  276. from: `${muc_jid}/${nick}`,
  277. id: u.getUniqueId()
  278. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  279. .c('item').attrs({ affiliation, role, 'jid': _converse.bare_jid }).up()
  280. .c('status').attrs({code:'110'}).up().up()
  281. if (features.includes(Strophe.NS.OCCUPANTID)) {
  282. presence.c('occupant-id', {'xmlns': Strophe.NS.OCCUPANTID, 'id': u.getUniqueId() });
  283. }
  284. if (_converse.xmppstatus.get('status')) {
  285. presence.c('show').t(_converse.xmppstatus.get('status'));
  286. }
  287. _converse.connection._dataRecv(createRequest(presence));
  288. }
  289. async function openAndEnterChatRoom (
  290. _converse,
  291. muc_jid,
  292. nick,
  293. features=[],
  294. members=[],
  295. force_open=true,
  296. settings={},
  297. own_affiliation='owner',
  298. own_role='moderator',
  299. ) {
  300. const { api } = _converse;
  301. muc_jid = muc_jid.toLowerCase();
  302. const room_creation_promise = api.rooms.open(muc_jid, settings, force_open);
  303. await getRoomFeatures(_converse, muc_jid, features);
  304. await waitForReservedNick(_converse, muc_jid, nick);
  305. // The user has just entered the room (because join was called)
  306. // and receives their own presence from the server.
  307. // See example 24: https://xmpp.org/extensions/xep-0045.html#enter-pres
  308. await receiveOwnMUCPresence(_converse, muc_jid, nick, own_affiliation, own_role, features);
  309. await room_creation_promise;
  310. const model = _converse.chatboxes.get(muc_jid);
  311. await u.waitUntil(() => (model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED));
  312. const affs = api.settings.get('muc_fetch_members');
  313. const all_affiliations = Array.isArray(affs) ? affs : (affs ? ['member', 'admin', 'owner'] : []);
  314. if (['member', 'admin', 'owner'].includes(own_affiliation)) {
  315. await returnMemberLists(_converse, muc_jid, members, all_affiliations);
  316. }
  317. await model.messages.fetched;
  318. return model;
  319. }
  320. async function createContact (_converse, name, ask, requesting, subscription) {
  321. const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
  322. if (_converse.roster.get(jid)) {
  323. return Promise.resolve();
  324. }
  325. const contact = await new Promise((success, error) => {
  326. _converse.roster.create({
  327. 'ask': ask,
  328. 'fullname': name,
  329. 'jid': jid,
  330. 'requesting': requesting,
  331. 'subscription': subscription
  332. }, {success, error});
  333. });
  334. return contact;
  335. }
  336. async function createContacts (_converse, type, length) {
  337. /* Create current (as opposed to requesting or pending) contacts
  338. * for the user's roster.
  339. *
  340. * These contacts are not grouped. See below.
  341. */
  342. await _converse.api.waitUntil('rosterContactsFetched');
  343. let names, subscription, requesting, ask;
  344. if (type === 'requesting') {
  345. names = req_names;
  346. subscription = 'none';
  347. requesting = true;
  348. ask = null;
  349. } else if (type === 'pending') {
  350. names = pend_names;
  351. subscription = 'none';
  352. requesting = false;
  353. ask = 'subscribe';
  354. } else if (type === 'current') {
  355. names = cur_names;
  356. subscription = 'both';
  357. requesting = false;
  358. ask = null;
  359. } else if (type === 'all') {
  360. await this.createContacts(_converse, 'current');
  361. await this.createContacts(_converse, 'requesting')
  362. await this.createContacts(_converse, 'pending');
  363. return this;
  364. } else {
  365. throw Error("Need to specify the type of contact to create");
  366. }
  367. const promises = names.slice(0, length).map(n => this.createContact(_converse, n, ask, requesting, subscription));
  368. await Promise.all(promises);
  369. }
  370. async function waitForRoster (_converse, type='current', length=-1, include_nick=true, grouped=true) {
  371. const s = `iq[type="get"] query[xmlns="${Strophe.NS.ROSTER}"]`;
  372. const iq = await u.waitUntil(() => _converse.connection.IQ_stanzas.filter(iq => sizzle(s, iq).length).pop());
  373. const result = $iq({
  374. 'to': _converse.connection.jid,
  375. 'type': 'result',
  376. 'id': iq.getAttribute('id')
  377. }).c('query', {
  378. 'xmlns': 'jabber:iq:roster'
  379. });
  380. if (type === 'pending' || type === 'all') {
  381. ((length > -1) ? pend_names.slice(0, length) : pend_names).map(name =>
  382. result.c('item', {
  383. jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit',
  384. name: include_nick ? name : undefined,
  385. subscription: 'none',
  386. ask: 'subscribe'
  387. }).up()
  388. );
  389. }
  390. if (type === 'current' || type === 'all') {
  391. const cur_names = Object.keys(current_contacts_map);
  392. const names = (length > -1) ? cur_names.slice(0, length) : cur_names;
  393. names.forEach(name => {
  394. result.c('item', {
  395. jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit',
  396. name: include_nick ? name : undefined,
  397. subscription: 'both',
  398. ask: null
  399. });
  400. if (grouped) {
  401. current_contacts_map[name].forEach(g => result.c('group').t(g).up());
  402. }
  403. result.up();
  404. });
  405. }
  406. _converse.connection._dataRecv(createRequest(result));
  407. await _converse.api.waitUntil('rosterContactsFetched');
  408. }
  409. function createChatMessage (_converse, sender_jid, message) {
  410. return $msg({
  411. from: sender_jid,
  412. to: _converse.connection.jid,
  413. type: 'chat',
  414. id: (new Date()).getTime()
  415. })
  416. .c('body').t(message).up()
  417. .c('markable', {'xmlns': Strophe.NS.MARKERS}).up()
  418. .c('active', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  419. }
  420. async function sendMessage (view, message) {
  421. const promise = new Promise(resolve => view.model.messages.once('rendered', resolve));
  422. const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
  423. textarea.value = message;
  424. const message_form = view.querySelector('converse-message-form') || view.querySelector('converse-muc-message-form');
  425. message_form.onKeyDown({
  426. target: view.querySelector('textarea.chat-textarea'),
  427. preventDefault: () => {},
  428. keyCode: 13
  429. });
  430. return promise;
  431. }
  432. window.libsignal = {
  433. 'SignalProtocolAddress': function (name, device_id) {
  434. this.name = name;
  435. this.deviceId = device_id;
  436. },
  437. 'SessionCipher': function (storage, remote_address) {
  438. this.remoteAddress = remote_address;
  439. this.storage = storage;
  440. this.encrypt = () => Promise.resolve({
  441. 'type': 1,
  442. 'body': 'c1ph3R73X7',
  443. 'registrationId': '1337'
  444. });
  445. this.decryptPreKeyWhisperMessage = (key_and_tag) => {
  446. return Promise.resolve(key_and_tag);
  447. };
  448. this.decryptWhisperMessage = (key_and_tag) => {
  449. return Promise.resolve(key_and_tag);
  450. }
  451. },
  452. 'SessionBuilder': function (storage, remote_address) { // eslint-disable-line no-unused-vars
  453. this.processPreKey = function () {
  454. return Promise.resolve();
  455. }
  456. },
  457. 'KeyHelper': {
  458. 'generateIdentityKeyPair': function () {
  459. return Promise.resolve({
  460. 'pubKey': new TextEncoder('utf-8').encode('1234'),
  461. 'privKey': new TextEncoder('utf-8').encode('4321')
  462. });
  463. },
  464. 'generateRegistrationId': function () {
  465. return '123456789';
  466. },
  467. 'generatePreKey': function (keyid) {
  468. return Promise.resolve({
  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. 'generateSignedPreKey': function (identity_keypair, keyid) {
  477. return Promise.resolve({
  478. 'signature': new TextEncoder('utf-8').encode('11112222333344445555'),
  479. 'keyId': keyid,
  480. 'keyPair': {
  481. 'pubKey': new TextEncoder('utf-8').encode('1234'),
  482. 'privKey': new TextEncoder('utf-8').encode('4321')
  483. }
  484. });
  485. }
  486. }
  487. }
  488. const default_muc_features = [
  489. 'http://jabber.org/protocol/muc',
  490. 'jabber:iq:register',
  491. Strophe.NS.SID,
  492. Strophe.NS.MAM,
  493. 'muc_passwordprotected',
  494. 'muc_hidden',
  495. 'muc_temporary',
  496. 'muc_open',
  497. 'muc_unmoderated',
  498. 'muc_anonymous'
  499. ];
  500. const view_mode = 'overlayed';
  501. // Names from http://www.fakenamegenerator.com/
  502. const req_names = [
  503. 'Escalus, prince of Verona', 'The Nurse', 'Paris'
  504. ];
  505. const pend_names = [
  506. 'Lord Capulet', 'Guard', 'Servant'
  507. ];
  508. const current_contacts_map = {
  509. 'Mercutio': ['Colleagues', 'friends & acquaintences'],
  510. 'Juliet Capulet': ['friends & acquaintences'],
  511. 'Lady Montague': ['Colleagues', 'Family'],
  512. 'Lord Montague': ['Family'],
  513. 'Friar Laurence': ['friends & acquaintences'],
  514. 'Tybalt': ['friends & acquaintences'],
  515. 'Lady Capulet': ['ænemies'],
  516. 'Benviolo': ['friends & acquaintences'],
  517. 'Balthasar': ['Colleagues'],
  518. 'Peter': ['Colleagues'],
  519. 'Abram': ['Colleagues'],
  520. 'Sampson': ['Colleagues'],
  521. 'Gregory': ['friends & acquaintences'],
  522. 'Potpan': [],
  523. 'Friar John': []
  524. }
  525. const map = current_contacts_map;
  526. const groups_map = {};
  527. Object.keys(map).forEach(k => {
  528. const groups = map[k].length ? map[k] : ["Ungrouped"];
  529. Object.values(groups).forEach(g => {
  530. groups_map[g] = groups_map[g] ? [...groups_map[g], k] : [k]
  531. });
  532. });
  533. const cur_names = Object.keys(current_contacts_map);
  534. const num_contacts = req_names.length + pend_names.length + cur_names.length;
  535. const groups = {
  536. 'colleagues': 3,
  537. 'friends & acquaintences': 3,
  538. 'Family': 4,
  539. 'ænemies': 3,
  540. 'Ungrouped': 2
  541. }
  542. const chatroom_names = [
  543. 'Dyon van de Wege',
  544. 'Thomas Kalb',
  545. 'Dirk Theissen',
  546. 'Felix Hofmann',
  547. 'Ka Lek',
  548. 'Anne Ebersbacher'
  549. ];
  550. // TODO: need to also test other roles and affiliations
  551. const chatroom_roles = {
  552. 'Anne Ebersbacher': { affiliation: "owner", role: "moderator" },
  553. 'Dirk Theissen': { affiliation: "admin", role: "moderator" },
  554. 'Dyon van de Wege': { affiliation: "member", role: "occupant" },
  555. 'Felix Hofmann': { affiliation: "member", role: "occupant" },
  556. 'Ka Lek': { affiliation: "member", role: "occupant" },
  557. 'Thomas Kalb': { affiliation: "member", role: "occupant" }
  558. }
  559. const event = {
  560. 'preventDefault': function () {}
  561. }
  562. function clearIndexedDB () {
  563. const promise = u.getOpenPromise();
  564. const db_request = window.indexedDB.open("converse-test-persistent");
  565. db_request.onsuccess = function () {
  566. const db = db_request.result;
  567. const bare_jid = "romeo@montague.lit";
  568. let store;
  569. try {
  570. store= db.transaction([bare_jid], "readwrite").objectStore(bare_jid);
  571. } catch (e) {
  572. return promise.resolve();
  573. }
  574. const request = store.clear();
  575. request.onsuccess = promise.resolve();
  576. request.onerror = promise.resolve();
  577. };
  578. db_request.onerror = function (ev) {
  579. return promise.reject(ev.target.error);
  580. }
  581. return promise;
  582. }
  583. function clearStores () {
  584. [localStorage, sessionStorage].forEach(
  585. s => Object.keys(s).forEach(k => k.match(/^converse-test-/) && s.removeItem(k))
  586. );
  587. const cache_key = `converse.room-bookmarksromeo@montague.lit`;
  588. window.sessionStorage.removeItem(cache_key+'fetched');
  589. }
  590. const theme = ['dracula', 'classic', 'concord'][Math.floor(Math.random()*3)];
  591. async function _initConverse (settings) {
  592. clearStores();
  593. await clearIndexedDB();
  594. _converse = await converse.initialize(Object.assign({
  595. 'animate': false,
  596. 'auto_subscribe': false,
  597. 'bosh_service_url': 'montague.lit/http-bind',
  598. 'discover_connection_methods': false,
  599. 'enable_smacks': false,
  600. 'i18n': 'en',
  601. 'loglevel': 'debug',
  602. 'no_trimming': true,
  603. 'persistent_store': 'localStorage',
  604. 'play_sounds': false,
  605. 'theme': theme,
  606. 'use_emojione': false,
  607. 'view_mode': view_mode
  608. }, settings || {}));
  609. window._converse = _converse;
  610. _converse.api.vcard.get = function (model, force) {
  611. let jid;
  612. if (typeof model === 'string' || model instanceof String) {
  613. jid = model;
  614. } else if (!model.get('vcard_updated') || force) {
  615. jid = model.get('jid') || model.get('muc_jid');
  616. }
  617. let fullname;
  618. let nickname;
  619. if (!jid || jid == 'romeo@montague.lit') {
  620. jid = settings?.vcard?.jid ?? 'romeo@montague.lit';
  621. fullname = settings?.vcard?.display_name ?? 'Romeo Montague' ;
  622. nickname = settings?.vcard?.nickname ?? 'Romeo';
  623. } else {
  624. const name = jid.split('@')[0].replace(/\./g, ' ').split(' ');
  625. const last = name.length-1;
  626. name[0] = name[0].charAt(0).toUpperCase()+name[0].slice(1);
  627. name[last] = name[last].charAt(0).toUpperCase()+name[last].slice(1);
  628. fullname = name.join(' ');
  629. }
  630. const vcard = $iq().c('vCard').c('FN').t(fullname).up();
  631. if (nickname) vcard.c('NICKNAME').t(nickname);
  632. const vcard_el = vcard.tree();
  633. return {
  634. 'stanza': vcard_el,
  635. 'fullname': vcard_el.querySelector('FN')?.textContent,
  636. 'nickname': vcard_el.querySelector('NICKNAME')?.textContent,
  637. 'image': vcard_el.querySelector('PHOTO BINVAL')?.textContent,
  638. 'image_type': vcard_el.querySelector('PHOTO TYPE')?.textContent,
  639. 'url': vcard_el.querySelector('URL')?.textContent,
  640. 'vcard_updated': dayjs().format(),
  641. 'vcard_error': undefined
  642. };
  643. };
  644. if (settings?.auto_login !== false) {
  645. _converse.api.user.login('romeo@montague.lit/orchard', 'secret');
  646. await _converse.api.waitUntil('afterResourceBinding');
  647. }
  648. window.converse_disable_effects = true;
  649. return _converse;
  650. }
  651. async function deviceListFetched (_converse, jid) {
  652. const selector = `iq[to="${jid}"] items[node="eu.siacs.conversations.axolotl.devicelist"]`;
  653. const stanza = await u.waitUntil(
  654. () => Array.from(_converse.connection.IQ_stanzas).filter(iq => iq.querySelector(selector)).pop()
  655. );
  656. await u.waitUntil(() => _converse.devicelists.get(jid));
  657. return stanza;
  658. }
  659. function ownDeviceHasBeenPublished (_converse) {
  660. return Array.from(_converse.connection.IQ_stanzas).filter(
  661. iq => iq.querySelector('iq[from="'+_converse.bare_jid+'"] publish[node="eu.siacs.conversations.axolotl.devicelist"]')
  662. ).pop();
  663. }
  664. function bundleHasBeenPublished (_converse) {
  665. const selector = 'publish[node="eu.siacs.conversations.axolotl.bundles:123456789"]';
  666. return Array.from(_converse.connection.IQ_stanzas).filter(iq => iq.querySelector(selector)).pop();
  667. }
  668. function bundleFetched (_converse, jid, device_id) {
  669. return Array.from(_converse.connection.IQ_stanzas).filter(
  670. iq => iq.querySelector(`iq[to="${jid}"] items[node="eu.siacs.conversations.axolotl.bundles:${device_id}"]`)
  671. ).pop();
  672. }
  673. async function initializedOMEMO (_converse) {
  674. await waitUntilDiscoConfirmed(
  675. _converse, _converse.bare_jid,
  676. [{'category': 'pubsub', 'type': 'pep'}],
  677. ['http://jabber.org/protocol/pubsub#publish-options']
  678. );
  679. let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
  680. let stanza = $iq({
  681. 'from': _converse.bare_jid,
  682. 'id': iq_stanza.getAttribute('id'),
  683. 'to': _converse.bare_jid,
  684. 'type': 'result',
  685. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  686. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  687. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  688. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  689. .c('device', {'id': '482886413b977930064a5888b92134fe'});
  690. _converse.connection._dataRecv(createRequest(stanza));
  691. iq_stanza = await u.waitUntil(() => ownDeviceHasBeenPublished(_converse))
  692. stanza = $iq({
  693. 'from': _converse.bare_jid,
  694. 'id': iq_stanza.getAttribute('id'),
  695. 'to': _converse.bare_jid,
  696. 'type': 'result'});
  697. _converse.connection._dataRecv(createRequest(stanza));
  698. iq_stanza = await u.waitUntil(() => bundleHasBeenPublished(_converse))
  699. stanza = $iq({
  700. 'from': _converse.bare_jid,
  701. 'id': iq_stanza.getAttribute('id'),
  702. 'to': _converse.bare_jid,
  703. 'type': 'result'});
  704. _converse.connection._dataRecv(createRequest(stanza));
  705. await _converse.api.waitUntil('OMEMOInitialized');
  706. }
  707. Object.assign(mock, {
  708. bundleFetched,
  709. bundleHasBeenPublished,
  710. chatroom_names,
  711. chatroom_roles,
  712. closeAllChatBoxes,
  713. closeControlBox,
  714. createChatMessage,
  715. createContact,
  716. createContacts,
  717. createRequest,
  718. cur_names,
  719. current_contacts_map,
  720. default_muc_features,
  721. deviceListFetched,
  722. event,
  723. getRoomFeatures,
  724. groups,
  725. groups_map,
  726. initConverse,
  727. initializedOMEMO,
  728. num_contacts,
  729. openAndEnterChatRoom,
  730. openChatBoxFor,
  731. openChatBoxes,
  732. openChatRoom,
  733. openChatRoomViaModal,
  734. openControlBox,
  735. ownDeviceHasBeenPublished,
  736. pend_names,
  737. receiveOwnMUCPresence,
  738. req_names,
  739. returnMemberLists,
  740. sendMessage,
  741. toggleControlBox,
  742. view_mode,
  743. waitForReservedNick,
  744. waitForRoster,
  745. waitUntilBookmarksReturned,
  746. waitUntilDiscoConfirmed
  747. });
  748. window.mock = mock;