diff --git a/data/eventFirmware.json b/data/eventFirmware.json new file mode 100644 index 0000000..f0d97ae --- /dev/null +++ b/data/eventFirmware.json @@ -0,0 +1,63 @@ +{ + "version": 1, + "generatedAt": "2026-06-23T00:00:00Z", + "source": "bundled", + "editions": [ + { + "edition": "HAMVENTION", + "displayName": "Hamvention", + "welcomeMessage": "Welcome to Hamvention! 🍖📻", + "eventStart": "2026-05-15", + "eventEnd": "2026-05-17", + "timeZone": "America/New_York", + "location": "Xenia, Ohio, USA", + "iconUrl": "https://api.meshtastic.org/resource/eventFirmware/hamvention.png", + "accentColor": "#BF1E2E", + "links": [ + { "label": "Event Website", "url": "https://hamvention.org" } + ] + }, + { + "edition": "OPEN_SAUCE", + "displayName": "Open Sauce", + "welcomeMessage": "Welcome to Open Sauce! 🔧", + "eventStart": "2026-07-17", + "eventEnd": "2026-07-19", + "timeZone": "America/Los_Angeles", + "location": "San Mateo, California, USA", + "iconUrl": null, + "accentColor": "#E94F1D", + "links": [ + { "label": "Event Website", "url": "https://opensauce.com" } + ] + }, + { + "edition": "DEFCON", + "displayName": "DEFCON", + "welcomeMessage": "Welcome to DEFCON! 💀", + "eventStart": "2026-08-06", + "eventEnd": "2026-08-09", + "timeZone": "America/Los_Angeles", + "location": "Las Vegas, Nevada, USA", + "iconUrl": null, + "accentColor": "#0D294A", + "links": [ + { "label": "Event Website", "url": "https://defcon.org" } + ] + }, + { + "edition": "BURNING_MAN", + "displayName": "Burning Man", + "welcomeMessage": "Welcome to Burning Man! 🔥", + "eventStart": "2026-08-30", + "eventEnd": "2026-09-07", + "timeZone": "America/Los_Angeles", + "location": "Black Rock City, Nevada, USA", + "iconUrl": null, + "accentColor": "#EC8819", + "links": [ + { "label": "Event Website", "url": "https://burningman.org" } + ] + } + ] +} diff --git a/src/index.ts b/src/index.ts index 4d4a828..e486fc6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,8 @@ import { logger } from "@tinyhttp/logger"; import { RegisterMqttClient } from "./lib/index.js"; import { DeviceLinksRoutes, + EventFirmwareIconRoutes, + EventFirmwareRoutes, FirmwareRoutes, GithubRoutes, MqttRoutes, @@ -83,6 +85,8 @@ FirmwareRoutes(); GithubRoutes(); ResourceRoutes(); DeviceLinksRoutes(); +EventFirmwareRoutes(); +EventFirmwareIconRoutes(); UpdaterRoutes(); MqttRoutes(); diff --git a/src/lib/eventFirmware.ts b/src/lib/eventFirmware.ts new file mode 100644 index 0000000..c104e45 --- /dev/null +++ b/src/lib/eventFirmware.ts @@ -0,0 +1,45 @@ +import { readFileSync } from "node:fs"; + +// Event-firmware display metadata. Authored to the contract in +// meshtastic/Meshtastic-Android#5920 (schemas/event_firmware.schema.json). The committed file is +// already in the response envelope shape, so unlike deviceLinks there is nothing to transform — +// we parse and serve it verbatim. This repo is the source of truth the app syncs from. +// The path resolves the same in dev (src/lib) and prod (dist/lib), both two levels below the root. +const DATA_PATH = new URL("../../data/eventFirmware.json", import.meta.url); + +export interface EventFirmwareLink { + label: string; + url: string; +} + +export interface EventFirmwareEdition { + edition: string; // FirmwareEdition proto enum name, e.g. "HAMVENTION" + displayName: string; + welcomeMessage: string; + eventStart?: string | null; + eventEnd?: string | null; + timeZone?: string | null; + location?: string | null; + iconUrl?: string | null; + accentColor?: string | null; + links?: EventFirmwareLink[]; +} + +export interface EventFirmwareResponse { + version: number; + generatedAt?: string; + source?: string; + editions: EventFirmwareEdition[]; +} + +// Parse once per process — the file only changes via a committed edit + redeploy. +let cached: EventFirmwareResponse | null = null; + +export const getEventFirmware = (): EventFirmwareResponse => { + if (!cached) { + cached = JSON.parse( + readFileSync(DATA_PATH, "utf8"), + ) as EventFirmwareResponse; + } + return cached; +}; diff --git a/src/routes/eventFirmware.ts b/src/routes/eventFirmware.ts new file mode 100644 index 0000000..53f06dc --- /dev/null +++ b/src/routes/eventFirmware.ts @@ -0,0 +1,12 @@ +import { app } from "../index.js"; +import { getEventFirmware } from "../lib/eventFirmware.js"; + +export const EventFirmwareRoutes = () => + app.get("resource/eventFirmware", (_req, res) => { + try { + res.json(getEventFirmware()); + } catch (err) { + console.error("eventFirmware", err); + res.sendStatus(502); + } + }); diff --git a/src/routes/eventFirmwareIcon.ts b/src/routes/eventFirmwareIcon.ts new file mode 100644 index 0000000..0261785 --- /dev/null +++ b/src/routes/eventFirmwareIcon.ts @@ -0,0 +1,24 @@ +import { readFileSync } from "node:fs"; +import { app } from "../index.js"; + +// Hamvention is the only event edition with bundled art today (the others' iconUrl is null). +// Served under resource/ alongside the metadata, mirroring how data/ files map to resource/* routes. +// ponytail: one fixed route for the one icon we host; generalize to a :slug param when more editions ship art. +const ICON_PATH = new URL( + "../../static/eventFirmware/hamvention.png", + import.meta.url, +); + +let icon: Buffer | null = null; + +export const EventFirmwareIconRoutes = () => + app.get("resource/eventFirmware/hamvention.png", (_req, res) => { + try { + if (!icon) icon = readFileSync(ICON_PATH); + res.setHeader("Content-Type", "image/png"); + res.send(icon); + } catch (err) { + console.error("eventFirmwareIcon", err); + res.sendStatus(404); + } + }); diff --git a/src/routes/index.ts b/src/routes/index.ts index b5fd7f6..482c8a4 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -1,4 +1,6 @@ export { DeviceLinksRoutes } from "./deviceLinks.js"; +export { EventFirmwareRoutes } from "./eventFirmware.js"; +export { EventFirmwareIconRoutes } from "./eventFirmwareIcon.js"; export { FirmwareRoutes } from "./firmware.js"; export { GithubRoutes } from "./github.js"; export { MqttRoutes } from "./mqtt.js"; diff --git a/static/eventFirmware/hamvention.png b/static/eventFirmware/hamvention.png new file mode 100644 index 0000000..8f33121 Binary files /dev/null and b/static/eventFirmware/hamvention.png differ