main.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. (function() {
  2. 'use strict';
  3. var initialized = false;
  4. var i18n;
  5. // i18n key prefix for MUC ("muc.") or 1:1 chat ("chat.")
  6. var key_prefix;
  7. var display_data = null;
  8. function get_platform() {
  9. var ua = navigator.userAgent;
  10. switch (true) {
  11. case (ua.indexOf("Windows") >= 0):
  12. return "windows";
  13. case (ua.indexOf("Android") >= 0):
  14. case (ua.indexOf("CrOS") >= 0):
  15. return "android";
  16. case (ua.indexOf("iPad") >= 0):
  17. case (ua.indexOf("iPhone") >= 0):
  18. return "ios";
  19. case (ua.indexOf("Mac OS X") >= 0):
  20. case (ua.indexOf("Macintosh") >= 0):
  21. return "macos";
  22. case (ua.indexOf("Linux") >= 0):
  23. return "linux";
  24. case (true):
  25. }
  26. return "web";
  27. }
  28. function get_client_link_element(client_id, client_info, platform) {
  29. let item = document.createElement("div");
  30. let link = document.createElement("a");
  31. let img = document.createElement("img");
  32. link.setAttribute("href", client_info[platform]);
  33. let logo_url = client_info.logo || ("assets/" + client_id + ".svg");
  34. img.setAttribute("src", logo_url);
  35. if(star_apps && star_apps.includes(client_id)) {
  36. let star_el = document.createElement("div");
  37. star_el.innerText = "\u2B50";
  38. star_el.classList.add("star");
  39. item.classList.add("starred");
  40. link.append(star_el);
  41. }
  42. link.append(img, client_info.title);
  43. item.append(link);
  44. item.classList.add("client-link");
  45. return item;
  46. }
  47. function show_clients(all_clients) {
  48. var list = document.getElementById('client_list');
  49. let platform = get_platform();
  50. let clients = [];
  51. console.log(all_clients);
  52. // Filter to clients suitable for our platform
  53. for (var id in all_clients) {
  54. if(hidden_apps.includes(id)) {
  55. // Configured to never show this app
  56. continue;
  57. }
  58. let client_info = all_clients[id];
  59. if(client_info[platform] || client_info.web) {
  60. clients.push(id);
  61. }
  62. }
  63. // Sort the clients
  64. clients.sort(function (a, b) {
  65. let a_info = all_clients[a];
  66. let b_info = all_clients[b];
  67. // Prefer native clients
  68. let a_is_native = !!a_info[platform];
  69. let b_is_native = !!b_info[platform];
  70. if(a_is_native && !b_is_native) {
  71. return -1;
  72. } else if(b_is_native && !a_is_native) {
  73. return 1;
  74. }
  75. // Prefer starred clients
  76. if(star_apps && star_apps.length > 0) {
  77. let a_is_starred = star_apps.includes(a);
  78. let b_is_starred = star_apps.includes(b);
  79. if(a_is_starred && !b_is_starred) {
  80. return -1;
  81. } else if(b_is_starred && !a_is_starred) {
  82. return 1;
  83. }
  84. }
  85. // Sort lexically by title
  86. if(a_info.title < b_info.title) {
  87. return -1;
  88. } else if(b_info.title < a_info.title) {
  89. return 1;
  90. }
  91. return 0;
  92. });
  93. // Empty any existing content from the list element
  94. list.replaceChildren();
  95. // Generate links and add them to the list element
  96. for(var id of clients) {
  97. let el = get_client_link_element(id, all_clients[id], platform);
  98. list.append(el);
  99. }
  100. }
  101. function load_clients() {
  102. var request = new XMLHttpRequest();
  103. request.open('GET', "clients.json");
  104. request.onreadystatechange = function () {
  105. if (request.readyState === 4) {
  106. if (request.status === 200 || (isLocalFileRequest(url) && request.responseText.length > 0)) {
  107. let loaded_clients = JSON.parse(request.responseText);
  108. if(custom_apps && custom_apps.length > 0) {
  109. for(let custom_app in custom_apps) {
  110. loaded_clients[custom_app] = custom_apps[custom_app];
  111. }
  112. }
  113. if(only_apps && only_apps.length > 0) {
  114. for(let id in loaded_clients) {
  115. if(!only_apps.includes(id)) {
  116. delete loaded_clients[id];
  117. }
  118. }
  119. }
  120. show_clients(loaded_clients);
  121. }
  122. }
  123. };
  124. request.send(null);
  125. }
  126. function load_hash() {
  127. key_prefix = "chat";
  128. var xmpp_uri = window.location.search || window.location.hash;
  129. xmpp_uri = decodeURIComponent(xmpp_uri.substring(xmpp_uri.indexOf('#') + 1, xmpp_uri.length));
  130. if (xmpp_uri.indexOf("xmpp:") === 0) {
  131. xmpp_uri = xmpp_uri.slice(5);
  132. }
  133. try {
  134. base_decoded = window.atob(xmpp_uri);
  135. if (base_decoded.search('@') >= 0)
  136. xmpp_uri = base_decoded;
  137. } catch (err) {
  138. // ignore error, JID wasn't base64 encoded
  139. }
  140. if (xmpp_uri.search("\\?join") >= 0) {
  141. key_prefix = "muc";
  142. } else if(xmpp_uri.search("\\?register") >= 0) {
  143. key_prefix = "register";
  144. }
  145. // TODO: proper error checking / display / Creation of invitations
  146. if (xmpp_uri.search("@") <= 0) return {xmpp_uri:xmpp_uri, xmpp_uri_encoded:xmpp_uri, name: xmpp_uri.split("?")[0]};
  147. var xmpp_params = {};
  148. var xmpp_uri_parts = xmpp_uri.split("?");
  149. if (xmpp_uri_parts.length > 1) {
  150. let parameter, parameters = xmpp_uri_parts[1].split(";")
  151. for (parameter of parameters) {
  152. let key_value = parameter.split("=")
  153. xmpp_params[key_value[0]] = key_value.length > 1 ? key_value[1] : "";
  154. }
  155. }
  156. const jid_parts = xmpp_uri_parts[0].split("@");
  157. const local_part = jid_parts[0];
  158. xmpp_params["name"] = local_part.charAt(0).toUpperCase() + local_part.slice(1);
  159. const domain_part = jid_parts[1];
  160. const jid_encoded = encodeURIComponent(local_part) + "@" + encodeURIComponent(domain_part)
  161. xmpp_uri_parts[0] = jid_encoded
  162. const xmpp_uri_encoded = xmpp_uri_parts.join("?")
  163. return {xmpp_uri: xmpp_uri, xmpp_uri_encoded: xmpp_uri_encoded, name: xmpp_params["name"]};
  164. }
  165. let fallbackLocale = "en";
  166. let requested_fallback_locale = false;
  167. function get_translated_string(key, data) {
  168. return new Promise(function (resolve, reject) {
  169. try {
  170. return resolve(i18n.text(key, data));
  171. } catch {
  172. if(i18n.hasLocale(fallbackLocale)) {
  173. return resolve(i18n.text(key, data, fallbackLocale));
  174. }
  175. i18n.once(I18nText.event.LOCALE_LOAD, function () {
  176. try {
  177. return resolve(i18n.text(key, data, fallbackLocale));
  178. } catch {
  179. return resolve("UNTRANSLATED[" + key + "]");
  180. }
  181. });
  182. if(!requested_fallback_locale) {
  183. i18n.loadLocale(fallbackLocale);
  184. requested_fallback_locale = true;
  185. }
  186. }
  187. });
  188. }
  189. function translate_ui() {
  190. // translation
  191. get_translated_string(key_prefix + '.title', display_data).then(function (text) {
  192. document.title = text;
  193. });
  194. let translatable_els = document.querySelectorAll("[data-i18n]");
  195. translatable_els.forEach(function (el) {
  196. let key = el.dataset.i18n;
  197. if(key.startsWith(".")) {
  198. key = key_prefix + key;
  199. }
  200. get_translated_string(key, display_data).then(function (text) {
  201. let target = el.dataset.i18nTarget || "innerText";
  202. if(target.startsWith("@")) {
  203. el.setAttribute(target.substr(1), text);
  204. } else {
  205. el[target] = text;
  206. }
  207. });
  208. });
  209. }
  210. function rehash() {
  211. let hash = window.location.search || window.location.hash;
  212. if(!hash || hash == "#") {
  213. // Input mode
  214. document.getElementById("display-uri").style.display = "none";
  215. document.getElementById("enter-uri").style.display = "block";
  216. initialize_uri_input();
  217. key_prefix = "create";
  218. } else {
  219. document.getElementById("display-uri").style.display = "block";
  220. document.getElementById("enter-uri").style.display = "none";
  221. display_data = load_hash();
  222. document.getElementById('button').href = "xmpp:" + display_data.xmpp_uri_encoded;
  223. document.getElementById('url_in').value = "xmpp:" + display_data.xmpp_uri;
  224. }
  225. translate_ui();
  226. }
  227. function createQR() {
  228. display_data = load_hash();
  229. new QRCode(document.getElementById("qrcode"), "xmpp:" + display_data.xmpp_uri_encoded);
  230. }
  231. function generate_link() {
  232. let input_el = document.getElementById("uri_input");
  233. let output_el = document.getElementById("generated-link");
  234. let is_muc_el = document.getElementById("is_muc");
  235. let input = input_el.value;
  236. var uri;
  237. if(!(input.indexOf("xmpp:") == 0)) {
  238. uri = "xmpp:" + input;
  239. if(is_muc_el.checked) {
  240. uri += "?join";
  241. }
  242. is_muc_el.disabled = false;
  243. } else {
  244. uri = decodeURIComponent(input);
  245. is_muc_el.disabled = true;
  246. is_muc_el.checked = uri.endsWith("?join");
  247. }
  248. let encoded_uri = uri.substr(5).split("@").map(encodeURIComponent).join("@");
  249. let link = document.location.origin + document.location.pathname + "#" + encoded_uri;
  250. output_el.href = link;
  251. output_el.innerText = link;
  252. }
  253. function copy_to_clipboard() {
  254. let link = document.getElementById("generated-link");
  255. let copy_result_el = document.getElementById("copy-result");
  256. Promise.resolve().then(function () {
  257. return navigator.clipboard.writeText(link.href);
  258. }).then(function () {
  259. get_translated_string('copy-success', {}).then(function (text) {
  260. copy_result_el.innerText = text;
  261. });
  262. }, function () {
  263. get_translated_string('copy-failure', {}).then(function (text) {
  264. copy_result_el.innerText = text;
  265. });
  266. }).finally(function () {
  267. copy_result_el.style.visibility = "visible";
  268. });
  269. }
  270. function initialize_uri_input() {
  271. document.getElementById("generate-link-btn").addEventListener("click", function () {
  272. generate_link();
  273. document.getElementById("display-link").style.display = "block";
  274. });
  275. document.getElementById("uri_input").addEventListener("input", generate_link);
  276. document.getElementById("uri_input").addEventListener("keyup", function(event) {
  277. event.preventDefault();
  278. if (event.keyCode === 13) {
  279. document.getElementById("generate-link-btn").click();
  280. }
  281. });
  282. document.getElementById("is_muc").addEventListener("change", generate_link);
  283. document.getElementById("copy-link").addEventListener("click", copy_to_clipboard);
  284. }
  285. function load_done() {
  286. if (initialized) return;
  287. initialized = true;
  288. // load i18n and perform translation
  289. i18n = new I18nText({path: 'lang'});
  290. i18n.once(I18nText.event.LOCALE_CHANGE, function (data) {
  291. rehash();
  292. });
  293. var preferredLocale, setLocale = false;
  294. for (preferredLocale of navigator.languages) {
  295. if (supportedLocales.includes(preferredLocale)) {
  296. i18n.setLocale(preferredLocale);
  297. setLocale = true;
  298. break;
  299. }
  300. }
  301. if (!setLocale) {
  302. i18n.setLocale(defaultLocale);
  303. }
  304. var rtlLangs = "ar, fa, he, ur"
  305. if (rtlLangs.includes(navigator.language)) {
  306. document.querySelector("body").dir = "rtl";
  307. }
  308. // functionality
  309. var ua = navigator.userAgent;
  310. load_clients();
  311. window.addEventListener("hashchange", rehash, false);
  312. document.getElementById("url_in").addEventListener("focus", function(event) {
  313. event.target.select();
  314. });
  315. }
  316. // Wait for the DOM to be ready
  317. document.addEventListener('DOMContentLoaded', load_done, false);
  318. document.onreadystatechange = function() {
  319. if (document.readyState === 'interactive') {
  320. load_done();
  321. }
  322. };
  323. var logo = document.createElement('img');
  324. logo.src = 'assets/xmpp.svg';
  325. logo.alt= 'XMPP logo';
  326. logo.width = 60;
  327. var link = document.createElement('a');
  328. link.href = 'https://xmpp.org/';
  329. link.append(logo)
  330. var brand = document.getElementById('xmpp');
  331. brand.append(link)
  332. })();