kmc.js 63 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747
  1. /*
  2. Verification Process:
  3. - Start verification from other device.
  4. - Run: `window.mClient.getVerificationRequestsToDeviceInProgress(userId)[0].accept();`
  5. - The other device will then ask about doing a QR or emoji thing. Select emoji.
  6. - Run `window.mClient.getVerificationRequestsToDeviceInProgress(userId)[0].verifier.doVerification();`
  7. - Once you see the emojis on the other device, the emoji data will appear in `window.mClient.getVerificationRequestsToDeviceInProgress(userId)[0].verifier.sasEvent.sas.emoji`. There is also a `.decimal` prop in the `sas` object.
  8. - Probably should display those emojis to so you can compare.
  9. - Run `window.mClient.getVerificationRequestsToDeviceInProgress(userId)[0].verifier.sasEvent.confirm();`
  10. - You're verified!
  11. */
  12. const LOCAL_STORAGE_SESSION_KEY = 'matrix-session';
  13. var initSync = false;
  14. var highlightedRoom = 0;
  15. var rooms = [];
  16. var highlightedMsg;
  17. var page = 'login';
  18. var currentRoom;
  19. var highlightedLoginInput = 0;
  20. var scrollMsg;
  21. var editingEvent;
  22. var replyingToEvent;
  23. var previousConv;
  24. var mainMenu = [];
  25. var highlightedMenuItem = 0;
  26. var verificationPhase = null;
  27. var currentlyWatching = null;
  28. var previousPage = 'rooms';
  29. var loadingMessagesIndex = 0;
  30. const swapMiddle = () => {
  31. document.getElementById('middle').innerHTML = 'REPLAY';
  32. };
  33. const backToChat = () => {
  34. const chat = document.getElementById('chat');
  35. chat.style.display = 'flex';
  36. document.getElementById('chat-textbox').focus();
  37. }
  38. const parseBody = (body) => {
  39. if(body) {
  40. const reg = /\b(https|http)?:\/\/\S+/gi;
  41. const urls = body.match(reg);
  42. const withoutUrls = body.split(reg);
  43. var i = -1;
  44. const parsedBody = withoutUrls.reduce((a, str) => {
  45. if(str === 'https' || str === 'http') {
  46. i++;
  47. return a + `<a target="_blank" href="${urls[i]}">${urls[i]}</a>`;
  48. } else {
  49. return a + str;
  50. }
  51. }, '');
  52. return parsedBody;
  53. }
  54. return body;
  55. }
  56. const getFormattedBody = (content = {}) => {
  57. if(content.formatted_body && content.formatted_body.indexOf('<mx-reply>') > -1) {
  58. const fBodyformattedBody = content.formatted_body;
  59. const mxReplyIndex = fBodyformattedBody.indexOf('</mx-reply>');
  60. const body = fBodyformattedBody && fBodyformattedBody.slice(mxReplyIndex + 11);
  61. const formattedBody = fBodyformattedBody && fBodyformattedBody.substring(0, mxReplyIndex).substring(10) + '<br/>' + body;
  62. return [formattedBody, body];
  63. }
  64. const parsedBody = parseBody(content.body);
  65. return [parsedBody, content.body];
  66. };
  67. const ALLOWED_BLOB_MIMETYPES = [
  68. 'image/jpeg',
  69. 'image/gif',
  70. 'image/png',
  71. 'image/apng',
  72. 'image/webp',
  73. 'image/avif',
  74. 'video/mp4',
  75. 'video/webm',
  76. 'video/ogg',
  77. 'video/quicktime',
  78. 'audio/mp4',
  79. 'audio/webm',
  80. 'audio/aac',
  81. 'audio/mpeg',
  82. 'audio/ogg',
  83. 'audio/wave',
  84. 'audio/wav',
  85. 'audio/x-wav',
  86. 'audio/x-pn-wav',
  87. 'audio/flac',
  88. 'audio/x-flac',
  89. ];
  90. const getBlobSafeMimeType = (mimetype) => {
  91. if (!ALLOWED_BLOB_MIMETYPES.includes(mimetype)) {
  92. return 'application/octet-stream';
  93. }
  94. return mimetype;
  95. };
  96. const getBlobData = (id, url, content, type) => {
  97. let file;
  98. let mimetype;
  99. if(type === 'image') {
  100. mimetype = content.info.mimetype ? content.info.mimetype.split(";")[0].trim() : '';
  101. file = content.file;
  102. } else if(type === 'video') {
  103. mimetype = content.info.thumbnail_info.mimetype ? content.info.thumbnail_info.mimetype.split(";")[0].trim() : '';
  104. file = content.info.thumbnail_file;
  105. } else if (type === 'audio') {
  106. mimetype = content.info.mimetype ? content.info.mimetype.split(";")[0].trim() : '';
  107. file = content.file;
  108. } else {
  109. return;
  110. }
  111. fetch(url).then((res) => {
  112. res.arrayBuffer().then((a) => {
  113. decryptAttachment(a, file).then((dataArray) => {
  114. mimetype = getBlobSafeMimeType(mimetype);
  115. const blob = new Blob([dataArray], { type: mimetype });
  116. const blobUrl = URL.createObjectURL(blob);
  117. const image = document.getElementById(`image-${id}`);
  118. image.src = blobUrl;
  119. if(type === 'video') {
  120. image.setAttribute('video', JSON.stringify(content));
  121. } else if( type === 'audio') {
  122. image.setAttribute('audio', JSON.stringify(content));
  123. }
  124. });
  125. })
  126. });
  127. };
  128. const messageBuilder = (event, room) => {
  129. const content = event.getContent();
  130. const sender = event.getSender();
  131. const date = event.getDate();
  132. const dateString = date.toLocaleString();
  133. const relation = event.getRelation();
  134. const members = room ? room.getMembers() : [];
  135. const shouldDisplayName = sender !== window.mClient.getUserId() && members.length > 2;
  136. const user = shouldDisplayName && room.getMember(sender);
  137. const floatDirection = sender === window.mClient.getUserId() ?
  138. 'align-self:flex-end;margin-right:10px;background-color:#4dc860' :
  139. 'align-self:flex-start;margin-left:10px;background-color:white';
  140. const dateDirection = sender === window.mClient.getUserId() ?
  141. 'text-align:right;margin-right:10px;' :
  142. 'text-align:left;margin-left:10px;';
  143. const reactDirection = sender === window.mClient.getUserId() ?
  144. 'justify-content:flex-end;margin-right:10px;' :
  145. 'justify-content:flex-start;margin-left:10px;';
  146. const [formattedBody, realBody] = getFormattedBody(content);
  147. const isSending = event.getId().indexOf(currentRoom) > -1;
  148. const dateOrSending = isSending ? `<p id="datetime-${event.getId()}" style="font-size:10px;color:gray;${dateDirection}" datetime="${dateString}">Sending...</p>`: `<p id="datetime-${event.getId()}" style="font-size:10px;color:gray;${dateDirection}">${dateString}</p>`;
  149. const nameOrNothing = user ? `<p id="name-${event.getId()}" style="font-size:10px;color:gray;${dateDirection}" datetime="${dateString}">${user.name}</p>` : '';
  150. window.mClient.sendReadReceipt(event, "m.read");
  151. if(content.msgtype === 'm.image') {
  152. const imageUrl = content.url ? window.mClient.mxcUrlToHttp(content.url) : window.mClient.mxcUrlToHttp(content.file.url);
  153. if (!content.url) {
  154. getBlobData(event.getId(), imageUrl, content, 'image');
  155. }
  156. return `
  157. <div id="msg-${event.getId()}" class="msgItem" sender="${sender}">
  158. <div id="innermsg-${event.getId()}" class="msgInnerItem" style="${floatDirection}">
  159. <img id="image-${event.getId()}" src="${imageUrl}" height=100 o_h="${content.info.h}" o_w="${content.info.w}" alt="${content.body}">
  160. </img>
  161. </div>
  162. ${dateOrSending}
  163. <div id="reactions-${event.getId()}" class="reactions" style="${reactDirection}"></div>
  164. </div>
  165. `;
  166. } else if(content.msgtype === 'm.video') {
  167. const imageUrl = content.info.thumbnail_file ? window.mClient.mxcUrlToHttp(content.info.thumbnail_file.url) : window.mClient.mxcUrlToHttp(content.info.thumbnail_url);
  168. if (content.info.thumbnail_file) {
  169. getBlobData(event.getId(), imageUrl, content, 'video');
  170. } else {
  171. setTimeout(() => {
  172. const image = document.getElementById(`image-${event.getId()}`);
  173. image.setAttribute('video', JSON.stringify(content));
  174. }, 200);
  175. }
  176. return `
  177. <div id="msg-${event.getId()}" class="msgItem" sender="${sender}">
  178. ${nameOrNothing}
  179. <div id="innermsg-${event.getId()}" class="msgInnerItem" style="${floatDirection}">
  180. <div class="play-button-container">
  181. <div class="play-button">
  182. </div>
  183. </div>
  184. <img id="image-${event.getId()}" src="${imageUrl}" height=100 alt="${content.body}">
  185. </img>
  186. </div>
  187. ${dateOrSending}
  188. <div id="reactions-${event.getId()}" class="reactions" style="${reactDirection}"></div>
  189. </div>
  190. `;
  191. } else if(content.msgtype === 'm.audio') {
  192. const imageUrl = content.url ? window.mClient.mxcUrlToHttp(content.url) : window.mClient.mxcUrlToHttp(content.file.url);
  193. if (!content.url) {
  194. getBlobData(event.getId(), imageUrl, content, 'audio');
  195. } else {
  196. setTimeout(() => {
  197. const image = document.getElementById(`image-${event.getId()}`);
  198. image.setAttribute('audio', JSON.stringify(content));
  199. }, 200);
  200. }
  201. return `
  202. <div id="msg-${event.getId()}" class="msgItem" sender="${sender}">
  203. ${nameOrNothing}
  204. <div id="innermsg-${event.getId()}" class="msgInnerItem" style="${floatDirection}">
  205. <pre id="image-${event.getId()}" audio="${JSON.stringify(content)}">
  206. ▶ ${content.body}
  207. </pre>
  208. </div>
  209. ${dateOrSending}
  210. <div id="reactions-${event.getId()}" class="reactions" style="${reactDirection}"></div>
  211. </div>
  212. `;
  213. } else {
  214. return `
  215. <div id="msg-${event.getId()}" class="msgItem" sender="${sender}">
  216. ${nameOrNothing}
  217. <div id="innermsg-${event.getId()}" class="msgInnerItem" style="${floatDirection}" body="${realBody}">
  218. <pre>
  219. ${formattedBody}
  220. </pre>
  221. </div>
  222. ${dateOrSending}
  223. <div id="reactions-${event.getId()}" class="reactions" style="${reactDirection}"></div>
  224. </div>
  225. `
  226. }
  227. };
  228. const gotoRoomView = () => {
  229. if(initSync) {
  230. highlightedRoom = 0;
  231. }
  232. const roomz = window.mClient.getRooms();
  233. const roomList = roomz.sort((roomA, roomB) => {
  234. return roomA.name.localeCompare(roomB.name);
  235. }).reduce((a, room) => {
  236. if (a === '<div>') {
  237. return a + `
  238. <div id="room-${room.roomId}" class="roomItem active">
  239. ${room.name}
  240. </div>
  241. <br/>
  242. `;
  243. } else {
  244. return a + `
  245. <div id="room-${room.roomId}" class="roomItem">
  246. ${room.name}
  247. </div>
  248. <br/>
  249. `;
  250. }
  251. }, '<div>'); + '</div>'
  252. document.getElementById('room-menu').style = 'display: unset';
  253. document.getElementById('chat').style = 'display: none';
  254. document.getElementById('menu').style = 'display: none';
  255. document.getElementById('chat-textbox').value = '';
  256. document.getElementById('chat-textbox').selectionStart = 0
  257. document.getElementById('chat-textbox').style.height = '24px';
  258. document.getElementById('conversation').style.height = '100%';
  259. document.getElementById('editing').style.bottom = '55px';
  260. document.getElementById('replying').style.bottom = '55px';
  261. document.getElementById('login').style = 'display: none';
  262. document.getElementById("left").innerHTML = "";
  263. document.getElementById("right").innerHTML = "Menu";
  264. document.getElementById("middle").innerHTML = "SELECT";
  265. document.getElementById("title-text").innerHTML = 'Rooms';
  266. document.getElementById("room-menu").innerHTML = roomList;
  267. initSync = true;
  268. rooms = document.getElementsByClassName('roomItem');
  269. page = 'rooms';
  270. highlightedMsg = null;
  271. };
  272. const markReaction = (event, relation) => {
  273. const {
  274. event_id,
  275. rel_type,
  276. key
  277. } = relation;
  278. const msgItem = document.getElementById(`msg-${event_id}`);
  279. const reactionsEl = document.getElementById(`reactions-${event_id}`);
  280. if(msgItem && reactionsEl) {
  281. reactionsEl.innerHTML = reactionsEl.innerHTML + `
  282. <div id="react-${event.getId()}" class="${relation.key} ${event.getSender()}" style="font-size:16px;">
  283. ${relation.key}
  284. </div>
  285. `
  286. // if the last message gets reacted, scroll down to see it
  287. const msgs = document.getElementsByClassName('msgItem');
  288. if(msgItem === msgs[msgs.length - 1]) {
  289. const conversationEl = document.getElementById("conversation");
  290. conversationEl.scrollTo(0, conversationEl.scrollHeight);
  291. }
  292. }
  293. };
  294. const removeReaction = (eventId) => {
  295. const reaction = document.getElementById(`react-${eventId}`);
  296. if(reaction)
  297. reaction.remove();
  298. };
  299. const replaceBody = (event, relation) => {
  300. const content = event.getContent();
  301. const innerMsgItem = document.getElementById(`innermsg-${relation.event_id}`);
  302. const newBody = content['m.new_content'].body;
  303. const hasFormattedBody = innerMsgItem.innerHTML.indexOf('blockquote') > -1;
  304. const parsedBody = parseBody(newBody);
  305. const allOfIt = parsedBody + '<br/><b class="edited-marker" style="color:black">(edited)</b>';
  306. if(innerMsgItem) {
  307. innerMsgItem.setAttribute('body', newBody);
  308. if(hasFormattedBody) {
  309. const blockquote = innerMsgItem.getElementsByTagName('blockquote')[0];
  310. innerMsgItem.innerHTML = `
  311. <pre>
  312. ${blockquote.outerHTML}
  313. <br/>
  314. ${allOfIt}
  315. `;
  316. } else {
  317. innerMsgItem.innerHTML = `
  318. <pre>
  319. ${allOfIt}
  320. </pre>
  321. `;
  322. }
  323. }
  324. };
  325. var timeout;
  326. const prepareSession = (client) => {
  327. document.getElementById("room-menu").innerHTML = "Loading..."
  328. document.getElementById("title-text").innerHTML = 'Rooms';
  329. document.getElementById("left").innerHTML = "";
  330. document.getElementById("right").innerHTML = "";
  331. document.getElementById("middle").innerHTML = "SELECT";
  332. window.mClient = window.matrixcs.createClient({
  333. baseUrl: client.baseUrl,
  334. accessToken: client.access_token,
  335. userId: client.user_id,
  336. deviceId: client.device_id,
  337. sessionStore: new window.matrixcs.WebStorageSessionStore(localStorage),
  338. });
  339. window.mClient.on("sync", function(event) {
  340. if(!initSync) {
  341. gotoRoomView();
  342. }
  343. });
  344. window.mClient.on("crypto.verification.request", function(event, room, toStartOfTimeline) {
  345. if(verificationPhase === 'accept') {
  346. verificationPhase = 'sas';
  347. event.accept().then(() => {
  348. alert('Accepted verification! On the other device, select emoji verification, then press ok here.');
  349. event.waitFor((e) => {
  350. if(e.verifier && verificationPhase) {
  351. verificationPhase = 'sas';
  352. e.verifier.doVerification();
  353. setTimeout(() => {
  354. if(e.verifier.sasEvent) {
  355. const emojis = e.verifier.sasEvent.sas.emoji.reduce((a, em) => {
  356. return a + em[0] + ' ' + em[1] + '\n';
  357. }, '');
  358. if(confirm('Match?\n' + emojis)) {
  359. e.verifier.sasEvent.confirm();
  360. } else {
  361. e.verifier.sasEvent.mismatch();
  362. }
  363. }
  364. }, 3000);
  365. clearTimeout(timeout);
  366. }
  367. });
  368. timeout = setTimeout(() => {
  369. verificationPhase = null;
  370. }, 2 * 60 * 1000); // you get 2 minutes
  371. }).catch(() => {
  372. alert('Failed to accept verification!');
  373. verificationPhase = null;
  374. });
  375. }
  376. });
  377. window.mClient.on("Room.timeline", function(event, room, toStartOfTimeline) {
  378. window.mClient.decryptEventIfNeeded(event).then(() => {
  379. const eventType = event.getType();
  380. const relation = event.getRelation();
  381. if (page === 'chat' && currentRoom === room.roomId ) {
  382. switch(eventType) {
  383. case 'm.room.message': {
  384. if(!relation) {
  385. const newMsg = messageBuilder(event, room);
  386. const conversationEl = document.getElementById("conversation");
  387. const typing = document.getElementsByClassName('typing');
  388. let newTyping = '';
  389. for(var i = 0; i < typing.length; i++) {
  390. const div = typing[i];
  391. div.remove();
  392. newTyping = newTyping + div.outerHTML;
  393. }
  394. if(loadingMessagesIndex > 0) {
  395. conversationEl.innerHTML = newMsg + conversationEl.innerHTML + newTyping;
  396. loadingMessagesIndex = loadingMessagesIndex - 1;
  397. highlightedMsg = highlightedMsg + 1;
  398. } else {
  399. conversationEl.innerHTML = conversationEl.innerHTML + newMsg + newTyping;
  400. }
  401. if(document.activeElement.id === 'chat-textbox') {
  402. conversationEl.scrollTo(0, conversationEl.scrollHeight);
  403. } else if(loadingMessagesIndex === 0) {
  404. const cur = document.getElementsByClassName('msgItem msgItem-selected')[0];
  405. cur.scrollIntoView({
  406. behavor: 'smooth',
  407. block: 'bottom',
  408. inline: 'nearest'
  409. });
  410. }
  411. } else {
  412. if(relation.rel_type === 'm.replace')
  413. replaceBody(event, relation);
  414. }
  415. return;
  416. }
  417. case 'm.reaction': {
  418. if(loadingMessagesIndex > 0) {
  419. loadingMessagesIndex = loadingMessagesIndex - 1;
  420. }
  421. if(relation && relation.rel_type === 'm.annotation') {
  422. markReaction(event, relation);
  423. }
  424. return;
  425. }
  426. case 'm.room.redaction': {
  427. if(loadingMessagesIndex > 0) {
  428. loadingMessagesIndex = loadingMessagesIndex - 1;
  429. }
  430. removeReaction(event.event.redacts);
  431. return;
  432. }
  433. }
  434. }
  435. });
  436. });
  437. window.mClient.on("RoomMember.typing", function(event, member) {
  438. const room = window.mClient.getRoom(currentRoom);
  439. if(room) {
  440. const members = room.getMembers().map(({userId}) => userId);
  441. if (page === 'chat' && members.indexOf(member.userId) > -1) {
  442. if (member.typing) {
  443. const typingDiv = `<div id="typing-${member.userId}" class="typing">${member.name} is typing...</div>`;
  444. const conv = document.getElementById('conversation');
  445. conv.innerHTML = conv.innerHTML + typingDiv;
  446. // if(conv.clientHeight - conv.scrollTop < 20) {
  447. // conv.scrollTo(0, conv.scrollHeight);
  448. // }
  449. }
  450. else {
  451. const typingDiv = document.getElementById(`typing-${member.userId}`);
  452. if(typingDiv)
  453. typingDiv.remove();
  454. }
  455. }
  456. }
  457. });
  458. // window.mClient.on("Room.receipt", function(event, member) {
  459. // const content = event.getContent();
  460. // const reads = Object.keys(content);
  461. // reads.forEach((eventId) => {
  462. // if(page === 'chat') {
  463. // const msg = document.getElementById(`msg-${eventId}`);
  464. // if(member.userId !== msg.getAttribute('sender')) {
  465. // const dateTime = document.getElementById(`datetime-${eventId}`);
  466. // if(dateTime)
  467. // dateTime.innerHTML = 'read <br/>' + dateTime.innerHTML;
  468. // }
  469. // }
  470. // })
  471. // })
  472. window.mClient.initCrypto().then(() => {
  473. window.mClient.startClient({
  474. initialSyncLimit: 10,
  475. lazyLoadMembers: true,
  476. });
  477. });
  478. };
  479. const login = (baseUrl, user, password) => {
  480. const client = window.matrixcs.createClient(baseUrl);
  481. client.loginWithPassword(user, password).then((d) => {
  482. const session = {...d, baseUrl};
  483. localStorage.setItem(LOCAL_STORAGE_SESSION_KEY, JSON.stringify(session));
  484. prepareSession(session);
  485. }).catch((e) => {
  486. console.error(e);
  487. alert('Login error! Check all inputs.')
  488. document.getElementById('middle').innerHTML = 'LOGIN';
  489. });
  490. };
  491. const sendReaction = (emoji) => {
  492. const el = document.getElementsByClassName('msgItem msgItem-selected')[0];
  493. if(el) {
  494. if(el.innerHTML.indexOf(emoji) > -1) { // has one already
  495. const react = el.getElementsByClassName(`${emoji} ${window.mClient.getUserId()}`)[0];
  496. if (react) {
  497. const eventId = react.id.substring(6);
  498. return window.mClient.redactEvent(currentRoom, eventId).then(() => {
  499. console.log('redacted!');
  500. }).catch((e) => {
  501. console.log(e);
  502. alert('Failed to redact reaction to message: ', JSON.stringify(e));
  503. });
  504. }
  505. }
  506. const event_id = el.id.substring(4);
  507. const content = {
  508. 'm.relates_to': {
  509. key: emoji,
  510. event_id,
  511. rel_type: 'm.annotation',
  512. }
  513. };
  514. const date = new Date();
  515. const tnxId = date.getTime()/1000;
  516. window.mClient.sendEvent(currentRoom, "m.reaction", content, tnxId).then(({event_id}) => {
  517. const room = window.mClient.getRoom(currentRoom);
  518. const newReact = document.getElementById(`react-~${room.roomId}:${tnxId}`);
  519. newReact.id = `react-${event_id}`;
  520. }).catch((e) => {
  521. console.log(e);
  522. alert(e.message);
  523. });
  524. }
  525. };
  526. const killSession = () => {
  527. window.mClient.stopClient();
  528. localStorage.removeItem(LOCAL_STORAGE_SESSION_KEY);
  529. close();
  530. };
  531. const verifyDevice = () => {
  532. if(confirm('Before starting the verification process on the other device, press okay here and wait.')) {
  533. verificationPhase = 'accept';
  534. }
  535. }
  536. const verifyDevices = () => {
  537. if(confirm('If this error was because of unverified devices, would you like to verify all devices in this room?')) {
  538. const members = window.mClient.getRoom(currentRoom).getMembers();
  539. Promise.all(members.map((member) => {
  540. const userId = member.userId;
  541. const devices = window.mClient.getStoredDevicesForUser(userId);
  542. devices.filter(({verified}) => verified === 0).map((dev) => {
  543. return window.mClient.setDeviceVerified(userId, dev.deviceId, true);
  544. });
  545. })).then(() => {
  546. alert('All devices in this room are now marked as verified!');
  547. }).catch(() => {
  548. alert('An error occurred while marking devices as verified.');
  549. });
  550. }
  551. }
  552. const openMenu = () => {
  553. page = 'menu';
  554. document.getElementById("left").innerHTML = "";
  555. document.getElementById("right").innerHTML = "Back";
  556. document.getElementById("middle").innerHTML = "SELECT";
  557. const menuList = mainMenu && mainMenu.reduce((a, item) => {
  558. if (a === '') {
  559. return a + `
  560. <div id="menu-${item.label}" class="menuItem active">
  561. ${item.label}
  562. </div>
  563. <br/>
  564. `;
  565. } else {
  566. return a + `
  567. <div id="menu-${item.label}" class="menuItem">
  568. ${item.label}
  569. </div>
  570. <br/>
  571. `;
  572. }
  573. }, '');
  574. const menu = document.getElementById('menu');
  575. const room = document.getElementById('room-menu');
  576. const chat = document.getElementById('chat');
  577. const input = document.getElementById('chat-textbox');
  578. menu.style.display = 'unset';
  579. room.style.display = 'none';
  580. chat.style.display = 'none';
  581. input.blur();
  582. menu.innerHTML = menuList;
  583. };
  584. const closeMenu = (goBack) => {
  585. page = previousPage;
  586. const menu = document.getElementById('menu');
  587. menu.style.display = 'none';
  588. goBack();
  589. };
  590. const progressHandler = ({loaded, total}) => {
  591. const percent = Math.round(loaded / total * 100);
  592. const uploading = document.getElementById('uploading');
  593. uploading.style.bottom = "55px";
  594. uploading.style.display = "unset";
  595. uploading.innerHTML = percent + "%";
  596. };
  597. const loadingHandler = () => {
  598. const chatBox = document.getElementById('chat-textbox');
  599. const uploading = document.getElementById('uploading');
  600. uploading.style.bottom = (chatBox.scrollHeight + 55 - 29) + "px";
  601. uploading.style.display = "unset";
  602. uploading.innerHTML = "Loading...";
  603. };
  604. const uploadContent = (file, img) => {
  605. const uploading = document.getElementById('uploading');
  606. return new Promise((res, rej) => {
  607. if(window.mClient.isRoomEncrypted(currentRoom)) {
  608. const reader = new FileReader();
  609. reader.onload = () => {
  610. const attachment = encryptAttachment(reader.result).then((encryptedResult) => {
  611. const blob = new Blob([encryptedResult.data]);
  612. window.mClient.uploadContent(blob, {
  613. includeFilename: false,
  614. progressHandler,
  615. }).then((url) => {
  616. uploading.style.display = "none";
  617. let content;
  618. if(file.type.indexOf('image') > -1) {
  619. content = {
  620. msgtype: 'm.image',
  621. body: file.name,
  622. info: {
  623. h: img.height,
  624. w: img.width,
  625. mimetype: file.type,
  626. size: file.size,
  627. },
  628. file: {
  629. ...encryptedResult.info,
  630. url,
  631. }
  632. };
  633. } else if(file.type.indexOf('video') > -1) {
  634. content = {
  635. msgtype: 'm.video',
  636. body: file.name,
  637. info: {
  638. mimetype: file.type,
  639. size: file.size,
  640. },
  641. file: {
  642. ...encryptedResult.info,
  643. url,
  644. }
  645. };
  646. } else if(file.type.indexOf('audio') > -1) {
  647. content = {
  648. msgtype: 'm.audio',
  649. body: file.name,
  650. info: {
  651. mimetype: file.type,
  652. size: file.size,
  653. },
  654. file: {
  655. ...encryptedResult.info,
  656. url,
  657. }
  658. };
  659. }
  660. res(content);
  661. }).catch((e) => {
  662. uploading.style.display = "none";
  663. alert('Failed to upload file!');
  664. });
  665. });
  666. };
  667. reader.readAsArrayBuffer(file);
  668. } else {
  669. window.mClient.uploadContent(file, {
  670. progressHandler,
  671. }).then((url) => {
  672. uploading.style.display = "none";
  673. let content;
  674. if(file.type.indexOf('image') > -1) {
  675. content = {
  676. msgtype: 'm.image',
  677. body: file.name,
  678. url,
  679. info: {
  680. h: img.height,
  681. w: img.width,
  682. mimetype: file.type,
  683. size: file.size,
  684. }
  685. };
  686. } else if(file.type.indexOf('video') > -1) {
  687. content = {
  688. msgtype: 'm.video',
  689. body: file.name,
  690. url,
  691. info: {
  692. mimetype: file.type,
  693. size: file.size,
  694. }
  695. };
  696. } else if(file.type.indexOf('audio') > -1) {
  697. content = {
  698. msgtype: 'm.audio',
  699. body: file.name,
  700. url,
  701. info: {
  702. mimetype: file.type,
  703. size: file.size,
  704. }
  705. };
  706. }
  707. res(content);
  708. }).catch((e) => {
  709. uploading.style.display = "none";
  710. alert('Failed to upload file!');
  711. });
  712. }
  713. });
  714. }
  715. const insertIntoTextbox = (v) => () => {
  716. const chatBox = document.getElementById('chat-textbox');
  717. chatBox.value = chatBox.value + v;
  718. closeMenu(backToChat);
  719. };
  720. const keydownListener = (e) => {
  721. const chatBox = document.getElementById('chat-textbox');
  722. if(page === 'login') {
  723. if(e.key === 'ArrowUp') {
  724. var inputs = document.getElementsByClassName('login-text');
  725. inputs[highlightedLoginInput].blur();
  726. highlightedLoginInput = highlightedLoginInput - 1;
  727. if(highlightedLoginInput === -1) {
  728. highlightedLoginInput = inputs.length - 1;
  729. }
  730. inputs[highlightedLoginInput].focus();
  731. } else if (e.key === 'ArrowDown') {
  732. var inputs = document.getElementsByClassName('login-text');
  733. inputs[highlightedLoginInput].blur();
  734. highlightedLoginInput = highlightedLoginInput + 1;
  735. if(highlightedLoginInput === inputs.length) {
  736. highlightedLoginInput = 0
  737. }
  738. inputs[highlightedLoginInput].focus();
  739. } else if (e.key === 'Enter' && document.getElementById('middle').innerHTML !== 'Loading...') {
  740. var baseUrl = document.getElementById('homeserver').value;
  741. var user = document.getElementById('user').value;
  742. var password = document.getElementById('password').value;
  743. document.getElementById('middle').innerHTML = 'Loading...'
  744. login(baseUrl, user, password);
  745. }
  746. return e;
  747. } else if(document.activeElement === chatBox) {
  748. const editing = document.getElementById('editing');
  749. const replying = document.getElementById('replying');
  750. const uploading = document.getElementById('uploading');
  751. if(e.key === 'End' || e.key === 'EndCall') {
  752. const chatBox = document.getElementById('chat-textbox');
  753. if(chatBox && chatBox.value !== '') {
  754. if(confirm('Are you sure you want to close the app?')) {
  755. window.mClient.stopClient();
  756. close();
  757. }
  758. return;
  759. }
  760. return;
  761. } else if(e.key === 'ArrowUp' && chatBox.selectionStart === 0) {
  762. chatBox.blur();
  763. var msgs = document.getElementsByClassName('msgItem');
  764. const cur = document.getElementsByClassName('msgItem msgItem-selected')[0];
  765. highlightedMsg = msgs.length - 1;
  766. msgs[highlightedMsg].className = 'msgItem msgItem-selected';
  767. msgs[highlightedMsg].scrollIntoView({
  768. behavor: 'smooth',
  769. block: 'center',
  770. inline: 'nearest'
  771. });
  772. if(cur)
  773. cur.className = 'msgItem';
  774. } else if (e.key === 'Call' || e.key === 'F1') {
  775. var msgs = document.getElementsByClassName('msgItem');
  776. if(msgs[highlightedMsg]) {
  777. chatBox.blur();
  778. highlightedMsg = highlightedMsg - 1;
  779. msgs[highlightedMsg].scrollIntoView({
  780. behavor: 'smooth',
  781. block: 'center',
  782. inline: 'nearest'
  783. });
  784. }
  785. } else if(e.key === 'Backspace' && chatBox.selectionStart === 0) {
  786. e.preventDefault();
  787. chatBox.blur();
  788. gotoRoomView(chatBox);
  789. } else if ((e.key === "SoftLeft" || e.key === "Control") && page === 'chat' && document.getElementById("left").innerHTML !== '') {
  790. if (editing.style.display === 'unset') { // editing
  791. const body = document.getElementById('chat-textbox').value.trim();
  792. const content = {
  793. body: ' * ' + body,
  794. msgtype: "m.text",
  795. 'm.relates_to': {
  796. event_id: editingEvent,
  797. rel_type: 'm.replace',
  798. },
  799. 'm.new_content': {
  800. body,
  801. msgtype: 'm.text',
  802. }
  803. };
  804. if (body !== '') {
  805. const date = new Date();
  806. const tnxId = date.getTime()/1000;
  807. loadingHandler();
  808. window.mClient.sendEvent(currentRoom, "m.room.message", content, tnxId).then(({event_id}) => {
  809. document.getElementById("left").innerHTML = "Send";
  810. document.getElementById("right").innerHTML = "Menu";
  811. document.getElementById("middle").innerHTML = "ENTER";
  812. document.getElementById("chat-textbox").value = '';
  813. document.getElementById("chat-textbox").disabled = false;
  814. document.getElementById("chat-textbox").style.height = "24px";
  815. document.getElementById('chat-textbox').selectionStart = 0
  816. document.getElementById('conversation').style.height = '100%';
  817. document.getElementById('editing').style.bottom = "55px";
  818. editing.style.display = 'none';
  819. uploading.style.display = 'none';
  820. editingEvent = null;
  821. // const msg = document.getElementsByClassName('msgItem msgItem-selected')[0];
  822. // msg.className = 'msgItem';
  823. // highlightedMsg = null;
  824. // const room = window.mClient.getRoom(currentRoom);
  825. // const newMsg = document.getElementById(`msg-~${room.roomId}:${tnxId}`);
  826. // const newInnerMsg = document.getElementById(`innermsg-~${room.roomId}:${tnxId}`);
  827. // newMsg.id = `msg-${event_id}`;
  828. // newInnerMsg.id = `innermsg-${event_id}`;
  829. return;
  830. }).catch((err) => {
  831. alert('Failed to send message: ' + err.message);
  832. if(err.name === 'UnknownDeviceError') {
  833. verifyDevices();
  834. }
  835. console.log(err);
  836. document.getElementById("left").innerHTML = "Send";
  837. document.getElementById("right").innerHTML = "Menu";
  838. document.getElementById("middle").innerHTML = "ENTER";
  839. document.getElementById("chat-textbox").disabled = false;
  840. document.getElementById("chat-textbox").focus();
  841. });
  842. }
  843. } else if (replying.style.display === 'unset') { // replying
  844. const body = document.getElementById('chat-textbox').value.trim();
  845. const content = {
  846. body: `> <${replyingToEvent.sender}> ${replyingToEvent.body}` + '\n\n' + body,
  847. format: 'org.matrix.custom.html',
  848. formatted_body: `<mx-reply><blockquote><a href=\"https://matrix.to/#/${currentRoom}/${replyingToEvent.eventId}">In reply to</a> <a href="https://matrix.to/#/${replyingToEvent.sender}">${replyingToEvent.sender}</a><br>${replyingToEvent.body}</blockquote></mx-reply>${body}`,
  849. msgtype: "m.text",
  850. 'm.relates_to': {
  851. 'm.in_reply_to': {
  852. event_id: replyingToEvent.eventId,
  853. },
  854. },
  855. };
  856. if (body !== '') {
  857. const date = new Date();
  858. const tnxId = date.getTime()/1000;
  859. document.getElementById("left").innerHTML = "Send";
  860. document.getElementById("right").innerHTML = "Menu";
  861. document.getElementById("middle").innerHTML = "ENTER";
  862. window.mClient.sendEvent(currentRoom, "m.room.message", content, tnxId).then(({event_id}) => {
  863. const room = window.mClient.getRoom(currentRoom);
  864. const newMsg = document.getElementById(`msg-~${room.roomId}:${tnxId}`);
  865. const newInnerMsg = document.getElementById(`innermsg-~${room.roomId}:${tnxId}`);
  866. const datetimeMsg = document.getElementById(`datetime-~${room.roomId}:${tnxId}`);
  867. newMsg.id = `msg-${event_id}`;
  868. newInnerMsg.id = `innermsg-${event_id}`;
  869. datetimeMsg.id = `datetime-${event_id}`;
  870. datetimeMsg.innerHTML = datetimeMsg.getAttribute('datetime');
  871. return;
  872. }).catch((err) => {
  873. alert('Failed to send message: ' + err.message);
  874. if(err.name === 'UnknownDeviceError') {
  875. verifyDevices();
  876. }
  877. console.log(err);
  878. });
  879. document.getElementById('chat-textbox').value = '';
  880. document.getElementById("chat-textbox").style.height = "24px";
  881. document.getElementById('chat-textbox').selectionStart = 0
  882. document.getElementById('conversation').style.height = '100%';
  883. document.getElementById('editing').style.bottom = "55px";
  884. replying.style.display = 'none';
  885. replyingToEvent = null;
  886. }
  887. } else {
  888. const body = document.getElementById('chat-textbox').value.trim();
  889. const content = {
  890. body,
  891. msgtype: "m.text"
  892. };
  893. if (body !== '') {
  894. // document.getElementById("chat-textbox").blur();
  895. // document.getElementById("chat-textbox").disabled = true;
  896. // document.getElementById("left").innerHTML = "";
  897. // document.getElementById("right").innerHTML = "";
  898. // document.getElementById("middle").innerHTML = "SENDING...";
  899. const date = new Date();
  900. const tnxId = date.getTime()/1000;
  901. window.mClient.sendEvent(currentRoom, "m.room.message", content, tnxId).then(({event_id}) => {
  902. const room = window.mClient.getRoom(currentRoom);
  903. const newMsg = document.getElementById(`msg-~${room.roomId}:${tnxId}`);
  904. const newInnerMsg = document.getElementById(`innermsg-~${room.roomId}:${tnxId}`);
  905. const datetimeMsg = document.getElementById(`datetime-~${room.roomId}:${tnxId}`);
  906. newMsg.id = `msg-${event_id}`;
  907. newInnerMsg.id = `innermsg-${event_id}`;
  908. datetimeMsg.id = `datetime-${event_id}`;
  909. datetimeMsg.innerHTML = datetimeMsg.getAttribute('datetime');
  910. return;
  911. }).catch((err) => {
  912. alert('Failed to send message: ' + err.message);
  913. if(err.name === 'UnknownDeviceError') {
  914. verifyDevices();
  915. }
  916. });
  917. document.getElementById('chat-textbox').value = '';
  918. document.getElementById("chat-textbox").style.height = "24px";
  919. document.getElementById('chat-textbox').selectionStart = 0
  920. document.getElementById('conversation').style.height = '100%';
  921. document.getElementById('replying').style.bottom = "55px";
  922. }
  923. }
  924. } else if ((e.key === "SoftRight" || e.key === "Shift") && page === 'chat' && document.getElementById("right").innerHTML !== '') { // cancel edit
  925. if(editing.style.display === 'unset' || replying.style.display === 'unset') {
  926. editing.style.display = 'none';
  927. replying.style.display = 'none';
  928. document.getElementById('chat-textbox').value = '';
  929. document.getElementById('chat-textbox').selectionStart = 0
  930. document.getElementById("chat-textbox").style.height = "24px";
  931. document.getElementById('editing').style.bottom = "55px";
  932. document.getElementById('replying').style.bottom = "55px";
  933. document.getElementById("left").innerHTML = "Send";
  934. document.getElementById("right").innerHTML = "Menu";
  935. document.getElementById("middle").innerHTML = "ENTER";
  936. editingEvent = null;
  937. replyingToEvent = null;
  938. const msg = document.getElementsByClassName('msgItem msgItem-selected')[0];
  939. msg.className = 'msgItem';
  940. highlightedMsg = null;
  941. } else {
  942. mainMenu = [
  943. {
  944. label: 'Send Attachment',
  945. fn: () => {
  946. new Promise((res, rej) => {
  947. try {
  948. const file = new MozActivity({name: 'pick'});
  949. file.onsuccess = function () {
  950. const f = this.result;
  951. res(f);
  952. };
  953. reader.onerror = function(e) {
  954. alert('Failed to read file.');
  955. };
  956. file.onerror = function() {
  957. alert('Failed to select file.');
  958. };
  959. } catch {
  960. var input = document.createElement('input');
  961. input.type = 'file';
  962. input.onchange = e => {
  963. var f = e.target.files[0];
  964. res(f);
  965. }
  966. input.click();
  967. }
  968. }).then((f) => {
  969. new Promise((res, rej) => {
  970. if(f.type.indexOf('image') > -1) {
  971. const reader = new FileReader();
  972. reader.onload = () => {
  973. const img = new Image();
  974. img.onload = () => {
  975. return res(img);
  976. };
  977. img.src = reader.result;
  978. };
  979. reader.readAsDataURL(f);
  980. } else {
  981. res(null);
  982. }
  983. }).then((img) => {
  984. closeMenu(backToChat);
  985. uploadContent(f, img).then((content) => {
  986. const date = new Date();
  987. const tnxId = date.getTime()/1000;
  988. window.mClient.sendEvent(currentRoom, "m.room.message", content, tnxId).then(({event_id}) => {
  989. const room = window.mClient.getRoom(currentRoom);
  990. const newMsg = document.getElementById(`msg-~${room.roomId}:${tnxId}`);
  991. const newInnerMsg = document.getElementById(`innermsg-~${room.roomId}:${tnxId}`);
  992. const img = document.getElementById(`image-~${room.roomId}:${tnxId}`);
  993. const datetimeMsg = document.getElementById(`datetime-~${room.roomId}:${tnxId}`);
  994. newMsg.id = `msg-${event_id}`;
  995. newInnerMsg.id = `innermsg-${event_id}`;
  996. datetimeMsg.id = `datetime-${event_id}`;
  997. datetimeMsg.innerHTML = datetimeMsg.getAttribute('datetime');
  998. img.id = `image-${event_id}`
  999. }).catch((e) => {
  1000. console.log(e);
  1001. });
  1002. });
  1003. });
  1004. });
  1005. },
  1006. },
  1007. {
  1008. label: 'Insert ☺️',
  1009. fn: insertIntoTextbox('☺️'),
  1010. },
  1011. {
  1012. label: 'Insert 😛️',
  1013. fn: insertIntoTextbox('😛️️'),
  1014. },
  1015. {
  1016. label: 'Insert 😕️',
  1017. fn: insertIntoTextbox('😕️️'),
  1018. },
  1019. {
  1020. label: 'Insert 👉👈',
  1021. fn: insertIntoTextbox('👉👈'),
  1022. },
  1023. {
  1024. label: 'Insert 👀',
  1025. fn: insertIntoTextbox('👀'),
  1026. },
  1027. {
  1028. label: 'Insert 🤔',
  1029. fn: insertIntoTextbox('🤔'),
  1030. },
  1031. ];
  1032. previousPage = 'chat';
  1033. openMenu();
  1034. }
  1035. }
  1036. } else {
  1037. e.preventDefault();
  1038. switch (e.key) {
  1039. case "F1":
  1040. case "Call": {
  1041. if(page === 'chat') {
  1042. const msgItem = document.getElementsByClassName('msgItem msgItem-selected')[0];
  1043. if(msgItem) {
  1044. const eventId = msgItem.id.substring(4);
  1045. const innerMsg = document.getElementById(`innermsg-${eventId}`);
  1046. let msg = innerMsg.innerText.trim();
  1047. if(innerMsg.getElementsByClassName('edited-marker').length > 0) {
  1048. msg = msg.slice(0, -9); // removed the "\n(edited)" part
  1049. }
  1050. const replying = document.getElementById('replying');
  1051. replying.style.display = 'unset';
  1052. replyingToEvent = {
  1053. eventId,
  1054. sender: msgItem.getAttribute('sender'),
  1055. body: msg,
  1056. };
  1057. const chatBox = document.getElementById('chat-textbox');
  1058. chatBox.focus();
  1059. highlightedMsg += 1;
  1060. if(chatBox.scrollHeight < 122) {
  1061. chatBox.style.height = "auto";
  1062. chatBox.style.height = (chatBox.scrollHeight) + "px";
  1063. document.getElementById('editing').style.bottom = (chatBox.scrollHeight + 55 - 29) + "px";
  1064. document.getElementById('replying').style.bottom = (chatBox.scrollHeight + 55 - 29) + "px";
  1065. }
  1066. }
  1067. }
  1068. return;
  1069. }
  1070. case "End":
  1071. case "EndCall": {
  1072. const chatBox = document.getElementById('chat-textbox');
  1073. if(chatBox && chatBox.value !== '') {
  1074. if(confirm('Are you sure you want to close the app?')) {
  1075. window.mClient.stopClient();
  1076. close();
  1077. }
  1078. return;
  1079. }
  1080. window.mClient.stopClient();
  1081. close();
  1082. return;
  1083. }
  1084. case "Backspace": {
  1085. if(page === 'chat') {
  1086. if(navigator.b2g && navigator.b2g.virtualCursor.enabled) {
  1087. navigator.b2g.virtualCursor.disable();
  1088. } else {
  1089. gotoRoomView();
  1090. }
  1091. } else if (page === 'image') {
  1092. const player = document.getElementById('big-video');
  1093. if(player) {
  1094. if(document.fullscreenElement && document.fullscreenElement !== null) {
  1095. return document.exitFullscreen();
  1096. }
  1097. player.removeEventListener('ended', swapMiddle);
  1098. }
  1099. const conv = document.getElementById('conversation');
  1100. conv.innerHTML = previousConv;
  1101. previousConv = null;
  1102. page = 'chat'
  1103. if(currentlyWatching) {
  1104. URL.revokeObjectURL(currentlyWatching);
  1105. currentlyWatching = null;
  1106. }
  1107. const msgItem = document.getElementsByClassName('msgItem msgItem-selected')[0];
  1108. if(msgItem) {
  1109. msgItem.scrollIntoView({
  1110. behavor: 'smooth',
  1111. block: 'center',
  1112. inline: 'nearest'
  1113. });
  1114. }
  1115. document.getElementById("left").innerHTML = "Reply";
  1116. document.getElementById("right").innerHTML = "Edit";
  1117. document.getElementById("middle").innerHTML = "SELECT";
  1118. } else if (page === 'menu') {
  1119. if(previousPage === 'rooms') {
  1120. closeMenu(gotoRoomView);
  1121. } else {
  1122. closeMenu(backToChat)
  1123. }
  1124. }
  1125. return;
  1126. }
  1127. case "Enter": {
  1128. if(page === 'rooms') {
  1129. gotoChat();
  1130. page = 'chat';
  1131. } else if(page === 'chat') {
  1132. const msgItem = document.getElementsByClassName('msgItem msgItem-selected')[0];
  1133. if(msgItem) {
  1134. const eventId = msgItem.id.substring(4);
  1135. const image = document.getElementById(`image-${eventId}`)
  1136. if(image) {
  1137. const video = image.getAttribute('video');
  1138. const audio = image.getAttribute('audio');
  1139. if(video) {
  1140. document.getElementById('left').innerHTML = 'Fullscreen'
  1141. document.getElementById('middle').innerHTML = 'PLAY'
  1142. document.getElementById('right').innerHTML = 'Mute'
  1143. const content = JSON.parse(video);
  1144. const imageUrl = content.url ? window.mClient.mxcUrlToHttp(content.url) : window.mClient.mxcUrlToHttp(content.file.url);
  1145. loadingHandler();
  1146. fetch(imageUrl).then((res) => {
  1147. res.arrayBuffer().then((a) => {
  1148. const uploading = document.getElementById('uploading');
  1149. if(uploading) {
  1150. uploading.style.display = 'none';
  1151. }
  1152. if(content.file) {
  1153. decryptAttachment(a, content.file).then((dataArray) => {
  1154. let mimetype = content.info.mimetype ? content.info.mimetype.split(";")[0].trim() : '';
  1155. mimetype = getBlobSafeMimeType(mimetype);
  1156. const blob = new Blob([dataArray], { type: mimetype });
  1157. const blobUrl = URL.createObjectURL(blob);
  1158. currentlyWatching = blobUrl;
  1159. const conv = document.getElementById('conversation');
  1160. previousConv = conv.innerHTML;
  1161. conv.innerHTML = `
  1162. <video id="big-video"/>
  1163. <source src="${blobUrl}">
  1164. Unsupported Video
  1165. </video>
  1166. `;
  1167. page = 'image';
  1168. });
  1169. } else {
  1170. let mimetype = content.info.mimetype ? content.info.mimetype.split(";")[0].trim() : '';
  1171. mimetype = getBlobSafeMimeType(mimetype);
  1172. const blob = new Blob([a], { type: mimetype });
  1173. const blobUrl = URL.createObjectURL(blob);
  1174. currentlyWatching = blobUrl;
  1175. const conv = document.getElementById('conversation');
  1176. previousConv = conv.innerHTML;
  1177. conv.innerHTML = `
  1178. <video id="big-video"/>
  1179. <source src="${blobUrl}">
  1180. Unsupported Video
  1181. </video>
  1182. `;
  1183. page = 'image';
  1184. }
  1185. });
  1186. });
  1187. } else if (audio) {
  1188. const content = JSON.parse(audio);
  1189. const imageUrl = content.url ? window.mClient.mxcUrlToHttp(content.url) : window.mClient.mxcUrlToHttp(content.file.url);
  1190. document.getElementById('left').innerHTML = 'Fullscreen'
  1191. document.getElementById('middle').innerHTML = 'PLAY'
  1192. document.getElementById('right').innerHTML = 'Mute'
  1193. loadingHandler();
  1194. fetch(imageUrl).then((res) => {
  1195. res.arrayBuffer().then((a) => {
  1196. const uploading = document.getElementById('uploading');
  1197. if(uploading) {
  1198. uploading.style.display = 'none';
  1199. }
  1200. if(content.file) {
  1201. decryptAttachment(a, content.file).then((dataArray) => {
  1202. let mimetype = content.info.mimetype ? content.info.mimetype.split(";")[0].trim() : '';
  1203. mimetype = getBlobSafeMimeType(mimetype);
  1204. const blob = new Blob([dataArray], { type: mimetype });
  1205. const blobUrl = URL.createObjectURL(blob);
  1206. currentlyWatching = blobUrl;
  1207. const conv = document.getElementById('conversation');
  1208. previousConv = conv.innerHTML;
  1209. conv.innerHTML = `
  1210. <audio id="big-video"/>
  1211. <source src="${blobUrl}">
  1212. Unsupported Video
  1213. </audio>
  1214. `;
  1215. page = 'image';
  1216. });
  1217. } else {
  1218. let mimetype = content.info.mimetype ? content.info.mimetype.split(";")[0].trim() : '';
  1219. mimetype = getBlobSafeMimeType(mimetype);
  1220. const blob = new Blob([a], { type: mimetype });
  1221. const blobUrl = URL.createObjectURL(blob);
  1222. currentlyWatching = blobUrl;
  1223. const conv = document.getElementById('conversation');
  1224. previousConv = conv.innerHTML;
  1225. conv.innerHTML = `
  1226. <audio id="big-video"/>
  1227. <source src="${blobUrl}">
  1228. Unsupported Video
  1229. </audio>
  1230. `;
  1231. page = 'image';
  1232. }
  1233. })
  1234. });
  1235. } else {
  1236. const imageUrl = image.getAttribute('src');
  1237. const conv = document.getElementById('conversation');
  1238. previousConv = conv.innerHTML;
  1239. conv.innerHTML = `<img id="big-image" src="${imageUrl}"/>`;
  1240. page = 'image';
  1241. document.getElementById('left').innerHTML = 'Zoom Out'
  1242. document.getElementById('middle').innerHTML = ''
  1243. document.getElementById('right').innerHTML = 'Zoom In'
  1244. }
  1245. } else {
  1246. navigator.b2g.virtualCursor.enable();
  1247. }
  1248. }
  1249. } else if(page === 'menu') {
  1250. const item = mainMenu[highlightedMenuItem];
  1251. if(item) {
  1252. item.fn();
  1253. }
  1254. } else if(page === 'image') {
  1255. const player = document.getElementById('big-video');
  1256. if(player) {
  1257. if(player.paused) {
  1258. document.getElementById('middle').innerHTML = 'PAUSE';
  1259. player.play();
  1260. player.addEventListener('ended', swapMiddle)
  1261. } else {
  1262. document.getElementById('middle').innerHTML = 'PLAY';
  1263. player.pause();
  1264. player.removeEventListener('ended', swapMiddle)
  1265. }
  1266. }
  1267. }
  1268. return;
  1269. }
  1270. case "ArrowUp": {
  1271. if(page === 'rooms') {
  1272. const cur = rooms[highlightedRoom];
  1273. let num = highlightedRoom - 1
  1274. if(num < 0) {
  1275. num = rooms.length - 1;
  1276. }
  1277. const next = rooms[num];
  1278. cur.className = "roomItem";
  1279. next.className = "roomItem active";
  1280. highlightedRoom = num;
  1281. } else if(page === 'chat') {
  1282. var msgs = document.getElementsByClassName('msgItem');
  1283. if (highlightedMsg === 0) {
  1284. document.getElementById('left').innerHTML = ''
  1285. document.getElementById('middle').innerHTML = 'Loading...'
  1286. document.getElementById('right').innerHTML = ''
  1287. const room = window.mClient.getRoom(currentRoom);
  1288. const timeline = room.getLiveTimeline();
  1289. const originalLength = msgs.length;
  1290. loadingMessagesIndex = 10;
  1291. window.mClient.paginateEventTimeline(timeline, {backwards: true, limit: 10}).then(() => {
  1292. document.getElementById('left').innerHTML = 'Reply'
  1293. document.getElementById('middle').innerHTML = 'SELECT';
  1294. document.getElementById('right').innerHTML = 'Edit'
  1295. });
  1296. return;
  1297. } else if(highlightedMsg !== msgs.length) {
  1298. msgs[highlightedMsg].className = 'msgItem';
  1299. }
  1300. highlightedMsg = highlightedMsg - 1;
  1301. msgs[highlightedMsg].className = 'msgItem msgItem-selected';
  1302. msgs[highlightedMsg].scrollIntoView({
  1303. behavor: 'smooth',
  1304. block: 'nearest',
  1305. inline: 'nearest'
  1306. });
  1307. } else if (page === 'image') {
  1308. const conv = document.getElementById('conversation');
  1309. conv.scrollBy({
  1310. top: -100,
  1311. });
  1312. } else if (page === 'menu') {
  1313. const menuItems = document.getElementsByClassName('menuItem');
  1314. const cur = menuItems[highlightedMenuItem];
  1315. let num = highlightedMenuItem - 1
  1316. if(num < 0) {
  1317. num = menuItems.length - 1;
  1318. }
  1319. const next = menuItems[num];
  1320. cur.className = "menuItem";
  1321. next.className = "menuItem active";
  1322. highlightedMenuItem = num;
  1323. }
  1324. return;
  1325. }
  1326. case "ArrowDown": {
  1327. if(page === 'rooms') {
  1328. const cur = rooms[highlightedRoom];
  1329. let num = highlightedRoom + 1
  1330. if(num > rooms.length - 1) {
  1331. num = 0;
  1332. }
  1333. const next = rooms[num];
  1334. cur.className = "roomItem";
  1335. next.className = "roomItem active";
  1336. highlightedRoom = num;
  1337. } else if (page === 'chat') {
  1338. var msgs = document.getElementsByClassName('msgItem');
  1339. var mNext = highlightedMsg + 1;
  1340. if(mNext === msgs.length && !scrollMsg) {
  1341. msgs[highlightedMsg].className = 'msgItem';
  1342. highlightedMsg = msgs.length;
  1343. document.getElementById('chat-textbox').focus();
  1344. } else {
  1345. if ((msgs[mNext] && msgs[mNext].clientHeight > document.getElementById('conversation').clientHeight) || scrollMsg) {
  1346. if(!scrollMsg) {
  1347. scrollMsg = msgs[mNext].clientHeight;
  1348. document.getElementById('conversation').scrollBy({
  1349. top: 100,
  1350. left: 0,
  1351. behavor: 'smooth',
  1352. });
  1353. scrollMsg = scrollMsg - 100;
  1354. } else if(scrollMsg <= document.getElementById('conversation').clientHeight - 100) {
  1355. scrollMsg = null;
  1356. } else {
  1357. document.getElementById('conversation').scrollBy({
  1358. top: 100,
  1359. left: 0,
  1360. behavor: 'smooth',
  1361. });
  1362. scrollMsg = scrollMsg - 100;
  1363. return;
  1364. }
  1365. }
  1366. if (mNext >= msgs.length) {
  1367. msgs[highlightedMsg].className = 'msgItem';
  1368. document.getElementById('chat-textbox').focus();
  1369. } else {
  1370. msgs[highlightedMsg].className = 'msgItem';
  1371. highlightedMsg = mNext
  1372. msgs[highlightedMsg].className = 'msgItem msgItem-selected';
  1373. msgs[highlightedMsg].scrollIntoView({
  1374. behavor: 'smooth',
  1375. block: 'nearest',
  1376. inline: 'nearest'
  1377. });
  1378. }
  1379. }
  1380. } else if (page === 'image') {
  1381. const conv = document.getElementById('conversation');
  1382. conv.scrollBy({
  1383. top: 100,
  1384. });
  1385. } else if (page === 'menu') {
  1386. const menuItems = document.getElementsByClassName('menuItem');
  1387. const cur = menuItems[highlightedMenuItem];
  1388. let num = highlightedMenuItem + 1
  1389. if(num === menuItems.length) {
  1390. num = 0
  1391. }
  1392. const next = menuItems[num];
  1393. cur.className = "menuItem";
  1394. next.className = "menuItem active";
  1395. highlightedMenuItem = num;
  1396. }
  1397. return;
  1398. }
  1399. case "ArrowLeft": {
  1400. if (page === 'image') {
  1401. const conv = document.getElementById('conversation');
  1402. conv.scrollBy({
  1403. left: -100,
  1404. });
  1405. }
  1406. return;
  1407. }
  1408. case "ArrowRight": {
  1409. if (page === 'image') {
  1410. const conv = document.getElementById('conversation');
  1411. conv.scrollBy({
  1412. left: 100,
  1413. });
  1414. }
  1415. return;
  1416. }
  1417. case "Shift":
  1418. case "SoftRight": {
  1419. if(page === 'chat') {
  1420. const msgItem = document.getElementsByClassName('msgItem msgItem-selected')[0];
  1421. if(msgItem) {
  1422. const eventId = msgItem.id.substring(4);
  1423. const innerMsg = document.getElementById(`innermsg-${eventId}`);
  1424. if(innerMsg.style['align-self'] === 'flex-end') {
  1425. let msg = innerMsg.getAttribute('body').trim();
  1426. document.getElementById('chat-textbox').value = msg;
  1427. const editing = document.getElementById('editing');
  1428. editing.style.display = 'unset';
  1429. editingEvent = eventId;
  1430. const chatBox = document.getElementById('chat-textbox');
  1431. chatBox.focus();
  1432. highlightedMsg += 1;
  1433. if(chatBox.scrollHeight < 122) {
  1434. chatBox.style.height = "auto";
  1435. chatBox.style.height = (chatBox.scrollHeight) + "px";
  1436. document.getElementById('editing').style.bottom = (chatBox.scrollHeight + 55 - 29) + "px";
  1437. document.getElementById('replying').style.bottom = (chatBox.scrollHeight + 55 - 29) + "px";
  1438. }
  1439. }
  1440. }
  1441. } else if (page === 'image') {
  1442. const image = document.getElementById('big-image');
  1443. const player = document.getElementById('big-video');
  1444. if(image) {
  1445. const height = Number(image.getAttribute('height'));
  1446. image.setAttribute('height', 25 + (height ? height : image.scrollHeight))
  1447. const width = Number(image.getAttribute('width'));
  1448. image.setAttribute('width', 25 + (width ? width : image.scrollWidth));
  1449. } else if(player) {
  1450. if(player.muted) {
  1451. document.getElementById("right").innerHTML = "Mute";
  1452. player.muted = false;
  1453. } else {
  1454. document.getElementById("right").innerHTML = "Unmute";
  1455. player.muted = true;
  1456. }
  1457. }
  1458. } else if (page === 'rooms') {
  1459. mainMenu = [
  1460. {
  1461. label: 'Verify Device',
  1462. fn: verifyDevice,
  1463. },
  1464. {
  1465. label: 'Logout',
  1466. fn: killSession,
  1467. },
  1468. ];
  1469. previousPage = 'rooms';
  1470. openMenu();
  1471. } else if (page === 'menu') {
  1472. if(previousPage === 'rooms') {
  1473. closeMenu(gotoRoomView);
  1474. } else {
  1475. closeMenu(backToChat);
  1476. }
  1477. }
  1478. return;
  1479. }
  1480. case "Control":
  1481. case "SoftLeft": {
  1482. if(page === 'chat') {
  1483. const msgItem = document.getElementsByClassName('msgItem msgItem-selected')[0];
  1484. if(msgItem) {
  1485. const eventId = msgItem.id.substring(4);
  1486. const innerMsg = document.getElementById(`innermsg-${eventId}`);
  1487. let msg = innerMsg.innerText.trim();
  1488. if(innerMsg.getElementsByClassName('edited-marker').length > 0) {
  1489. msg = msg.slice(0, -9); // removed the "\n(edited)" part
  1490. }
  1491. const replying = document.getElementById('replying');
  1492. replying.style.display = 'unset';
  1493. replyingToEvent = {
  1494. eventId,
  1495. sender: msgItem.getAttribute('sender'),
  1496. body: msg,
  1497. };
  1498. const chatBox = document.getElementById('chat-textbox');
  1499. chatBox.focus();
  1500. highlightedMsg += 1;
  1501. if(chatBox.scrollHeight < 122) {
  1502. chatBox.style.height = "auto";
  1503. chatBox.style.height = (chatBox.scrollHeight) + "px";
  1504. document.getElementById('editing').style.bottom = (chatBox.scrollHeight + 55 - 29) + "px";
  1505. document.getElementById('replying').style.bottom = (chatBox.scrollHeight + 55 - 29) + "px";
  1506. }
  1507. }
  1508. } else if (page === 'image') {
  1509. const image = document.getElementById('big-image');
  1510. const player = document.getElementById('big-video');
  1511. if(image) {
  1512. const height = Number(image.getAttribute('height'));
  1513. image.setAttribute('height', (height ? height : image.scrollHeight) - 25 )
  1514. const width = Number(image.getAttribute('width'));
  1515. image.setAttribute('width', (width ? width : image.scrollWidth) - 25);
  1516. } else if (player) {
  1517. if(document.fullscreenElement && document.fullscreenElement !== null) {
  1518. document.exitFullscreen();
  1519. } else {
  1520. player.requestFullscreen();
  1521. }
  1522. }
  1523. }
  1524. return;
  1525. }
  1526. case "1": {
  1527. sendReaction('👍');
  1528. return;
  1529. }
  1530. case "2": {
  1531. sendReaction('👎');
  1532. return;
  1533. }
  1534. case "3": {
  1535. sendReaction('❤️');
  1536. return;
  1537. }
  1538. case "4": {
  1539. sendReaction('🔥');
  1540. return;
  1541. }
  1542. case "5": {
  1543. sendReaction('😁');
  1544. return;
  1545. }
  1546. case "6": {
  1547. sendReaction('🎉');
  1548. return;
  1549. }
  1550. case "7": {
  1551. sendReaction('🤣');
  1552. return;
  1553. }
  1554. case "8": {
  1555. sendReaction('🤔');
  1556. return;
  1557. }
  1558. case "9": {
  1559. sendReaction('🤯');
  1560. return;
  1561. }
  1562. case "0": {
  1563. sendReaction('👀');
  1564. return;
  1565. }
  1566. case "#": {
  1567. sendReaction('😭');
  1568. return;
  1569. }
  1570. case "*": {
  1571. sendReaction('💩');
  1572. return;
  1573. }
  1574. }
  1575. }
  1576. };
  1577. document.addEventListener("DOMContentLoaded", async () => {
  1578. const session = localStorage.getItem(LOCAL_STORAGE_SESSION_KEY);
  1579. if(session) {
  1580. const client = JSON.parse(session);
  1581. prepareSession(client);
  1582. } else {
  1583. document.getElementById("title-text").innerHTML = 'Login';
  1584. document.getElementById("login").style = 'display: unset';
  1585. document.getElementById("left").innerHTML = "";
  1586. document.getElementById("right").innerHTML = "";
  1587. document.getElementById("middle").innerHTML = "LOGIN";
  1588. document.getElementById('homeserver').focus();
  1589. }
  1590. const chatBox = document.getElementById('chat-textbox');
  1591. chatBox.addEventListener("focus", (e) => {
  1592. const editing = document.getElementById('editing');
  1593. const replying = document.getElementById('replying');
  1594. if(editing.style.display === 'unset') { // is editting
  1595. document.getElementById("left").innerHTML = "Save";
  1596. document.getElementById("right").innerHTML = "Discard";
  1597. document.getElementById("middle").innerHTML = "ENTER";
  1598. } else if (replying.style.display === 'unset') {
  1599. document.getElementById("left").innerHTML = "Send";
  1600. document.getElementById("right").innerHTML = "Discard";
  1601. document.getElementById("middle").innerHTML = "ENTER";
  1602. } else { // new msg
  1603. document.getElementById("left").innerHTML = "Send";
  1604. document.getElementById("right").innerHTML = "Menu";
  1605. document.getElementById("middle").innerHTML = "ENTER";
  1606. const scrollHeight = document.getElementById("conversation").scrollHeight;
  1607. document.getElementById("conversation").scrollTo(0, scrollHeight);
  1608. }
  1609. });
  1610. chatBox.addEventListener("blur", (e) => {
  1611. if(page === 'chat') {
  1612. document.getElementById("left").innerHTML = "Reply";
  1613. document.getElementById("right").innerHTML = "Edit";
  1614. document.getElementById("middle").innerHTML = "SELECT";
  1615. }
  1616. });
  1617. // auto resize the text boxes
  1618. const tx = document.getElementsByTagName("textarea");
  1619. for (let i = 0; i < tx.length; i++) {
  1620. tx[i].setAttribute("style", "height:24px;overflow-y:hidden;");
  1621. tx[i].addEventListener("input", OnInput, false);
  1622. };
  1623. function OnInput() {
  1624. if(this.scrollHeight < 122) {
  1625. this.style.height = "auto";
  1626. this.style.height = (this.scrollHeight) + "px";
  1627. const conv = document.getElementById("conversation");
  1628. conv.style.height = `calc(100% - ${this.scrollHeight + 52}px)`;
  1629. if(document.getElementById('editing').style.display === 'none'
  1630. &&
  1631. document.getElementById('replying').style.display === 'none') {
  1632. conv.scrollTo(0, conv.scrollHeight);
  1633. }
  1634. document.getElementById('editing').style.bottom = (this.scrollHeight + 55 - 29) + "px";
  1635. document.getElementById('replying').style.bottom = (this.scrollHeight + 55 - 29) + "px";
  1636. }
  1637. };
  1638. });
  1639. const gotoChat = () => {
  1640. const roomEl = document.getElementsByClassName('roomItem active')[0];
  1641. const id = roomEl.id.substring(5);
  1642. const room = window.mClient.getRoom(id);
  1643. // const timeline = room.getLiveTimeline();
  1644. // const rest = window.mClient.paginateEventTimeline(timeline, {backwards: true}).then((d) => {
  1645. const events = window.mClient.getRoom(id).timeline;
  1646. var relations = [];
  1647. const eventList = events.reduce((a, event) => {
  1648. const relation = event.getRelation();
  1649. if(relation) {
  1650. relations.push({event, relation});
  1651. return a + '';
  1652. }
  1653. if (event.getType() === 'm.room.message') {
  1654. // window.mClient.decryptEventIfNeeded(event, {isRetry: false, emit: true}).then((d) => {
  1655. // const events = window.mClient.getRoom(id).timeline;
  1656. // console.log('DECRYPTED: ', d);
  1657. // })
  1658. return a + messageBuilder(event, room);
  1659. }
  1660. return a + '';
  1661. }, '');
  1662. document.getElementById("conversation").innerHTML = eventList;
  1663. relations.forEach(({event, relation}) => {
  1664. const {
  1665. event_id,
  1666. rel_type,
  1667. } = relation;
  1668. const msgItem = document.getElementById(`msg-${event_id}`);
  1669. if(msgItem) {
  1670. if(rel_type === 'm.annotation') {
  1671. markReaction(event, relation);
  1672. } else if(rel_type === 'm.replace') {
  1673. replaceBody(event, relation);
  1674. }
  1675. }
  1676. return;
  1677. });
  1678. document.getElementById('room-menu').style = 'display: none';
  1679. document.getElementById('chat').style = 'display: flex';
  1680. document.getElementById('chat-textbox').value = '';
  1681. document.getElementById('chat-textbox').focus();
  1682. document.getElementById("title-text").innerHTML = room.name;
  1683. document.getElementById("left").innerHTML = "Send";
  1684. document.getElementById("right").innerHTML = "Menu";
  1685. document.getElementById("middle").innerHTML = "ENTER";
  1686. setTimeout(() => {
  1687. const scrollHeight = document.getElementById("conversation").scrollHeight;
  1688. document.getElementById("conversation").scrollTo(0, scrollHeight);
  1689. }, 500);
  1690. currentRoom = id;
  1691. };
  1692. document.addEventListener("keydown", keydownListener);
  1693. document.addEventListener('click', (event) => {
  1694. if(event.target.localName === 'a') {
  1695. if(confirm('Open in popup?')) {
  1696. event.preventDefault();
  1697. window.open(event.target.href);
  1698. }
  1699. }
  1700. });