Fix push notifications for account runtimes

This commit is contained in:
jjbrunton committed 2026-08-11 22:36:21 +01:00
1 parent a71ad56f3c
commit c028637556
5 files changed
+178 -1

No files matched your search

+1
View File
@@ -373,6 +373,7 @@ export async function bootstrap(
logger,
db,
getBotMode: () => webServer?.getMode() ?? 'running',
events: broadcaster.dashboardEvents,
}),
);
const redditAccounts = new RedditAccountsService(
@@ -0,0 +1,82 @@
import {
type ChatService,
type SyncCompletion,
type SyncProgress,
} from './chat/index.js';
/** Events emitted by one account-owned chat service into the shared UI/push hub. */
export interface AccountRuntimeEventReporter {
conversation(input: { id: string; state: 'replied'; unread: number }): unknown;
reply(input: { conversationId: string; id: string; username: string; body?: string }): unknown;
mediaReceived(input: { conversationId: string; username: string; count: number }): unknown;
sync(input:
| { phase: 'started'; id: string; kind: 'delta' | 'full' }
| { phase: 'progress'; id: string; kind: 'delta' | 'full'; current: number; total?: number; label?: string }
| { phase: 'completed'; id: string; kind: 'delta' | 'full'; processed: number }
| { phase: 'failed'; id: string; kind: 'delta' | 'full'; error: string }
): unknown;
}
type AccountChatEventSource = Pick<
ChatService,
'setProgressListener' | 'setNewReplyListener' | 'setMediaReceivedListener'
>;
/** Connects one account-owned ChatService to the process-wide event stream. */
export function wireAccountChatEvents(
chatService: AccountChatEventSource,
events: AccountRuntimeEventReporter,
accountId: string,
): void {
let activeSync: SyncProgress | null = null;
chatService.setProgressListener((progress, completion) => {
if (progress) {
const id = accountSyncId(accountId, progress.startedAt);
if (progress.phase === 'starting') {
events.sync({ phase: 'started', id, kind: progress.kind });
} else {
events.sync({
phase: 'progress',
id,
kind: progress.kind,
current: progress.current,
total: progress.total,
label: progress.label ?? progress.phase,
});
}
activeSync = progress;
return;
}
if (!activeSync) return;
reportSyncCompletion(events, accountId, activeSync, completion);
activeSync = null;
});
chatService.setMediaReceivedListener(({ conversationId, username, count }) => {
events.mediaReceived({ conversationId, username, count });
});
chatService.setNewReplyListener(({ conversationId, replyId, username, replySnippet }) => {
events.conversation({ id: conversationId, state: 'replied', unread: 1 });
events.reply({ conversationId, id: replyId, username, body: replySnippet });
});
}
function reportSyncCompletion(
events: AccountRuntimeEventReporter,
accountId: string,
progress: SyncProgress,
completion?: SyncCompletion,
): void {
const id = accountSyncId(accountId, progress.startedAt);
if (completion?.status === 'failed') {
events.sync({ phase: 'failed', id, kind: progress.kind, error: completion.error });
return;
}
events.sync({ phase: 'completed', id, kind: progress.kind, processed: progress.current });
}
function accountSyncId(accountId: string, startedAt: number): string {
return `chat-${accountId}-${startedAt}`;
}
@@ -66,12 +66,17 @@ import { AccountGrowthRepository } from '../database/growth-repository.js';
import { AccountBrowsingSimulationRepository } from '../database/browsing-simulation-repository.js';
import { AccountBrowsingSimulationService } from './account-browsing-simulation.js';
import { allowsConversationSync, allowsSending, type BotMode } from './bot-mode.js';
import {
wireAccountChatEvents,
type AccountRuntimeEventReporter,
} from './account-runtime-event-wiring.js';
export interface ConcreteRuntimeFactoryOptions {
config: Config;
logger: Logger;
db: Database.Database;
getBotMode?: () => BotMode;
events?: AccountRuntimeEventReporter;
}
export function materializePreparedPostForAccount(
@@ -106,7 +111,7 @@ export function createConcreteRedditAccountRuntimeFactory(
}
async function buildRuntime(
{ config, logger: rootLogger, db, getBotMode = () => 'running' }: ConcreteRuntimeFactoryOptions,
{ config, logger: rootLogger, db, getBotMode = () => 'running', events }: ConcreteRuntimeFactoryOptions,
session: VerifiedAccountSession,
recipientDeduplicator: RecipientDeduplicator,
redditRequestCoordinator: RedditRequestCoordinator,
@@ -228,6 +233,7 @@ async function buildRuntime(
tokenRefresher, usernameResolver, chatCache,
messagesRepo, redditUsersRepo, accountAuth, conversationOutcomesRepo, conversationStateRepo,
);
if (events) wireAccountChatEvents(chatService, events, session.accountId);
await chatService.loadCache();
const mediaSyncService = new MediaSyncService(chatService, logger, accountConfig.media.syncIntervalMs, accountAuth);
const recipientAccountStatusRepo = new RedditRecipientAccountStatusRepository(db);