2023-05-24 09:03:28 +02:00
|
|
|
import { Base64 } from "js-base64";
|
2023-05-23 21:13:01 +02:00
|
|
|
import { rawEmojis } from "./emojis";
|
2022-03-09 21:58:21 +01: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 05:28:55 +01:00
|
|
|
import config from "./config";
|
2022-02-24 18:26:07 +01:00
|
|
|
|
2023-05-24 10:20:15 +02:00
|
|
|
export const tiersUrl = (baseUrl) => `${baseUrl}/v1/tiers`;
|
|
|
|
export const shortUrl = (url) => url.replaceAll(/https?:\/\//g, "");
|
|
|
|
export const expandUrl = (url) => [`https://${url}`, `http://${url}`];
|
|
|
|
export const expandSecureUrl = (url) => `https://${url}`;
|
2022-02-18 20:41:01 +01:00
|
|
|
export const topicUrl = (baseUrl, topic) => `${baseUrl}/${topic}`;
|
2023-05-24 02:16:29 +02:00
|
|
|
export const topicUrlWs = (baseUrl, topic) =>
|
|
|
|
`${topicUrl(baseUrl, topic)}/ws`.replaceAll("https://", "wss://").replaceAll("http://", "ws://");
|
2023-05-24 01:29:47 +02:00
|
|
|
export const topicUrlJson = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/json`;
|
|
|
|
export const topicUrlJsonPoll = (baseUrl, topic) => `${topicUrlJson(baseUrl, topic)}?poll=1`;
|
|
|
|
export const topicUrlJsonPollWithSince = (baseUrl, topic, since) => `${topicUrlJson(baseUrl, topic)}?poll=1&since=${since}`;
|
|
|
|
export const topicUrlAuth = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/auth`;
|
|
|
|
export const topicShortUrl = (baseUrl, topic) => shortUrl(topicUrl(baseUrl, topic));
|
2022-12-15 05:11:22 +01:00
|
|
|
export const accountUrl = (baseUrl) => `${baseUrl}/v1/account`;
|
2022-12-16 04:07:04 +01:00
|
|
|
export const accountPasswordUrl = (baseUrl) => `${baseUrl}/v1/account/password`;
|
2022-12-15 05:11:22 +01:00
|
|
|
export const accountTokenUrl = (baseUrl) => `${baseUrl}/v1/account/token`;
|
|
|
|
export const accountSettingsUrl = (baseUrl) => `${baseUrl}/v1/account/settings`;
|
2023-05-24 01:29:47 +02:00
|
|
|
export const accountSubscriptionUrl = (baseUrl) => `${baseUrl}/v1/account/subscription`;
|
|
|
|
export const accountReservationUrl = (baseUrl) => `${baseUrl}/v1/account/reservation`;
|
|
|
|
export const accountReservationSingleUrl = (baseUrl, topic) => `${baseUrl}/v1/account/reservation/${topic}`;
|
|
|
|
export const accountBillingSubscriptionUrl = (baseUrl) => `${baseUrl}/v1/account/billing/subscription`;
|
|
|
|
export const accountBillingPortalUrl = (baseUrl) => `${baseUrl}/v1/account/billing/portal`;
|
2023-05-13 03:47:41 +02:00
|
|
|
export const accountPhoneUrl = (baseUrl) => `${baseUrl}/v1/account/phone`;
|
2023-05-24 01:29:47 +02:00
|
|
|
export const accountPhoneVerifyUrl = (baseUrl) => `${baseUrl}/v1/account/phone/verify`;
|
2022-02-23 05:22:30 +01:00
|
|
|
|
2023-05-24 09:03:28 +02:00
|
|
|
export const validUrl = (url) => url.match(/^https?:\/\/.+/);
|
2022-02-28 22:56:38 +01:00
|
|
|
|
2023-05-24 10:20:15 +02:00
|
|
|
export const disallowedTopic = (topic) => config.disallowed_topics.includes(topic);
|
|
|
|
|
2022-02-26 17:45:39 +01:00
|
|
|
export const validTopic = (topic) => {
|
2023-05-23 21:13:01 +02:00
|
|
|
if (disallowedTopic(topic)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return topic.match(/^([-_a-zA-Z0-9]{1,64})$/); // Regex must match Go & Android app!
|
|
|
|
};
|
2022-02-26 17:45:39 +01:00
|
|
|
|
2022-06-29 21:57:56 +02:00
|
|
|
export const topicDisplayName = (subscription) => {
|
2023-05-23 21:13:01 +02:00
|
|
|
if (subscription.displayName) {
|
|
|
|
return subscription.displayName;
|
2023-05-24 09:03:28 +02:00
|
|
|
}
|
|
|
|
if (subscription.baseUrl === config.base_url) {
|
2023-05-23 21:13:01 +02:00
|
|
|
return subscription.topic;
|
|
|
|
}
|
|
|
|
return topicShortUrl(subscription.baseUrl, subscription.topic);
|
2022-06-29 21:57:56 +02:00
|
|
|
};
|
|
|
|
|
2022-02-24 18:26:07 +01:00
|
|
|
// Format emojis (see emoji.js)
|
|
|
|
const emojis = {};
|
2023-05-23 21:13:01 +02:00
|
|
|
rawEmojis.forEach((emoji) => {
|
|
|
|
emoji.aliases.forEach((alias) => {
|
|
|
|
emojis[alias] = emoji.emoji;
|
|
|
|
});
|
2022-02-24 18:26:07 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
const toEmojis = (tags) => {
|
2023-05-23 21:13:01 +02:00
|
|
|
if (!tags) return [];
|
2023-05-24 09:03:28 +02:00
|
|
|
return tags.filter((tag) => tag in emojis).map((tag) => emojis[tag]);
|
2023-05-23 21:13:01 +02:00
|
|
|
};
|
2022-02-24 18:26:07 +01:00
|
|
|
|
|
|
|
export const formatTitle = (m) => {
|
2023-05-23 21:13:01 +02:00
|
|
|
const emojiList = toEmojis(m.tags);
|
|
|
|
if (emojiList.length > 0) {
|
|
|
|
return `${emojiList.join(" ")} ${m.title}`;
|
|
|
|
}
|
2023-05-24 09:03:28 +02:00
|
|
|
return m.title;
|
2022-02-24 18:26:07 +01:00
|
|
|
};
|
|
|
|
|
2023-05-24 10:20:15 +02:00
|
|
|
export const formatTitleWithDefault = (m, fallback) => {
|
|
|
|
if (m.title) {
|
|
|
|
return formatTitle(m);
|
|
|
|
}
|
|
|
|
return fallback;
|
|
|
|
};
|
|
|
|
|
2022-02-24 18:26:07 +01:00
|
|
|
export const formatMessage = (m) => {
|
2023-05-23 21:13:01 +02:00
|
|
|
if (m.title) {
|
|
|
|
return m.message;
|
|
|
|
}
|
2023-05-24 09:03:28 +02:00
|
|
|
const emojiList = toEmojis(m.tags);
|
|
|
|
if (emojiList.length > 0) {
|
|
|
|
return `${emojiList.join(" ")} ${m.message}`;
|
|
|
|
}
|
|
|
|
return m.message;
|
2022-02-24 18:26:07 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
export const unmatchedTags = (tags) => {
|
2023-05-23 21:13:01 +02:00
|
|
|
if (!tags) return [];
|
2023-05-24 09:03:28 +02:00
|
|
|
return tags.filter((tag) => !(tag in emojis));
|
2023-05-23 21:13:01 +02:00
|
|
|
};
|
2022-02-24 18:26:07 +01:00
|
|
|
|
2023-05-24 10:20:15 +02:00
|
|
|
export const encodeBase64 = (s) => Base64.encode(s);
|
|
|
|
|
|
|
|
export const encodeBase64Url = (s) => Base64.encodeURI(s);
|
|
|
|
|
|
|
|
export const bearerAuth = (token) => `Bearer ${token}`;
|
|
|
|
|
|
|
|
export const basicAuth = (username, password) => `Basic ${encodeBase64(`${username}:${password}`)}`;
|
|
|
|
|
|
|
|
export const withBearerAuth = (headers, token) => ({ ...headers, Authorization: bearerAuth(token) });
|
2022-02-26 05:25:04 +01:00
|
|
|
|
2023-02-11 20:13:10 +01:00
|
|
|
export const maybeWithBearerAuth = (headers, token) => {
|
2023-05-23 21:13:01 +02:00
|
|
|
if (token) {
|
|
|
|
return withBearerAuth(headers, token);
|
|
|
|
}
|
|
|
|
return headers;
|
|
|
|
};
|
2023-02-11 20:13:10 +01:00
|
|
|
|
2023-05-24 10:20:15 +02:00
|
|
|
export const withBasicAuth = (headers, username, password) => ({ ...headers, Authorization: basicAuth(username, password) });
|
2022-02-26 05:25:04 +01:00
|
|
|
|
2023-05-24 10:20:15 +02:00
|
|
|
export const maybeWithAuth = (headers, user) => {
|
|
|
|
if (user && user.password) {
|
|
|
|
return withBasicAuth(headers, user.username, user.password);
|
|
|
|
}
|
|
|
|
if (user && user.token) {
|
|
|
|
return withBearerAuth(headers, user.token);
|
|
|
|
}
|
2023-05-23 21:13:01 +02:00
|
|
|
return headers;
|
|
|
|
};
|
2022-12-27 03:27:07 +01:00
|
|
|
|
2022-04-21 22:33:49 +02:00
|
|
|
export const maybeAppendActionErrors = (message, notification) => {
|
2023-05-23 21:13:01 +02:00
|
|
|
const actionErrors = (notification.actions ?? [])
|
|
|
|
.map((action) => action.error)
|
|
|
|
.filter((action) => !!action)
|
|
|
|
.join("\n");
|
|
|
|
if (actionErrors.length === 0) {
|
|
|
|
return message;
|
|
|
|
}
|
2023-05-24 09:03:28 +02:00
|
|
|
return `${message}\n\n${actionErrors}`;
|
2023-05-23 21:13:01 +02:00
|
|
|
};
|
2022-04-21 22:33:49 +02:00
|
|
|
|
2022-03-11 04:58:24 +01:00
|
|
|
export const shuffle = (arr) => {
|
2023-05-24 09:03:28 +02:00
|
|
|
let j;
|
|
|
|
let x;
|
2023-05-24 10:20:15 +02:00
|
|
|
for (let index = arr.length - 1; index > 0; index -= 1) {
|
2023-05-23 21:13:01 +02:00
|
|
|
j = Math.floor(Math.random() * (index + 1));
|
|
|
|
x = arr[index];
|
2023-05-24 10:20:15 +02:00
|
|
|
// eslint-disable-next-line no-param-reassign
|
2023-05-23 21:13:01 +02:00
|
|
|
arr[index] = arr[j];
|
2023-05-24 10:20:15 +02:00
|
|
|
// eslint-disable-next-line no-param-reassign
|
2023-05-23 21:13:01 +02:00
|
|
|
arr[j] = x;
|
|
|
|
}
|
|
|
|
return arr;
|
|
|
|
};
|
2022-03-11 04:58:24 +01:00
|
|
|
|
2023-05-24 09:03:28 +02:00
|
|
|
export const splitNoEmpty = (s, delimiter) =>
|
|
|
|
s
|
2023-05-23 21:13:01 +02:00
|
|
|
.split(delimiter)
|
|
|
|
.map((x) => x.trim())
|
|
|
|
.filter((x) => x !== "");
|
2022-03-29 21:22:26 +02:00
|
|
|
|
2022-03-12 14:15:30 +01:00
|
|
|
/** Non-cryptographic hash function, see https://stackoverflow.com/a/8831937/1440785 */
|
|
|
|
export const hashCode = async (s) => {
|
2023-05-23 21:13:01 +02:00
|
|
|
let hash = 0;
|
2023-05-24 10:20:15 +02:00
|
|
|
for (let i = 0; i < s.length; i += 1) {
|
2023-05-23 21:13:01 +02:00
|
|
|
const char = s.charCodeAt(i);
|
2023-05-24 10:20:15 +02:00
|
|
|
// eslint-disable-next-line no-bitwise
|
2023-05-23 21:13:01 +02:00
|
|
|
hash = (hash << 5) - hash + char;
|
2023-05-24 10:20:15 +02:00
|
|
|
// eslint-disable-next-line no-bitwise
|
2023-05-24 09:03:28 +02:00
|
|
|
hash &= hash; // Convert to 32bit integer
|
2023-05-23 21:13:01 +02:00
|
|
|
}
|
|
|
|
return hash;
|
|
|
|
};
|
2022-03-04 02:07:35 +01:00
|
|
|
|
2023-05-24 09:03:28 +02:00
|
|
|
export const formatShortDateTime = (timestamp) =>
|
|
|
|
new Intl.DateTimeFormat("default", {
|
2023-05-23 21:13:01 +02:00
|
|
|
dateStyle: "short",
|
|
|
|
timeStyle: "short",
|
|
|
|
}).format(new Date(timestamp * 1000));
|
2022-03-03 20:51:56 +01:00
|
|
|
|
2023-05-24 09:03:28 +02:00
|
|
|
export const formatShortDate = (timestamp) => new Intl.DateTimeFormat("default", { dateStyle: "short" }).format(new Date(timestamp * 1000));
|
2023-01-16 16:35:12 +01:00
|
|
|
|
2022-03-03 20:51:56 +01:00
|
|
|
export const formatBytes = (bytes, decimals = 2) => {
|
2023-05-23 21:13:01 +02:00
|
|
|
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));
|
2023-05-24 09:03:28 +02:00
|
|
|
return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`;
|
2023-05-23 21:13:01 +02:00
|
|
|
};
|
2022-03-03 20:51:56 +01:00
|
|
|
|
2023-01-18 01:40:03 +01:00
|
|
|
export const formatNumber = (n) => {
|
2023-05-23 21:13:01 +02:00
|
|
|
if (n === 0) {
|
|
|
|
return n;
|
2023-05-24 09:03:28 +02:00
|
|
|
}
|
|
|
|
if (n % 1000 === 0) {
|
2023-05-23 21:13:01 +02:00
|
|
|
return `${n / 1000}k`;
|
|
|
|
}
|
|
|
|
return n.toLocaleString();
|
|
|
|
};
|
2023-01-18 01:40:03 +01:00
|
|
|
|
2023-02-22 04:44:30 +01:00
|
|
|
export const formatPrice = (n) => {
|
2023-05-23 21:13:01 +02:00
|
|
|
if (n % 100 === 0) {
|
|
|
|
return `$${n / 100}`;
|
|
|
|
}
|
|
|
|
return `$${(n / 100).toPrecision(2)}`;
|
|
|
|
};
|
2023-02-22 04:44:30 +01:00
|
|
|
|
2022-03-04 17:08:32 +01:00
|
|
|
export const openUrl = (url) => {
|
2023-05-23 21:13:01 +02:00
|
|
|
window.open(url, "_blank", "noopener,noreferrer");
|
2022-03-04 17:08:32 +01:00
|
|
|
};
|
|
|
|
|
2022-03-09 21:58:21 +01:00
|
|
|
export const sounds = {
|
2023-05-23 21:13:01 +02:00
|
|
|
ding: {
|
|
|
|
file: ding,
|
|
|
|
label: "Ding",
|
|
|
|
},
|
|
|
|
juntos: {
|
|
|
|
file: juntos,
|
|
|
|
label: "Juntos",
|
|
|
|
},
|
|
|
|
pristine: {
|
|
|
|
file: pristine,
|
|
|
|
label: "Pristine",
|
|
|
|
},
|
|
|
|
dadum: {
|
|
|
|
file: dadum,
|
|
|
|
label: "Dadum",
|
|
|
|
},
|
|
|
|
pop: {
|
|
|
|
file: pop,
|
|
|
|
label: "Pop",
|
|
|
|
},
|
|
|
|
"pop-swoosh": {
|
|
|
|
file: popSwoosh,
|
|
|
|
label: "Pop swoosh",
|
|
|
|
},
|
|
|
|
beep: {
|
|
|
|
file: beep,
|
|
|
|
label: "Beep",
|
|
|
|
},
|
2022-03-09 21:58:21 +01:00
|
|
|
};
|
|
|
|
|
2022-04-10 21:13:12 +02:00
|
|
|
export const playSound = async (id) => {
|
2023-05-23 21:13:01 +02:00
|
|
|
const audio = new Audio(sounds[id].file);
|
|
|
|
return audio.play();
|
2022-03-06 06:02:27 +01:00
|
|
|
};
|
|
|
|
|
2022-02-23 05:22:30 +01:00
|
|
|
// From: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
|
2023-05-24 10:20:15 +02:00
|
|
|
// eslint-disable-next-line func-style
|
2022-02-26 05:25:04 +01:00
|
|
|
export async function* fetchLinesIterator(fileURL, headers) {
|
2023-05-23 21:13:01 +02:00
|
|
|
const utf8Decoder = new TextDecoder("utf-8");
|
|
|
|
const response = await fetch(fileURL, {
|
2023-05-24 09:03:28 +02:00
|
|
|
headers,
|
2023-05-23 21:13:01 +02: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 (;;) {
|
2023-05-24 09:03:28 +02:00
|
|
|
const result = re.exec(chunk);
|
2023-05-23 21:13:01 +02:00
|
|
|
if (!result) {
|
|
|
|
if (readerDone) {
|
|
|
|
break;
|
|
|
|
}
|
2023-05-24 09:03:28 +02:00
|
|
|
const remainder = chunk.substr(startIndex);
|
2023-05-24 10:20:15 +02:00
|
|
|
// eslint-disable-next-line no-await-in-loop
|
2023-05-23 21:13:01 +02:00
|
|
|
({ value: chunk, done: readerDone } = await reader.read());
|
|
|
|
chunk = remainder + (chunk ? utf8Decoder.decode(chunk) : "");
|
2023-05-24 10:20:15 +02:00
|
|
|
startIndex = 0;
|
|
|
|
re.lastIndex = 0;
|
|
|
|
// eslint-disable-next-line no-continue
|
2023-05-23 21:13:01 +02:00
|
|
|
continue;
|
2022-02-23 05:22:30 +01:00
|
|
|
}
|
2023-05-23 21:13:01 +02:00
|
|
|
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
|
|
|
|
}
|
2022-02-23 05:22:30 +01:00
|
|
|
}
|
2022-12-08 15:16:59 +01:00
|
|
|
|
|
|
|
export const randomAlphanumericString = (len) => {
|
2023-05-24 01:29:47 +02:00
|
|
|
const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
2023-05-23 21:13:01 +02:00
|
|
|
let id = "";
|
2023-05-24 10:20:15 +02:00
|
|
|
for (let i = 0; i < len; i += 1) {
|
|
|
|
// eslint-disable-next-line no-bitwise
|
2023-05-23 21:13:01 +02:00
|
|
|
id += alphabet[(Math.random() * alphabet.length) | 0];
|
|
|
|
}
|
|
|
|
return id;
|
|
|
|
};
|