2022-02-26 04:25:04 +00:00
|
|
|
import {rawEmojis} from "./emojis";
|
2022-03-09 20:58:21 +00:00
|
|
|
import beep from "../sounds/beep.mp3";
|
|
|
|
import juntos from "../sounds/juntos.mp3";
|
|
|
|
import pristine from "../sounds/pristine.mp3";
|
|
|
|
import ding from "../sounds/ding.mp3";
|
|
|
|
import dadum from "../sounds/dadum.mp3";
|
|
|
|
import pop from "../sounds/pop.mp3";
|
|
|
|
import popSwoosh from "../sounds/pop-swoosh.mp3";
|
2022-03-10 04:28:55 +00:00
|
|
|
import config from "./config";
|
2022-03-10 23:11:12 +00:00
|
|
|
import {Base64} from 'js-base64';
|
2022-02-24 17:26:07 +00:00
|
|
|
|
2022-02-18 19:41:01 +00:00
|
|
|
export const topicUrl = (baseUrl, topic) => `${baseUrl}/${topic}`;
|
2022-02-24 15:30:58 +00:00
|
|
|
export const topicUrlWs = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/ws`
|
2022-02-18 19:41:01 +00:00
|
|
|
.replaceAll("https://", "wss://")
|
|
|
|
.replaceAll("http://", "ws://");
|
2022-02-23 04:22:30 +00:00
|
|
|
export const topicUrlJson = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/json`;
|
|
|
|
export const topicUrlJsonPoll = (baseUrl, topic) => `${topicUrlJson(baseUrl, topic)}?poll=1`;
|
2022-02-26 16:45:39 +00:00
|
|
|
export const topicUrlJsonPollWithSince = (baseUrl, topic, since) => `${topicUrlJson(baseUrl, topic)}?poll=1&since=${since}`;
|
2022-02-25 18:40:03 +00:00
|
|
|
export const topicUrlAuth = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/auth`;
|
2022-02-26 04:25:04 +00:00
|
|
|
export const topicShortUrl = (baseUrl, topic) => shortUrl(topicUrl(baseUrl, topic));
|
2022-02-18 19:41:01 +00:00
|
|
|
export const shortUrl = (url) => url.replaceAll(/https?:\/\//g, "");
|
2022-03-08 19:13:32 +00:00
|
|
|
export const expandUrl = (url) => [`https://${url}`, `http://${url}`];
|
2022-03-08 20:19:15 +00:00
|
|
|
export const expandSecureUrl = (url) => `https://${url}`;
|
2022-02-23 04:22:30 +00:00
|
|
|
|
2022-03-29 19:22:26 +00:00
|
|
|
export const splitTopicUrl = (url) => {
|
|
|
|
if (!validTopicUrl(url)) {
|
|
|
|
throw new Error("Invalid topic URL");
|
|
|
|
}
|
|
|
|
const parts = url.split("/");
|
|
|
|
if (parts.length < 2) {
|
|
|
|
throw new Error("Invalid topic URL");
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
baseUrl: parts.slice(0, parts.length-1).join("/"),
|
|
|
|
topic: parts[parts.length-1]
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2022-02-28 21:56:38 +00:00
|
|
|
export const validUrl = (url) => {
|
|
|
|
return url.match(/^https?:\/\//);
|
|
|
|
}
|
|
|
|
|
2022-03-29 19:22:26 +00:00
|
|
|
export const validTopicUrl = (url) => {
|
|
|
|
return url.match(/^https?:\/\/.+\/.*[^/]/); // At least one other slash
|
|
|
|
}
|
|
|
|
|
2022-02-26 16:45:39 +00:00
|
|
|
export const validTopic = (topic) => {
|
2022-03-10 04:28:55 +00:00
|
|
|
if (disallowedTopic(topic)) {
|
|
|
|
return false;
|
|
|
|
}
|
2022-02-28 21:56:38 +00:00
|
|
|
return topic.match(/^([-_a-zA-Z0-9]{1,64})$/); // Regex must match Go & Android app!
|
2022-02-26 16:45:39 +00:00
|
|
|
}
|
|
|
|
|
2022-03-10 04:28:55 +00:00
|
|
|
export const disallowedTopic = (topic) => {
|
|
|
|
return config.disallowedTopics.includes(topic);
|
|
|
|
}
|
|
|
|
|
2022-02-24 17:26:07 +00:00
|
|
|
// Format emojis (see emoji.js)
|
|
|
|
const emojis = {};
|
|
|
|
rawEmojis.forEach(emoji => {
|
|
|
|
emoji.aliases.forEach(alias => {
|
|
|
|
emojis[alias] = emoji.emoji;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
const toEmojis = (tags) => {
|
|
|
|
if (!tags) return [];
|
|
|
|
else return tags.filter(tag => tag in emojis).map(tag => emojis[tag]);
|
|
|
|
}
|
|
|
|
|
2022-03-04 17:10:11 +00:00
|
|
|
export const formatTitleWithDefault = (m, fallback) => {
|
2022-02-26 16:45:39 +00:00
|
|
|
if (m.title) {
|
|
|
|
return formatTitle(m);
|
|
|
|
}
|
|
|
|
return fallback;
|
|
|
|
};
|
|
|
|
|
2022-02-24 17:26:07 +00:00
|
|
|
export const formatTitle = (m) => {
|
|
|
|
const emojiList = toEmojis(m.tags);
|
|
|
|
if (emojiList.length > 0) {
|
|
|
|
return `${emojiList.join(" ")} ${m.title}`;
|
|
|
|
} else {
|
|
|
|
return m.title;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
export const formatMessage = (m) => {
|
|
|
|
if (m.title) {
|
|
|
|
return m.message;
|
|
|
|
} else {
|
|
|
|
const emojiList = toEmojis(m.tags);
|
|
|
|
if (emojiList.length > 0) {
|
|
|
|
return `${emojiList.join(" ")} ${m.message}`;
|
|
|
|
} else {
|
|
|
|
return m.message;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
export const unmatchedTags = (tags) => {
|
|
|
|
if (!tags) return [];
|
|
|
|
else return tags.filter(tag => !(tag in emojis));
|
|
|
|
}
|
|
|
|
|
2022-02-26 04:25:04 +00:00
|
|
|
|
|
|
|
export const maybeWithBasicAuth = (headers, user) => {
|
|
|
|
if (user) {
|
|
|
|
headers['Authorization'] = `Basic ${encodeBase64(`${user.username}:${user.password}`)}`;
|
|
|
|
}
|
|
|
|
return headers;
|
|
|
|
}
|
|
|
|
|
|
|
|
export const basicAuth = (username, password) => {
|
|
|
|
return `Basic ${encodeBase64(`${username}:${password}`)}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
export const encodeBase64 = (s) => {
|
2022-03-10 23:11:12 +00:00
|
|
|
return Base64.encode(s);
|
2022-02-26 04:25:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export const encodeBase64Url = (s) => {
|
2022-03-10 23:11:12 +00:00
|
|
|
return Base64.encodeURI(s);
|
2022-02-26 04:25:04 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 03:58:24 +00:00
|
|
|
export const shuffle = (arr) => {
|
|
|
|
let j, x;
|
|
|
|
for (let index = arr.length - 1; index > 0; index--) {
|
|
|
|
j = Math.floor(Math.random() * (index + 1));
|
|
|
|
x = arr[index];
|
|
|
|
arr[index] = arr[j];
|
|
|
|
arr[j] = x;
|
|
|
|
}
|
|
|
|
return arr;
|
|
|
|
}
|
|
|
|
|
2022-03-29 19:22:26 +00:00
|
|
|
export const splitNoEmpty = (s, delimiter) => {
|
|
|
|
return s
|
|
|
|
.split(delimiter)
|
|
|
|
.map(x => x.trim())
|
|
|
|
.filter(x => x !== "");
|
|
|
|
}
|
|
|
|
|
2022-03-12 13:15:30 +00:00
|
|
|
/** Non-cryptographic hash function, see https://stackoverflow.com/a/8831937/1440785 */
|
|
|
|
export const hashCode = async (s) => {
|
|
|
|
let hash = 0;
|
|
|
|
for (let i = 0; i < s.length; i++) {
|
|
|
|
const char = s.charCodeAt(i);
|
|
|
|
hash = ((hash<<5)-hash)+char;
|
|
|
|
hash = hash & hash; // Convert to 32bit integer
|
|
|
|
}
|
|
|
|
return hash;
|
2022-03-04 01:07:35 +00:00
|
|
|
}
|
|
|
|
|
2022-03-03 19:51:56 +00:00
|
|
|
export const formatShortDateTime = (timestamp) => {
|
|
|
|
return new Intl.DateTimeFormat('default', {dateStyle: 'short', timeStyle: 'short'})
|
|
|
|
.format(new Date(timestamp * 1000));
|
|
|
|
}
|
|
|
|
|
|
|
|
export const formatBytes = (bytes, decimals = 2) => {
|
|
|
|
if (bytes === 0) return '0 bytes';
|
|
|
|
const k = 1024;
|
|
|
|
const dm = decimals < 0 ? 0 : decimals;
|
|
|
|
const sizes = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
|
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
|
|
|
}
|
|
|
|
|
2022-03-04 16:08:32 +00:00
|
|
|
export const openUrl = (url) => {
|
|
|
|
window.open(url, "_blank", "noopener,noreferrer");
|
|
|
|
};
|
|
|
|
|
2022-03-09 20:58:21 +00:00
|
|
|
export const sounds = {
|
|
|
|
"beep": beep,
|
|
|
|
"juntos": juntos,
|
|
|
|
"pristine": pristine,
|
|
|
|
"ding": ding,
|
|
|
|
"dadum": dadum,
|
|
|
|
"pop": pop,
|
|
|
|
"pop-swoosh": popSwoosh
|
|
|
|
};
|
|
|
|
|
2022-03-06 05:02:27 +00:00
|
|
|
export const playSound = async (sound) => {
|
2022-03-09 20:58:21 +00:00
|
|
|
const audio = new Audio(sounds[sound]);
|
2022-03-06 05:02:27 +00:00
|
|
|
return audio.play();
|
|
|
|
};
|
|
|
|
|
2022-02-23 04:22:30 +00:00
|
|
|
// From: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
|
2022-02-26 04:25:04 +00:00
|
|
|
export async function* fetchLinesIterator(fileURL, headers) {
|
2022-02-23 04:22:30 +00:00
|
|
|
const utf8Decoder = new TextDecoder('utf-8');
|
2022-02-26 04:25:04 +00:00
|
|
|
const response = await fetch(fileURL, {
|
|
|
|
headers: headers
|
|
|
|
});
|
2022-02-23 04:22:30 +00:00
|
|
|
const reader = response.body.getReader();
|
|
|
|
let { value: chunk, done: readerDone } = await reader.read();
|
|
|
|
chunk = chunk ? utf8Decoder.decode(chunk) : '';
|
|
|
|
|
|
|
|
const re = /\n|\r|\r\n/gm;
|
|
|
|
let startIndex = 0;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
let result = re.exec(chunk);
|
|
|
|
if (!result) {
|
|
|
|
if (readerDone) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
let remainder = chunk.substr(startIndex);
|
|
|
|
({ value: chunk, done: readerDone } = await reader.read());
|
|
|
|
chunk = remainder + (chunk ? utf8Decoder.decode(chunk) : '');
|
|
|
|
startIndex = re.lastIndex = 0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
yield chunk.substring(startIndex, result.index);
|
|
|
|
startIndex = re.lastIndex;
|
|
|
|
}
|
|
|
|
if (startIndex < chunk.length) {
|
|
|
|
yield chunk.substr(startIndex); // last line didn't end in a newline char
|
|
|
|
}
|
|
|
|
}
|