Line width
This commit is contained in:
parent
2e27f58963
commit
ca5d736a71
33 changed files with 521 additions and 2033 deletions
|
@ -56,9 +56,7 @@ class AccountApi {
|
|||
|
||||
async logout() {
|
||||
const url = accountTokenUrl(config.base_url);
|
||||
console.log(
|
||||
`[AccountApi] Logging out from ${url} using token ${session.token()}`
|
||||
);
|
||||
console.log(`[AccountApi] Logging out from ${url} using token ${session.token()}`);
|
||||
await fetchOrThrow(url, {
|
||||
method: "DELETE",
|
||||
headers: withBearerAuth({}, session.token()),
|
||||
|
@ -227,9 +225,7 @@ class AccountApi {
|
|||
|
||||
async upsertReservation(topic, everyone) {
|
||||
const url = accountReservationUrl(config.base_url);
|
||||
console.log(
|
||||
`[AccountApi] Upserting user access to topic ${topic}, everyone=${everyone}`
|
||||
);
|
||||
console.log(`[AccountApi] Upserting user access to topic ${topic}, everyone=${everyone}`);
|
||||
await fetchOrThrow(url, {
|
||||
method: "POST",
|
||||
headers: withBearerAuth({}, session.token()),
|
||||
|
@ -264,16 +260,12 @@ class AccountApi {
|
|||
}
|
||||
|
||||
async createBillingSubscription(tier, interval) {
|
||||
console.log(
|
||||
`[AccountApi] Creating billing subscription with ${tier} and interval ${interval}`
|
||||
);
|
||||
console.log(`[AccountApi] Creating billing subscription with ${tier} and interval ${interval}`);
|
||||
return await this.upsertBillingSubscription("POST", tier, interval);
|
||||
}
|
||||
|
||||
async updateBillingSubscription(tier, interval) {
|
||||
console.log(
|
||||
`[AccountApi] Updating billing subscription with ${tier} and interval ${interval}`
|
||||
);
|
||||
console.log(`[AccountApi] Updating billing subscription with ${tier} and interval ${interval}`);
|
||||
return await this.upsertBillingSubscription("PUT", tier, interval);
|
||||
}
|
||||
|
||||
|
@ -324,9 +316,7 @@ class AccountApi {
|
|||
|
||||
async addPhoneNumber(phoneNumber, code) {
|
||||
const url = accountPhoneUrl(config.base_url);
|
||||
console.log(
|
||||
`[AccountApi] Adding phone number with verification code ${url}`
|
||||
);
|
||||
console.log(`[AccountApi] Adding phone number with verification code ${url}`);
|
||||
await fetchOrThrow(url, {
|
||||
method: "PUT",
|
||||
headers: withBearerAuth({}, session.token()),
|
||||
|
@ -371,10 +361,7 @@ class AccountApi {
|
|||
}
|
||||
}
|
||||
if (account.subscriptions) {
|
||||
await subscriptionManager.syncFromRemote(
|
||||
account.subscriptions,
|
||||
account.reservations
|
||||
);
|
||||
await subscriptionManager.syncFromRemote(account.subscriptions, account.reservations);
|
||||
}
|
||||
return account;
|
||||
} catch (e) {
|
||||
|
|
|
@ -1,12 +1,4 @@
|
|||
import {
|
||||
fetchLinesIterator,
|
||||
maybeWithAuth,
|
||||
topicShortUrl,
|
||||
topicUrl,
|
||||
topicUrlAuth,
|
||||
topicUrlJsonPoll,
|
||||
topicUrlJsonPollWithSince,
|
||||
} from "./utils";
|
||||
import { fetchLinesIterator, maybeWithAuth, topicShortUrl, topicUrl, topicUrlAuth, topicUrlJsonPoll, topicUrlJsonPollWithSince } from "./utils";
|
||||
import userManager from "./UserManager";
|
||||
import { fetchOrThrow } from "./errors";
|
||||
|
||||
|
@ -14,9 +6,7 @@ class Api {
|
|||
async poll(baseUrl, topic, since) {
|
||||
const user = await userManager.get(baseUrl);
|
||||
const shortUrl = topicShortUrl(baseUrl, topic);
|
||||
const url = since
|
||||
? topicUrlJsonPollWithSince(baseUrl, topic, since)
|
||||
: topicUrlJsonPoll(baseUrl, topic);
|
||||
const url = since ? topicUrlJsonPollWithSince(baseUrl, topic, since) : topicUrlJsonPoll(baseUrl, topic);
|
||||
const messages = [];
|
||||
const headers = maybeWithAuth({}, user);
|
||||
console.log(`[Api] Polling ${url}`);
|
||||
|
@ -73,17 +63,11 @@ class Api {
|
|||
xhr.upload.addEventListener("progress", onProgress);
|
||||
xhr.addEventListener("readystatechange", () => {
|
||||
if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status <= 299) {
|
||||
console.log(
|
||||
`[Api] Publish successful (HTTP ${xhr.status})`,
|
||||
xhr.response
|
||||
);
|
||||
console.log(`[Api] Publish successful (HTTP ${xhr.status})`, xhr.response);
|
||||
resolve(xhr.response);
|
||||
} else if (xhr.readyState === 4) {
|
||||
// Firefox bug; see description above!
|
||||
console.log(
|
||||
`[Api] Publish failed (HTTP ${xhr.status})`,
|
||||
xhr.responseText
|
||||
);
|
||||
console.log(`[Api] Publish failed (HTTP ${xhr.status})`, xhr.responseText);
|
||||
let errorText;
|
||||
try {
|
||||
const error = JSON.parse(xhr.responseText);
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
import {
|
||||
basicAuth,
|
||||
bearerAuth,
|
||||
encodeBase64Url,
|
||||
topicShortUrl,
|
||||
topicUrlWs,
|
||||
} from "./utils";
|
||||
import { basicAuth, bearerAuth, encodeBase64Url, topicShortUrl, topicUrlWs } from "./utils";
|
||||
|
||||
const retryBackoffSeconds = [5, 10, 20, 30, 60, 120];
|
||||
|
||||
|
@ -15,16 +9,7 @@ const retryBackoffSeconds = [5, 10, 20, 30, 60, 120];
|
|||
* Incoming messages and state changes are forwarded via listeners.
|
||||
*/
|
||||
class Connection {
|
||||
constructor(
|
||||
connectionId,
|
||||
subscriptionId,
|
||||
baseUrl,
|
||||
topic,
|
||||
user,
|
||||
since,
|
||||
onNotification,
|
||||
onStateChanged
|
||||
) {
|
||||
constructor(connectionId, subscriptionId, baseUrl, topic, user, since, onNotification, onStateChanged) {
|
||||
this.connectionId = connectionId;
|
||||
this.subscriptionId = subscriptionId;
|
||||
this.baseUrl = baseUrl;
|
||||
|
@ -44,78 +29,51 @@ class Connection {
|
|||
// we don't want to re-trigger the main view re-render potentially hundreds of times.
|
||||
|
||||
const wsUrl = this.wsUrl();
|
||||
console.log(
|
||||
`[Connection, ${this.shortUrl}, ${this.connectionId}] Opening connection to ${wsUrl}`
|
||||
);
|
||||
console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Opening connection to ${wsUrl}`);
|
||||
|
||||
this.ws = new WebSocket(wsUrl);
|
||||
this.ws.onopen = (event) => {
|
||||
console.log(
|
||||
`[Connection, ${this.shortUrl}, ${this.connectionId}] Connection established`,
|
||||
event
|
||||
);
|
||||
console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Connection established`, event);
|
||||
this.retryCount = 0;
|
||||
this.onStateChanged(this.subscriptionId, ConnectionState.Connected);
|
||||
};
|
||||
this.ws.onmessage = (event) => {
|
||||
console.log(
|
||||
`[Connection, ${this.shortUrl}, ${this.connectionId}] Message received from server: ${event.data}`
|
||||
);
|
||||
console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Message received from server: ${event.data}`);
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.event === "open") {
|
||||
return;
|
||||
}
|
||||
const relevantAndValid =
|
||||
data.event === "message" &&
|
||||
"id" in data &&
|
||||
"time" in data &&
|
||||
"message" in data;
|
||||
const relevantAndValid = data.event === "message" && "id" in data && "time" in data && "message" in data;
|
||||
if (!relevantAndValid) {
|
||||
console.log(
|
||||
`[Connection, ${this.shortUrl}, ${this.connectionId}] Unexpected message. Ignoring.`
|
||||
);
|
||||
console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Unexpected message. Ignoring.`);
|
||||
return;
|
||||
}
|
||||
this.since = data.id;
|
||||
this.onNotification(this.subscriptionId, data);
|
||||
} catch (e) {
|
||||
console.log(
|
||||
`[Connection, ${this.shortUrl}, ${this.connectionId}] Error handling message: ${e}`
|
||||
);
|
||||
console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Error handling message: ${e}`);
|
||||
}
|
||||
};
|
||||
this.ws.onclose = (event) => {
|
||||
if (event.wasClean) {
|
||||
console.log(
|
||||
`[Connection, ${this.shortUrl}, ${this.connectionId}] Connection closed cleanly, code=${event.code} reason=${event.reason}`
|
||||
);
|
||||
console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
|
||||
this.ws = null;
|
||||
} else {
|
||||
const retrySeconds =
|
||||
retryBackoffSeconds[
|
||||
Math.min(this.retryCount, retryBackoffSeconds.length - 1)
|
||||
];
|
||||
const retrySeconds = retryBackoffSeconds[Math.min(this.retryCount, retryBackoffSeconds.length - 1)];
|
||||
this.retryCount++;
|
||||
console.log(
|
||||
`[Connection, ${this.shortUrl}, ${this.connectionId}] Connection died, retrying in ${retrySeconds} seconds`
|
||||
);
|
||||
console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Connection died, retrying in ${retrySeconds} seconds`);
|
||||
this.retryTimeout = setTimeout(() => this.start(), retrySeconds * 1000);
|
||||
this.onStateChanged(this.subscriptionId, ConnectionState.Connecting);
|
||||
}
|
||||
};
|
||||
this.ws.onerror = (event) => {
|
||||
console.log(
|
||||
`[Connection, ${this.shortUrl}, ${this.connectionId}] Error occurred: ${event}`,
|
||||
event
|
||||
);
|
||||
console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Error occurred: ${event}`, event);
|
||||
};
|
||||
}
|
||||
|
||||
close() {
|
||||
console.log(
|
||||
`[Connection, ${this.shortUrl}, ${this.connectionId}] Closing connection`
|
||||
);
|
||||
console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Closing connection`);
|
||||
const socket = this.ws;
|
||||
const retryTimeout = this.retryTimeout;
|
||||
if (socket !== null) {
|
||||
|
|
|
@ -49,12 +49,8 @@ class ConnectionManager {
|
|||
return { ...s, user, connectionId };
|
||||
})
|
||||
);
|
||||
const targetIds = subscriptionsWithUsersAndConnectionId.map(
|
||||
(s) => s.connectionId
|
||||
);
|
||||
const deletedIds = Array.from(this.connections.keys()).filter(
|
||||
(id) => !targetIds.includes(id)
|
||||
);
|
||||
const targetIds = subscriptionsWithUsersAndConnectionId.map((s) => s.connectionId);
|
||||
const deletedIds = Array.from(this.connections.keys()).filter((id) => !targetIds.includes(id));
|
||||
|
||||
// Create and add new connections
|
||||
subscriptionsWithUsersAndConnectionId.forEach((subscription) => {
|
||||
|
@ -73,15 +69,12 @@ class ConnectionManager {
|
|||
topic,
|
||||
user,
|
||||
since,
|
||||
(subscriptionId, notification) =>
|
||||
this.notificationReceived(subscriptionId, notification),
|
||||
(subscriptionId, notification) => this.notificationReceived(subscriptionId, notification),
|
||||
(subscriptionId, state) => this.stateChanged(subscriptionId, state)
|
||||
);
|
||||
this.connections.set(connectionId, connection);
|
||||
console.log(
|
||||
`[ConnectionManager] Starting new connection ${connectionId} (subscription ${subscriptionId} with user ${
|
||||
user ? user.username : "anonymous"
|
||||
})`
|
||||
`[ConnectionManager] Starting new connection ${connectionId} (subscription ${subscriptionId} with user ${user ? user.username : "anonymous"})`
|
||||
);
|
||||
connection.start();
|
||||
}
|
||||
|
@ -101,10 +94,7 @@ class ConnectionManager {
|
|||
try {
|
||||
this.stateListener(subscriptionId, state);
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`[ConnectionManager] Error updating state of ${subscriptionId} to ${state}`,
|
||||
e
|
||||
);
|
||||
console.error(`[ConnectionManager] Error updating state of ${subscriptionId} to ${state}`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,23 +104,14 @@ class ConnectionManager {
|
|||
try {
|
||||
this.messageListener(subscriptionId, notification);
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`[ConnectionManager] Error handling notification for ${subscriptionId}`,
|
||||
e
|
||||
);
|
||||
console.error(`[ConnectionManager] Error handling notification for ${subscriptionId}`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const makeConnectionId = async (subscription, user) => {
|
||||
return user
|
||||
? hashCode(
|
||||
`${subscription.id}|${user.username}|${user.password ?? ""}|${
|
||||
user.token ?? ""
|
||||
}`
|
||||
)
|
||||
: hashCode(`${subscription.id}`);
|
||||
return user ? hashCode(`${subscription.id}|${user.username}|${user.password ?? ""}|${user.token ?? ""}`) : hashCode(`${subscription.id}`);
|
||||
};
|
||||
|
||||
const connectionManager = new ConnectionManager();
|
||||
|
|
|
@ -1,11 +1,4 @@
|
|||
import {
|
||||
formatMessage,
|
||||
formatTitleWithDefault,
|
||||
openUrl,
|
||||
playSound,
|
||||
topicDisplayName,
|
||||
topicShortUrl,
|
||||
} from "./utils";
|
||||
import { formatMessage, formatTitleWithDefault, openUrl, playSound, topicDisplayName, topicShortUrl } from "./utils";
|
||||
import prefs from "./Prefs";
|
||||
import subscriptionManager from "./SubscriptionManager";
|
||||
import logo from "../img/ntfy.png";
|
||||
|
@ -30,9 +23,7 @@ class Notifier {
|
|||
const title = formatTitleWithDefault(notification, displayName);
|
||||
|
||||
// Show notification
|
||||
console.log(
|
||||
`[Notifier, ${shortUrl}] Displaying notification ${notification.id}: ${message}`
|
||||
);
|
||||
console.log(`[Notifier, ${shortUrl}] Displaying notification ${notification.id}: ${message}`);
|
||||
const n = new Notification(title, {
|
||||
body: message,
|
||||
icon: logo,
|
||||
|
@ -96,11 +87,7 @@ class Notifier {
|
|||
* is not supported, see https://developer.mozilla.org/en-US/docs/Web/API/notification
|
||||
*/
|
||||
contextSupported() {
|
||||
return (
|
||||
location.protocol === "https:" ||
|
||||
location.hostname.match("^127.") ||
|
||||
location.hostname === "localhost"
|
||||
);
|
||||
return location.protocol === "https:" || location.hostname.match("^127.") || location.hostname === "localhost";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,18 +34,12 @@ class Poller {
|
|||
console.log(`[Poller] Polling ${subscription.id}`);
|
||||
|
||||
const since = subscription.last;
|
||||
const notifications = await api.poll(
|
||||
subscription.baseUrl,
|
||||
subscription.topic,
|
||||
since
|
||||
);
|
||||
const notifications = await api.poll(subscription.baseUrl, subscription.topic, since);
|
||||
if (!notifications || notifications.length === 0) {
|
||||
console.log(`[Poller] No new notifications found for ${subscription.id}`);
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
`[Poller] Adding ${notifications.length} notification(s) for ${subscription.id}`
|
||||
);
|
||||
console.log(`[Poller] Adding ${notifications.length} notification(s) for ${subscription.id}`);
|
||||
await subscriptionManager.addNotifications(subscription.id, notifications);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,15 +20,12 @@ class Pruner {
|
|||
|
||||
async prune() {
|
||||
const deleteAfterSeconds = await prefs.deleteAfter();
|
||||
const pruneThresholdTimestamp =
|
||||
Math.round(Date.now() / 1000) - deleteAfterSeconds;
|
||||
const pruneThresholdTimestamp = Math.round(Date.now() / 1000) - deleteAfterSeconds;
|
||||
if (deleteAfterSeconds === 0) {
|
||||
console.log(`[Pruner] Pruning is disabled. Skipping.`);
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
`[Pruner] Pruning notifications older than ${deleteAfterSeconds}s (timestamp ${pruneThresholdTimestamp})`
|
||||
);
|
||||
console.log(`[Pruner] Pruning notifications older than ${deleteAfterSeconds}s (timestamp ${pruneThresholdTimestamp})`);
|
||||
try {
|
||||
await subscriptionManager.pruneNotifications(pruneThresholdTimestamp);
|
||||
} catch (e) {
|
||||
|
|
|
@ -7,9 +7,7 @@ class SubscriptionManager {
|
|||
const subscriptions = await db.subscriptions.toArray();
|
||||
await Promise.all(
|
||||
subscriptions.map(async (s) => {
|
||||
s.new = await db.notifications
|
||||
.where({ subscriptionId: s.id, new: 1 })
|
||||
.count();
|
||||
s.new = await db.notifications.where({ subscriptionId: s.id, new: 1 }).count();
|
||||
})
|
||||
);
|
||||
return subscriptions;
|
||||
|
@ -38,20 +36,14 @@ class SubscriptionManager {
|
|||
}
|
||||
|
||||
async syncFromRemote(remoteSubscriptions, remoteReservations) {
|
||||
console.log(
|
||||
`[SubscriptionManager] Syncing subscriptions from remote`,
|
||||
remoteSubscriptions
|
||||
);
|
||||
console.log(`[SubscriptionManager] Syncing subscriptions from remote`, remoteSubscriptions);
|
||||
|
||||
// Add remote subscriptions
|
||||
let remoteIds = []; // = topicUrl(baseUrl, topic)
|
||||
for (let i = 0; i < remoteSubscriptions.length; i++) {
|
||||
const remote = remoteSubscriptions[i];
|
||||
const local = await this.add(remote.base_url, remote.topic, false);
|
||||
const reservation =
|
||||
remoteReservations?.find(
|
||||
(r) => remote.base_url === config.base_url && remote.topic === r.topic
|
||||
) || null;
|
||||
const reservation = remoteReservations?.find((r) => remote.base_url === config.base_url && remote.topic === r.topic) || null;
|
||||
await this.update(local.id, {
|
||||
displayName: remote.display_name, // May be undefined
|
||||
reservation: reservation, // May be null!
|
||||
|
@ -122,9 +114,7 @@ class SubscriptionManager {
|
|||
|
||||
/** Adds/replaces notifications, will not throw if they exist */
|
||||
async addNotifications(subscriptionId, notifications) {
|
||||
const notificationsWithSubscriptionId = notifications.map(
|
||||
(notification) => ({ ...notification, subscriptionId })
|
||||
);
|
||||
const notificationsWithSubscriptionId = notifications.map((notification) => ({ ...notification, subscriptionId }));
|
||||
const lastNotificationId = notifications.at(-1).id;
|
||||
await db.notifications.bulkPut(notificationsWithSubscriptionId);
|
||||
await db.subscriptions.update(subscriptionId, {
|
||||
|
@ -158,9 +148,7 @@ class SubscriptionManager {
|
|||
}
|
||||
|
||||
async markNotificationsRead(subscriptionId) {
|
||||
await db.notifications
|
||||
.where({ subscriptionId: subscriptionId, new: 1 })
|
||||
.modify({ new: 0 });
|
||||
await db.notifications.where({ subscriptionId: subscriptionId, new: 1 }).modify({ new: 0 });
|
||||
}
|
||||
|
||||
async setMutedUntil(subscriptionId, mutedUntil) {
|
||||
|
|
|
@ -15,12 +15,7 @@ export const throwAppError = async (response) => {
|
|||
}
|
||||
const error = await maybeToJson(response);
|
||||
if (error?.code) {
|
||||
console.log(
|
||||
`[Error] HTTP ${response.status}, ntfy error ${error.code}: ${
|
||||
error.error || ""
|
||||
}`,
|
||||
response
|
||||
);
|
||||
console.log(`[Error] HTTP ${response.status}, ntfy error ${error.code}: ${error.error || ""}`, response);
|
||||
if (error.code === UserExistsError.CODE) {
|
||||
throw new UserExistsError();
|
||||
} else if (error.code === TopicReservedError.CODE) {
|
||||
|
|
|
@ -10,37 +10,23 @@ import config from "./config";
|
|||
import { Base64 } from "js-base64";
|
||||
|
||||
export const topicUrl = (baseUrl, topic) => `${baseUrl}/${topic}`;
|
||||
export const topicUrlWs = (baseUrl, topic) =>
|
||||
`${topicUrl(baseUrl, topic)}/ws`
|
||||
.replaceAll("https://", "wss://")
|
||||
.replaceAll("http://", "ws://");
|
||||
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));
|
||||
export const topicUrlWs = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/ws`.replaceAll("https://", "wss://").replaceAll("http://", "ws://");
|
||||
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));
|
||||
export const accountUrl = (baseUrl) => `${baseUrl}/v1/account`;
|
||||
export const accountPasswordUrl = (baseUrl) => `${baseUrl}/v1/account/password`;
|
||||
export const accountTokenUrl = (baseUrl) => `${baseUrl}/v1/account/token`;
|
||||
export const accountSettingsUrl = (baseUrl) => `${baseUrl}/v1/account/settings`;
|
||||
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`;
|
||||
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`;
|
||||
export const accountPhoneUrl = (baseUrl) => `${baseUrl}/v1/account/phone`;
|
||||
export const accountPhoneVerifyUrl = (baseUrl) =>
|
||||
`${baseUrl}/v1/account/phone/verify`;
|
||||
export const accountPhoneVerifyUrl = (baseUrl) => `${baseUrl}/v1/account/phone/verify`;
|
||||
export const tiersUrl = (baseUrl) => `${baseUrl}/v1/tiers`;
|
||||
export const shortUrl = (url) => url.replaceAll(/https?:\/\//g, "");
|
||||
export const expandUrl = (url) => [`https://${url}`, `http://${url}`];
|
||||
|
@ -208,9 +194,7 @@ export const formatShortDateTime = (timestamp) => {
|
|||
};
|
||||
|
||||
export const formatShortDate = (timestamp) => {
|
||||
return new Intl.DateTimeFormat("default", { dateStyle: "short" }).format(
|
||||
new Date(timestamp * 1000)
|
||||
);
|
||||
return new Intl.DateTimeFormat("default", { dateStyle: "short" }).format(new Date(timestamp * 1000));
|
||||
};
|
||||
|
||||
export const formatBytes = (bytes, decimals = 2) => {
|
||||
|
@ -312,8 +296,7 @@ export async function* fetchLinesIterator(fileURL, headers) {
|
|||
}
|
||||
|
||||
export const randomAlphanumericString = (len) => {
|
||||
const alphabet =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
let id = "";
|
||||
for (let i = 0; i < len; i++) {
|
||||
id += alphabet[(Math.random() * alphabet.length) | 0];
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue