From ab826d6bbfb3e4be7829d129481836468ecfb040 Mon Sep 17 00:00:00 2001 From: Seth Etter Date: Fri, 19 Jun 2026 17:12:13 -0500 Subject: [PATCH] openmeet: make client stateful, hold tokens internally --- src/notifier.ts | 12 +++++----- src/openmeet.ts | 58 ++++++++++++++++++++++++++++++------------------- 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/src/notifier.ts b/src/notifier.ts index 9e5b762..c1cb167 100644 --- a/src/notifier.ts +++ b/src/notifier.ts @@ -1,7 +1,7 @@ import { setTimeout as sleep } from "node:timers/promises"; import type { Channel } from "./channels/channel.ts"; import type { Config } from "./config.ts"; -import { authenticate, getUpcomingEvents } from "./openmeet.ts"; +import { getOpenMeetClient } from "./openmeet.ts"; import { nextWeeklyOccurrence, TIMEZONE } from "./schedule.ts"; interface Notifier { @@ -17,7 +17,7 @@ interface NotifierOpts { } export async function newNotifier({ cfg, aborter, channels }: NotifierOpts): Promise { - let tokens = await authenticate(cfg); + const om = await getOpenMeetClient(cfg); return { // Runs a loop that checks for both single-notify events and weekly digests @@ -34,9 +34,7 @@ export async function newNotifier({ cfg, aborter, channels }: NotifierOpts): Pro while (!aborter.signal.aborted) { try { - const { events, tokens: newTokens } = await getUpcomingEvents(cfg, tokens); - // Tokens can be refreshed inside the above function, so make sure we update them. - tokens = newTokens; + const events = await om.getUpcomingEvents(cfg); // TODO: Only handle events that have been created for more than 24 hours so // we don't notify too early. @@ -82,14 +80,14 @@ export async function newNotifier({ cfg, aborter, channels }: NotifierOpts): Pro // Single fetch-and-notify pass in the weekly digest format, then exit. async once(): Promise { - const { events } = await getUpcomingEvents(cfg, tokens); + const events = await om.getUpcomingEvents(cfg); for (const ch of channels) await ch.notifyMany(events); }, // Single fetch, then send one event (first, or by slug) in the new-event // format, then exit. async onceEvent(slug?: string): Promise { - const { events } = await getUpcomingEvents(cfg, tokens); + const events = await om.getUpcomingEvents(cfg); if (events.length === 0) throw new Error("no upcoming events found"); const event = slug ? events.find((e) => e.slug === slug) : events[0]!; diff --git a/src/openmeet.ts b/src/openmeet.ts index d32c9c5..0004168 100644 --- a/src/openmeet.ts +++ b/src/openmeet.ts @@ -64,29 +64,43 @@ async function refreshOrReauth( } } -// Fetches all events in [today, today+7d), refreshing/re-authing on 401. -// Returns the events along with the current tokens so the caller can thread -// them into the next call. -export async function getUpcomingEvents( +interface OpenMeetClient { + getUpcomingEvents( + cfg: OpenMeetConfig & AtprotoLogin, + today?: Temporal.PlainDate, + ): Promise; +} + +let _client: OpenMeetClient | null = null; + +export async function getOpenMeetClient( cfg: OpenMeetConfig & AtprotoLogin, - tokens: Tokens, - today: Temporal.PlainDate = Temporal.Now.plainDateISO(TIMEZONE), -): Promise<{ events: OpenMeetEvent[]; tokens: Tokens }> { - const url = new URL(`${cfg.openMeetBaseUrl}/api/groups/${GROUP_SLUG}/events`); - url.searchParams.set("startDate", today.toPlainDateTime().toString()); +): Promise { + if (_client) return _client; + + let tokens = await authenticate(cfg); - for (let attempt = 1; ; attempt++) { - try { - const events = await fetchJson("events fetch", url, { - headers: headers(tokens), - }); - return { events, tokens }; - } catch (err) { - if (err instanceof HttpError && err.status === 401 && attempt < 3) { - tokens = await refreshOrReauth(cfg, tokens); - continue; + return { + async getUpcomingEvents( + cfg: OpenMeetConfig & AtprotoLogin, + today: Temporal.PlainDate = Temporal.Now.plainDateISO(TIMEZONE), + ): Promise { + const url = new URL(`${cfg.openMeetBaseUrl}/api/groups/${GROUP_SLUG}/events`); + url.searchParams.set("startDate", today.toPlainDateTime().toString()); + + for (let attempt = 1; ; attempt++) { + try { + return fetchJson("events fetch", url, { + headers: headers(tokens), + }); + } catch (err) { + if (err instanceof HttpError && err.status === 401 && attempt < 3) { + tokens = await refreshOrReauth(cfg, tokens); + continue; + } + throw err; + } } - throw err; - } - } + }, + }; } -- 2.51.2