Make manual eslint fixes

These are safe fixes, more complicated fixes can be done separately
(just disabled those errors for now).

- Reorder declarations to fix `no-use-before-define`
- Rename parameters for `no-shadow`
- Remove unused parameters, functions, imports
- Switch from `++` and `—` to `+= 1` and `-= 1` for `no-unary`
- Use object spreading instead of parameter reassignment in auth utils
- Use `window.location` instead of `location` global
- Use inline JSX strings instead of unescaped values
-
This commit is contained in:
nimbleghost 2023-05-24 10:20:15 +02:00
parent 8319f1cf26
commit 59011c8a32
20 changed files with 369 additions and 351 deletions

View file

@ -261,12 +261,12 @@ class AccountApi {
async createBillingSubscription(tier, interval) {
console.log(`[AccountApi] Creating billing subscription with ${tier} and interval ${interval}`);
return await this.upsertBillingSubscription("POST", tier, interval);
return this.upsertBillingSubscription("POST", tier, interval);
}
async updateBillingSubscription(tier, interval) {
console.log(`[AccountApi] Updating billing subscription with ${tier} and interval ${interval}`);
return await this.upsertBillingSubscription("PUT", tier, interval);
return this.upsertBillingSubscription("PUT", tier, interval);
}
async upsertBillingSubscription(method, tier, interval) {
@ -279,7 +279,7 @@ class AccountApi {
interval,
}),
});
return await response.json(); // May throw SyntaxError
return response.json(); // May throw SyntaxError
}
async deleteBillingSubscription() {
@ -298,7 +298,7 @@ class AccountApi {
method: "POST",
headers: withBearerAuth({}, session.token()),
});
return await response.json(); // May throw SyntaxError
return response.json(); // May throw SyntaxError
}
async verifyPhoneNumber(phoneNumber, channel) {
@ -327,7 +327,7 @@ class AccountApi {
});
}
async deletePhoneNumber(phoneNumber, code) {
async deletePhoneNumber(phoneNumber) {
const url = accountPhoneUrl(config.base_url);
console.log(`[AccountApi] Deleting phone number ${url}`);
await fetchOrThrow(url, {
@ -369,6 +369,7 @@ class AccountApi {
if (e instanceof UnauthorizedError) {
session.resetAndRedirect(routes.login);
}
return undefined;
}
}

View file

@ -1,7 +1,14 @@
/* eslint-disable max-classes-per-file */
import { basicAuth, bearerAuth, encodeBase64Url, topicShortUrl, topicUrlWs } from "./utils";
const retryBackoffSeconds = [5, 10, 20, 30, 60, 120];
export class ConnectionState {
static Connected = "connected";
static Connecting = "connecting";
}
/**
* A connection contains a single WebSocket connection for one topic. It handles its connection
* status itself, including reconnect attempts and backoff.
@ -63,7 +70,7 @@ class Connection {
this.ws = null;
} else {
const retrySeconds = retryBackoffSeconds[Math.min(this.retryCount, retryBackoffSeconds.length - 1)];
this.retryCount++;
this.retryCount += 1;
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);
@ -108,10 +115,4 @@ class Connection {
}
}
export class ConnectionState {
static Connected = "connected";
static Connecting = "connecting";
}
export default Connection;

View file

@ -1,6 +1,9 @@
import Connection from "./Connection";
import { hashCode } from "./utils";
const makeConnectionId = async (subscription, user) =>
user ? hashCode(`${subscription.id}|${user.username}|${user.password ?? ""}|${user.token ?? ""}`) : hashCode(`${subscription.id}`);
/**
* The connection manager keeps track of active connections (WebSocket connections, see Connection).
*
@ -69,8 +72,8 @@ class ConnectionManager {
topic,
user,
since,
(subscriptionId, notification) => this.notificationReceived(subscriptionId, notification),
(subscriptionId, state) => this.stateChanged(subscriptionId, state)
(subId, notification) => this.notificationReceived(subId, notification),
(subId, state) => this.stateChanged(subId, state)
);
this.connections.set(connectionId, connection);
console.log(
@ -112,8 +115,5 @@ class ConnectionManager {
}
}
const makeConnectionId = async (subscription, user) =>
user ? hashCode(`${subscription.id}|${user.username}|${user.password ?? ""}|${user.token ?? ""}`) : hashCode(`${subscription.id}`);
const connectionManager = new ConnectionManager();
export default connectionManager;

View file

@ -29,7 +29,7 @@ class Notifier {
icon: logo,
});
if (notification.click) {
n.onclick = (e) => openUrl(notification.click);
n.onclick = () => openUrl(notification.click);
} else {
n.onclick = () => onClickFallback(subscription);
}
@ -87,7 +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 window.location.protocol === "https:" || window.location.hostname.match("^127.") || window.location.hostname === "localhost";
}
}

View file

@ -23,6 +23,8 @@ class Poller {
const subscriptions = await subscriptionManager.all();
for (const s of subscriptions) {
try {
// TODO(eslint): Switch to Promise.all
// eslint-disable-next-line no-await-in-loop
await this.poll(s);
} catch (e) {
console.log(`[Poller] Error polling ${s.id}`, e);

View file

@ -7,6 +7,7 @@ class SubscriptionManager {
const subscriptions = await db.subscriptions.toArray();
await Promise.all(
subscriptions.map(async (s) => {
// eslint-disable-next-line no-param-reassign
s.new = await db.notifications.where({ subscriptionId: s.id, new: 1 }).count();
})
);
@ -14,7 +15,7 @@ class SubscriptionManager {
}
async get(subscriptionId) {
return await db.subscriptions.get(subscriptionId);
return db.subscriptions.get(subscriptionId);
}
async add(baseUrl, topic, internal) {
@ -40,10 +41,14 @@ class SubscriptionManager {
// Add remote subscriptions
const remoteIds = []; // = topicUrl(baseUrl, topic)
for (let i = 0; i < remoteSubscriptions.length; i++) {
for (let i = 0; i < remoteSubscriptions.length; i += 1) {
const remote = remoteSubscriptions[i];
// TODO(eslint): Switch to Promise.all
// eslint-disable-next-line no-await-in-loop
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;
// TODO(eslint): Switch to Promise.all
// eslint-disable-next-line no-await-in-loop
await this.update(local.id, {
displayName: remote.display_name, // May be undefined
reservation, // May be null!
@ -53,10 +58,12 @@ class SubscriptionManager {
// Remove local subscriptions that do not exist remotely
const localSubscriptions = await db.subscriptions.toArray();
for (let i = 0; i < localSubscriptions.length; i++) {
for (let i = 0; i < localSubscriptions.length; i += 1) {
const local = localSubscriptions[i];
const remoteExists = remoteIds.includes(local.id);
if (!local.internal && !remoteExists) {
// TODO(eslint): Switch to Promise.all
// eslint-disable-next-line no-await-in-loop
await this.remove(local.id);
}
}
@ -101,6 +108,7 @@ class SubscriptionManager {
return false;
}
try {
// eslint-disable-next-line no-param-reassign
notification.new = 1; // New marker (used for bubble indicator); cannot be boolean; Dexie index limitation
await db.notifications.add({ ...notification, subscriptionId }); // FIXME consider put() for double tab
await db.subscriptions.update(subscriptionId, {

View file

@ -1,37 +1,6 @@
/* eslint-disable max-classes-per-file */
// This is a subset of, and the counterpart to errors.go
export const fetchOrThrow = async (url, options) => {
const response = await fetch(url, options);
if (response.status !== 200) {
await throwAppError(response);
}
return response; // Promise!
};
export const throwAppError = async (response) => {
if (response.status === 401 || response.status === 403) {
console.log(`[Error] HTTP ${response.status}`, response);
throw new UnauthorizedError();
}
const error = await maybeToJson(response);
if (error?.code) {
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) {
throw new TopicReservedError();
} else if (error.code === AccountCreateLimitReachedError.CODE) {
throw new AccountCreateLimitReachedError();
} else if (error.code === IncorrectPasswordError.CODE) {
throw new IncorrectPasswordError();
} else if (error?.error) {
throw new Error(`Error ${error.code}: ${error.error}`);
}
}
console.log(`[Error] HTTP ${response.status}, not a ntfy error`, response);
throw new Error(`Unexpected response ${response.status}`);
};
const maybeToJson = async (response) => {
try {
return await response.json();
@ -77,3 +46,35 @@ export class IncorrectPasswordError extends Error {
super("Password incorrect");
}
}
export const throwAppError = async (response) => {
if (response.status === 401 || response.status === 403) {
console.log(`[Error] HTTP ${response.status}`, response);
throw new UnauthorizedError();
}
const error = await maybeToJson(response);
if (error?.code) {
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) {
throw new TopicReservedError();
} else if (error.code === AccountCreateLimitReachedError.CODE) {
throw new AccountCreateLimitReachedError();
} else if (error.code === IncorrectPasswordError.CODE) {
throw new IncorrectPasswordError();
} else if (error?.error) {
throw new Error(`Error ${error.code}: ${error.error}`);
}
}
console.log(`[Error] HTTP ${response.status}, not a ntfy error`, response);
throw new Error(`Unexpected response ${response.status}`);
};
export const fetchOrThrow = async (url, options) => {
const response = await fetch(url, options);
if (response.status !== 200) {
await throwAppError(response);
}
return response; // Promise!
};

View file

@ -9,6 +9,10 @@ import pop from "../sounds/pop.mp3";
import popSwoosh from "../sounds/pop-swoosh.mp3";
import config from "./config";
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}`;
export const topicUrl = (baseUrl, topic) => `${baseUrl}/${topic}`;
export const topicUrlWs = (baseUrl, topic) =>
`${topicUrl(baseUrl, topic)}/ws`.replaceAll("https://", "wss://").replaceAll("http://", "ws://");
@ -28,13 +32,11 @@ export const accountBillingSubscriptionUrl = (baseUrl) => `${baseUrl}/v1/account
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 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}`;
export const validUrl = (url) => url.match(/^https?:\/\/.+/);
export const disallowedTopic = (topic) => config.disallowed_topics.includes(topic);
export const validTopic = (topic) => {
if (disallowedTopic(topic)) {
return false;
@ -42,8 +44,6 @@ export const validTopic = (topic) => {
return topic.match(/^([-_a-zA-Z0-9]{1,64})$/); // Regex must match Go & Android app!
};
export const disallowedTopic = (topic) => config.disallowed_topics.includes(topic);
export const topicDisplayName = (subscription) => {
if (subscription.displayName) {
return subscription.displayName;
@ -67,13 +67,6 @@ const toEmojis = (tags) => {
return tags.filter((tag) => tag in emojis).map((tag) => emojis[tag]);
};
export const formatTitleWithDefault = (m, fallback) => {
if (m.title) {
return formatTitle(m);
}
return fallback;
};
export const formatTitle = (m) => {
const emojiList = toEmojis(m.tags);
if (emojiList.length > 0) {
@ -82,6 +75,13 @@ export const formatTitle = (m) => {
return m.title;
};
export const formatTitleWithDefault = (m, fallback) => {
if (m.title) {
return formatTitle(m);
}
return fallback;
};
export const formatMessage = (m) => {
if (m.title) {
return m.message;
@ -98,6 +98,25 @@ export const unmatchedTags = (tags) => {
return tags.filter((tag) => !(tag in emojis));
};
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) });
export const maybeWithBearerAuth = (headers, token) => {
if (token) {
return withBearerAuth(headers, token);
}
return headers;
};
export const withBasicAuth = (headers, username, password) => ({ ...headers, Authorization: basicAuth(username, password) });
export const maybeWithAuth = (headers, user) => {
if (user && user.password) {
return withBasicAuth(headers, user.username, user.password);
@ -108,31 +127,6 @@ export const maybeWithAuth = (headers, user) => {
return headers;
};
export const maybeWithBearerAuth = (headers, token) => {
if (token) {
return withBearerAuth(headers, token);
}
return headers;
};
export const withBasicAuth = (headers, username, password) => {
headers.Authorization = basicAuth(username, password);
return headers;
};
export const basicAuth = (username, password) => `Basic ${encodeBase64(`${username}:${password}`)}`;
export const withBearerAuth = (headers, token) => {
headers.Authorization = bearerAuth(token);
return headers;
};
export const bearerAuth = (token) => `Bearer ${token}`;
export const encodeBase64 = (s) => Base64.encode(s);
export const encodeBase64Url = (s) => Base64.encodeURI(s);
export const maybeAppendActionErrors = (message, notification) => {
const actionErrors = (notification.actions ?? [])
.map((action) => action.error)
@ -147,10 +141,12 @@ export const maybeAppendActionErrors = (message, notification) => {
export const shuffle = (arr) => {
let j;
let x;
for (let index = arr.length - 1; index > 0; index--) {
for (let index = arr.length - 1; index > 0; index -= 1) {
j = Math.floor(Math.random() * (index + 1));
x = arr[index];
// eslint-disable-next-line no-param-reassign
arr[index] = arr[j];
// eslint-disable-next-line no-param-reassign
arr[j] = x;
}
return arr;
@ -165,9 +161,11 @@ export const splitNoEmpty = (s, delimiter) =>
/** 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++) {
for (let i = 0; i < s.length; i += 1) {
const char = s.charCodeAt(i);
// eslint-disable-next-line no-bitwise
hash = (hash << 5) - hash + char;
// eslint-disable-next-line no-bitwise
hash &= hash; // Convert to 32bit integer
}
return hash;
@ -248,6 +246,7 @@ export const playSound = async (id) => {
};
// From: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
// eslint-disable-next-line func-style
export async function* fetchLinesIterator(fileURL, headers) {
const utf8Decoder = new TextDecoder("utf-8");
const response = await fetch(fileURL, {
@ -267,9 +266,12 @@ export async function* fetchLinesIterator(fileURL, headers) {
break;
}
const remainder = chunk.substr(startIndex);
// eslint-disable-next-line no-await-in-loop
({ value: chunk, done: readerDone } = await reader.read());
chunk = remainder + (chunk ? utf8Decoder.decode(chunk) : "");
startIndex = re.lastIndex = 0;
startIndex = 0;
re.lastIndex = 0;
// eslint-disable-next-line no-continue
continue;
}
yield chunk.substring(startIndex, result.index);
@ -283,7 +285,8 @@ export async function* fetchLinesIterator(fileURL, headers) {
export const randomAlphanumericString = (len) => {
const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let id = "";
for (let i = 0; i < len; i++) {
for (let i = 0; i < len; i += 1) {
// eslint-disable-next-line no-bitwise
id += alphabet[(Math.random() * alphabet.length) | 0];
}
return id;