Show announcements from the Bambuddy maintainers

Fetch a signed feed.json from the public bambuddy-notifications repo on
GitHub at startup and every 6 hours. Nothing about the install is sent;
targeting (version, beta channel, install type) is decided locally.

- Ed25519 against a key built into the app; an older serial is refused so a
  withdrawn message can't come back. The feed replaces the stored list, and
  a failed or rejected fetch keeps the last good one.
- Sidebar entry above System with an unread count, a slide-over list, and a
  banner for unread important/critical messages. Read state per user.
- Admins by default; Settings > General > Updates can show them to all
  users or switch them off, which also stops the fetch.
- Plain text only; links to github.com and bambuddy.cool only.
This commit is contained in:
maziggy
2026-10-01 10:45:06 +02:00
parent 1cc4bae5d2
commit 5eb37cd41c
35 changed files with 1961 additions and 1 deletions
+29
View File
@@ -1383,6 +1383,9 @@ export interface AppSettings {
check_updates: boolean;
check_printer_firmware: boolean;
include_beta_updates: boolean;
// Announcements from the Bambuddy maintainers (a signed file on GitHub).
announcements_enabled?: boolean;
announcements_all_users?: boolean;
// #1589: false hides the local username/password form on the login page;
// BAMBUDDY_LOCAL_LOGIN=true on the server flips the reported value back to
// true so the env-var recovery path is visible to the SPA.
@@ -9196,6 +9199,32 @@ export const bugReportApi = {
}),
};
export type AnnouncementLevel = 'info' | 'important' | 'critical';
export interface AnnouncementText {
title: string;
body: string;
link_label?: string;
}
// One message from the Bambuddy maintainers. `texts` holds every language the
// message was written in; English is always there.
export interface Announcement {
id: string;
level: AnnouncementLevel;
texts: Record<string, AnnouncementText>;
link_url: string | null;
published_at: string | null;
expires_at: string | null;
read: boolean;
}
export const announcementsApi = {
list: () => request<Announcement[]>('/announcements'),
markRead: (id: string) =>
request<void>(`/announcements/${encodeURIComponent(id)}/read`, { method: 'POST' }),
};
export interface SponsorPromptCheckResponse {
show: boolean;
milestone?: string;