This commit is contained in:
binwiederhier
2026-07-08 22:42:42 +02:00
parent 08d81a3645
commit 6869d166ae
18 changed files with 50 additions and 42 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
+12 -4
View File
@@ -5,7 +5,9 @@
font-family: "Roboto";
font-style: normal;
font-weight: 300;
src: local(""), url("../fonts/roboto-v29-latin-300.woff2") format("woff2");
src:
local(""),
url("../fonts/roboto-v29-latin-300.woff2") format("woff2");
}
/* roboto-regular - latin */
@@ -13,7 +15,9 @@
font-family: "Roboto";
font-style: normal;
font-weight: 400;
src: local(""), url("../fonts/roboto-v29-latin-regular.woff2") format("woff2");
src:
local(""),
url("../fonts/roboto-v29-latin-regular.woff2") format("woff2");
}
/* roboto-500 - latin */
@@ -21,7 +25,9 @@
font-family: "Roboto";
font-style: normal;
font-weight: 500;
src: local(""), url("../fonts/roboto-v29-latin-500.woff2") format("woff2");
src:
local(""),
url("../fonts/roboto-v29-latin-500.woff2") format("woff2");
}
/* roboto-700 - latin */
@@ -29,5 +35,7 @@
font-family: "Roboto";
font-style: normal;
font-weight: 700;
src: local(""), url("../fonts/roboto-v29-latin-700.woff2") format("woff2");
src:
local(""),
url("../fonts/roboto-v29-latin-700.woff2") format("woff2");
}
+3 -3
View File
@@ -61,7 +61,7 @@ const handlePushMessage = async (data) => {
topicRoute: new URL(message.topic, self.location.origin).toString(),
baseUrl: subscription.baseUrl,
topic: subscription.topic,
})
}),
);
// Delete existing notification with same sequence ID (if any)
@@ -417,7 +417,7 @@ self.addEventListener("notificationclick", (event) => {
// [{"revision":"aaabbbcccdddeeefff12345","url":"/index.html"},...]
precacheAndRoute(
// eslint-disable-next-line no-underscore-dangle
self.__WB_MANIFEST
self.__WB_MANIFEST,
);
// Claim all open windows
@@ -441,7 +441,7 @@ if (!import.meta.env.DEV) {
// the app root itself, could be /, or not
new RegExp(`^${config.app_root}$`),
],
})
}),
);
// the manifest excludes config.js (see vite.config.js) since the dist-file differs from the
+2 -2
View File
@@ -65,7 +65,7 @@ describe("AccountApi.create", () => {
await accountApi.create("phil", "pw");
expect(fetchMock).toHaveBeenCalledWith(
"https://ntfy.sh/v1/account",
expect.objectContaining({ method: "POST", body: JSON.stringify({ username: "phil", password: "pw", email: "" }) })
expect.objectContaining({ method: "POST", body: JSON.stringify({ username: "phil", password: "pw", email: "" }) }),
);
});
});
@@ -195,7 +195,7 @@ describe("AccountApi.sync", () => {
notification: { sound: "ding", delete_after: 3600, min_priority: 3 },
subscriptions: [{ topic: "t" }],
reservations: [{ topic: "t" }],
})
}),
);
await accountApi.sync();
+3 -3
View File
@@ -57,7 +57,7 @@ describe("Api.publish", () => {
expect.objectContaining({
method: "PUT",
body: JSON.stringify({ topic: "mytopic", message: "Hello", priority: 5, tags: ["warning"] }),
})
}),
);
});
@@ -109,7 +109,7 @@ describe("Api web push", () => {
expect.objectContaining({
method: "POST",
body: JSON.stringify({ endpoint: "https://push.example/abc", auth: "AUTH", p256dh: "P256", topics: ["topicA", "topicB"] }),
})
}),
);
});
@@ -121,7 +121,7 @@ describe("Api web push", () => {
expect.objectContaining({
method: "DELETE",
body: JSON.stringify({ endpoint: "https://push.example/abc" }),
})
}),
);
});
});
+1 -1
View File
@@ -67,7 +67,7 @@ class Connection {
this.ws.onclose = (event) => {
if (event.wasClean) {
console.log(
`[Connection, ${this.shortUrl}, ${this.connectionId}] Connection closed cleanly, code=${event.code} reason=${event.reason}`
`[Connection, ${this.shortUrl}, ${this.connectionId}] Connection closed cleanly, code=${event.code} reason=${event.reason}`,
);
this.ws = null;
} else {
+2 -2
View File
@@ -70,13 +70,13 @@ class ConnectionManager {
user,
since,
(subId, notification) => this.notificationReceived(subId, notification),
(subId, state) => this.stateChanged(subId, state)
(subId, state) => this.stateChanged(subId, state),
);
this.connections.set(connectionId, connection);
console.log(
`[ConnectionManager] Starting new connection ${connectionId} (subscription ${subscriptionId} with user ${
user ? user.username : "anonymous"
})`
})`,
);
connection.start();
}
+1 -1
View File
@@ -30,7 +30,7 @@ class Notifier {
topicRoute: new URL(routes.forSubscription(subscription), window.location.origin).toString(),
baseUrl: subscription.baseUrl,
topic: subscription.topic,
})
}),
);
}
+2 -2
View File
@@ -35,7 +35,7 @@ class Poller {
} catch (e) {
console.log(`[Poller] Error polling ${s.id}`, e);
}
})
}),
);
}
@@ -61,7 +61,7 @@ class Poller {
if (deletedSequenceIds.length > 0) {
console.log(`[Poller] Deleting notifications with deleted sequence IDs for ${subscription.id}`, deletedSequenceIds);
await Promise.all(
deletedSequenceIds.map((sequenceId) => subscriptionManager.deleteNotificationBySequenceId(subscription.id, sequenceId))
deletedSequenceIds.map((sequenceId) => subscriptionManager.deleteNotificationBySequenceId(subscription.id, sequenceId)),
);
}
+4 -4
View File
@@ -18,7 +18,7 @@ export class SubscriptionManager {
subscriptions.map(async (s) => ({
...s,
new: await this.db.notifications.where({ subscriptionId: s.id, new: 1 }).count(),
}))
})),
);
}
@@ -122,7 +122,7 @@ export class SubscriptionManager {
});
return local.id;
})
}),
);
// Remove local subscriptions that do not exist remotely
@@ -134,7 +134,7 @@ export class SubscriptionManager {
if (!local.internal && !remoteExists) {
await this.remove(local);
}
})
}),
);
}
@@ -144,7 +144,7 @@ export class SubscriptionManager {
if (!browserSubscription) {
console.log(
"[SubscriptionManager] No browser subscription currently exists, so web push was never enabled or the notification permission was removed. Skipping."
"[SubscriptionManager] No browser subscription currently exists, so web push was never enabled or the notification permission was removed. Skipping.",
);
return;
}
+3 -3
View File
@@ -56,7 +56,7 @@ describe("throwAppError", () => {
await expect(throwAppError(fakeResponse(409, { code: UserExistsError.CODE }))).rejects.toBeInstanceOf(UserExistsError);
await expect(throwAppError(fakeResponse(409, { code: TopicReservedError.CODE }))).rejects.toBeInstanceOf(TopicReservedError);
await expect(throwAppError(fakeResponse(429, { code: AccountActionLimitReachedError.CODE }))).rejects.toBeInstanceOf(
AccountActionLimitReachedError
AccountActionLimitReachedError,
);
});
@@ -78,7 +78,7 @@ describe("fetchOrThrow", () => {
const response = { status: 200 };
vi.stubGlobal(
"fetch",
vi.fn(async () => response)
vi.fn(async () => response),
);
await expect(fetchOrThrow("https://ntfy.sh/mytopic/json")).resolves.toBe(response);
});
@@ -86,7 +86,7 @@ describe("fetchOrThrow", () => {
it("throws on a non-200 response", async () => {
vi.stubGlobal(
"fetch",
vi.fn(async () => fakeResponse(401))
vi.fn(async () => fakeResponse(401)),
);
await expect(fetchOrThrow("https://ntfy.sh/mytopic/json")).rejects.toBeInstanceOf(UnauthorizedError);
});
+1 -1
View File
@@ -130,7 +130,7 @@ const Layout = () => {
const [selected] = (subscriptionsWithoutInternal || []).filter(
(s) =>
(params.baseUrl && expandUrl(params.baseUrl).includes(s.baseUrl) && params.topic === s.topic) ||
(config.base_url === s.baseUrl && params.topic === s.topic)
(config.base_url === s.baseUrl && params.topic === s.topic),
);
useConnectionListeners(account, subscriptions, users, webPushTopics);
+3 -3
View File
@@ -80,7 +80,7 @@ const SingleSubscriptionList = (props) => {
// getAllNotifications() filtered by id), so topic switches are instant.
const notifications = useMemo(
() => allNotifications.filter((notification) => notification.subscriptionId === subscription.id),
[allNotifications, subscription.id]
[allNotifications, subscription.id],
);
if (notifications.length === 0) {
return <NoNotifications subscription={subscription} />;
@@ -104,7 +104,7 @@ const NotificationList = (props) => {
main.scrollTo(0, 0);
}
},
[props.id]
[props.id],
);
return (
@@ -319,7 +319,7 @@ const Attachment = (props) => {
infos.push(
t("notifications_attachment_link_expires", {
date: formatDateTime(attachment.expires, dateFormat, timeFormat),
})
}),
);
}
if (expired) {
+4 -4
View File
@@ -168,7 +168,7 @@ const PublishDialog = (props) => {
loaded: formatBytes(ev.loaded),
total: formatBytes(ev.total),
percent: Math.round((ev.loaded * 100.0) / ev.total),
})
}),
);
} else {
setStatus(t("publish_dialog_progress_uploading"));
@@ -201,19 +201,19 @@ const PublishDialog = (props) => {
t("publish_dialog_attachment_limits_file_and_quota_reached", {
fileSizeLimit: formatBytes(fileSizeLimit),
remainingBytes: formatBytes(remainingBytes),
})
}),
);
} else if (fileSizeLimitReached) {
setAttachFileError(
t("publish_dialog_attachment_limits_file_reached", {
fileSizeLimit: formatBytes(fileSizeLimit),
})
}),
);
} else if (quotaReached) {
setAttachFileError(
t("publish_dialog_attachment_limits_quota_reached", {
remainingBytes: formatBytes(remainingBytes),
})
}),
);
} else {
setAttachFileError("");
+2 -2
View File
@@ -93,7 +93,7 @@ const SubscribePage = (props) => {
const { topic } = props;
const existingTopicUrls = props.subscriptions.map((s) => topicUrl(s.baseUrl, s.topic));
const existingBaseUrls = Array.from(new Set([publicBaseUrl, ...props.subscriptions.map((s) => s.baseUrl)])).filter(
(s) => s !== config.base_url
(s) => s !== config.base_url,
);
const showReserveTopicCheckbox = config.enable_reservations && !anotherServerVisible && (config.enable_payments || account);
const reserveTopicEnabled =
@@ -113,7 +113,7 @@ const SubscribePage = (props) => {
setError(
t("subscribe_dialog_error_user_not_authorized", {
username,
})
}),
);
return;
}
+2 -2
View File
@@ -120,14 +120,14 @@ export const SubscriptionPopup = (props) => {
`Hello friend, this is a test notification from ntfy web. It's ${formatDateTime(
nowSeconds,
dateFormat,
timeFormat
timeFormat,
)} right now. Is that early or late?`,
`So I heard you like ntfy? If that's true, go to GitHub and star it, or to the Play store and rate it. Thanks! Oh yeah, this is a test notification.`,
`It's almost like you want to hear what I have to say. I'm not even a machine. I'm just a sentence that Phil typed on a random Thursday.`,
`Alright then, it's ${formatDateTime(
nowSeconds,
dateFormat,
timeFormat
timeFormat,
)} already. Boy oh boy, where did the time go? I hope you're alright, friend.`,
`There are nine million bicycles in Beijing That's a fact; It's a thing we can't deny. I wonder if that's true ...`,
`I'm really excited that you're trying out ntfy. Did you know that there are a few public topics, such as ntfy.sh/stats and ntfy.sh/announcements.`,
+3 -3
View File
@@ -30,7 +30,7 @@ export const useConnectionListeners = (account, subscriptions, users, webPushTop
// wsSubscriptions should stay stable unless the list of subscription IDs changes. Without the memo, the connection
// listener calls a refresh for no reason. This isn't a problem due to the makeConnectionId, but it triggers an
// unnecessary recomputation for every received message.
[JSON.stringify({ subscriptions: subscriptions?.map(({ id }) => id), webPushTopics })]
[JSON.stringify({ subscriptions: subscriptions?.map(({ id }) => id), webPushTopics })],
);
// Register listeners for incoming messages, and connection state changes
@@ -103,7 +103,7 @@ export const useConnectionListeners = (account, subscriptions, users, webPushTop
},
// We have to disable dep checking for "navigate". This is fine, it never changes.
[]
[],
);
// Sync topic listener: For accounts with sync_topic, subscribe to an internal topic
@@ -233,7 +233,7 @@ export const useWebPushTopics = () => {
const topics = useLiveQuery(
async () => subscriptionManager.webPushTopics(pushPossible),
// invalidate (reload) query when these values change
[pushPossible]
[pushPossible],
);
useWebPushListener(topics);
+1 -1
View File
@@ -39,7 +39,7 @@ export default defineConfig(({ mode }) => ({
...entry,
url: "app.html",
}
: entry
: entry,
),
}),
],