utils.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import {rawEmojis} from "./emojis";
  2. import beep from "../sounds/beep.mp3";
  3. import juntos from "../sounds/juntos.mp3";
  4. import pristine from "../sounds/pristine.mp3";
  5. import ding from "../sounds/ding.mp3";
  6. import dadum from "../sounds/dadum.mp3";
  7. import pop from "../sounds/pop.mp3";
  8. import popSwoosh from "../sounds/pop-swoosh.mp3";
  9. import config from "./config";
  10. import {Base64} from 'js-base64';
  11. export const topicUrl = (baseUrl, topic) => `${baseUrl}/${topic}`;
  12. export const topicUrlWs = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/ws`
  13. .replaceAll("https://", "wss://")
  14. .replaceAll("http://", "ws://");
  15. export const topicUrlJson = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/json`;
  16. export const topicUrlJsonPoll = (baseUrl, topic) => `${topicUrlJson(baseUrl, topic)}?poll=1`;
  17. export const topicUrlJsonPollWithSince = (baseUrl, topic, since) => `${topicUrlJson(baseUrl, topic)}?poll=1&since=${since}`;
  18. export const topicUrlAuth = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/auth`;
  19. export const topicShortUrl = (baseUrl, topic) => shortUrl(topicUrl(baseUrl, topic));
  20. export const userStatsUrl = (baseUrl) => `${baseUrl}/user/stats`;
  21. export const shortUrl = (url) => url.replaceAll(/https?:\/\//g, "");
  22. export const expandUrl = (url) => [`https://${url}`, `http://${url}`];
  23. export const expandSecureUrl = (url) => `https://${url}`;
  24. export const validUrl = (url) => {
  25. return url.match(/^https?:\/\//);
  26. }
  27. export const validTopic = (topic) => {
  28. if (disallowedTopic(topic)) {
  29. return false;
  30. }
  31. return topic.match(/^([-_a-zA-Z0-9]{1,64})$/); // Regex must match Go & Android app!
  32. }
  33. export const disallowedTopic = (topic) => {
  34. return config.disallowedTopics.includes(topic);
  35. }
  36. // Format emojis (see emoji.js)
  37. const emojis = {};
  38. rawEmojis.forEach(emoji => {
  39. emoji.aliases.forEach(alias => {
  40. emojis[alias] = emoji.emoji;
  41. });
  42. });
  43. const toEmojis = (tags) => {
  44. if (!tags) return [];
  45. else return tags.filter(tag => tag in emojis).map(tag => emojis[tag]);
  46. }
  47. export const formatTitleWithDefault = (m, fallback) => {
  48. if (m.title) {
  49. return formatTitle(m);
  50. }
  51. return fallback;
  52. };
  53. export const formatTitle = (m) => {
  54. const emojiList = toEmojis(m.tags);
  55. if (emojiList.length > 0) {
  56. return `${emojiList.join(" ")} ${m.title}`;
  57. } else {
  58. return m.title;
  59. }
  60. };
  61. export const formatMessage = (m) => {
  62. if (m.title) {
  63. return m.message;
  64. } else {
  65. const emojiList = toEmojis(m.tags);
  66. if (emojiList.length > 0) {
  67. return `${emojiList.join(" ")} ${m.message}`;
  68. } else {
  69. return m.message;
  70. }
  71. }
  72. };
  73. export const unmatchedTags = (tags) => {
  74. if (!tags) return [];
  75. else return tags.filter(tag => !(tag in emojis));
  76. }
  77. export const maybeWithBasicAuth = (headers, user) => {
  78. if (user) {
  79. headers['Authorization'] = `Basic ${encodeBase64(`${user.username}:${user.password}`)}`;
  80. }
  81. return headers;
  82. }
  83. export const basicAuth = (username, password) => {
  84. return `Basic ${encodeBase64(`${username}:${password}`)}`;
  85. }
  86. export const encodeBase64 = (s) => {
  87. return Base64.encode(s);
  88. }
  89. export const encodeBase64Url = (s) => {
  90. return Base64.encodeURI(s);
  91. }
  92. export const shuffle = (arr) => {
  93. let j, x;
  94. for (let index = arr.length - 1; index > 0; index--) {
  95. j = Math.floor(Math.random() * (index + 1));
  96. x = arr[index];
  97. arr[index] = arr[j];
  98. arr[j] = x;
  99. }
  100. return arr;
  101. }
  102. export const splitNoEmpty = (s, delimiter) => {
  103. return s
  104. .split(delimiter)
  105. .map(x => x.trim())
  106. .filter(x => x !== "");
  107. }
  108. /** Non-cryptographic hash function, see https://stackoverflow.com/a/8831937/1440785 */
  109. export const hashCode = async (s) => {
  110. let hash = 0;
  111. for (let i = 0; i < s.length; i++) {
  112. const char = s.charCodeAt(i);
  113. hash = ((hash<<5)-hash)+char;
  114. hash = hash & hash; // Convert to 32bit integer
  115. }
  116. return hash;
  117. }
  118. export const formatShortDateTime = (timestamp) => {
  119. return new Intl.DateTimeFormat('default', {dateStyle: 'short', timeStyle: 'short'})
  120. .format(new Date(timestamp * 1000));
  121. }
  122. export const formatBytes = (bytes, decimals = 2) => {
  123. if (bytes === 0) return '0 bytes';
  124. const k = 1024;
  125. const dm = decimals < 0 ? 0 : decimals;
  126. const sizes = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
  127. const i = Math.floor(Math.log(bytes) / Math.log(k));
  128. return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
  129. }
  130. export const openUrl = (url) => {
  131. window.open(url, "_blank", "noopener,noreferrer");
  132. };
  133. export const sounds = {
  134. "beep": beep,
  135. "juntos": juntos,
  136. "pristine": pristine,
  137. "ding": ding,
  138. "dadum": dadum,
  139. "pop": pop,
  140. "pop-swoosh": popSwoosh
  141. };
  142. export const playSound = async (sound) => {
  143. const audio = new Audio(sounds[sound]);
  144. return audio.play();
  145. };
  146. // From: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
  147. export async function* fetchLinesIterator(fileURL, headers) {
  148. const utf8Decoder = new TextDecoder('utf-8');
  149. const response = await fetch(fileURL, {
  150. headers: headers
  151. });
  152. const reader = response.body.getReader();
  153. let { value: chunk, done: readerDone } = await reader.read();
  154. chunk = chunk ? utf8Decoder.decode(chunk) : '';
  155. const re = /\n|\r|\r\n/gm;
  156. let startIndex = 0;
  157. for (;;) {
  158. let result = re.exec(chunk);
  159. if (!result) {
  160. if (readerDone) {
  161. break;
  162. }
  163. let remainder = chunk.substr(startIndex);
  164. ({ value: chunk, done: readerDone } = await reader.read());
  165. chunk = remainder + (chunk ? utf8Decoder.decode(chunk) : '');
  166. startIndex = re.lastIndex = 0;
  167. continue;
  168. }
  169. yield chunk.substring(startIndex, result.index);
  170. startIndex = re.lastIndex;
  171. }
  172. if (startIndex < chunk.length) {
  173. yield chunk.substr(startIndex); // last line didn't end in a newline char
  174. }
  175. }