Skip to content

Commit 3950e9b

Browse files
Clean up push notification logging and debug endpoints
1 parent 2dce1e2 commit 3950e9b

5 files changed

Lines changed: 18 additions & 149 deletions

File tree

backend/src/index.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -209,24 +209,19 @@ if (ENV.VAPID.PUBLIC_KEY && ENV.VAPID.PRIVATE_KEY) {
209209
if (!ENV.VAPID.SUBJECT) {
210210
logger.warn('VAPID_SUBJECT is not set — push notifications require a mailto: subject (e.g. mailto:you@example.com)')
211211
} else if (!ENV.VAPID.SUBJECT.startsWith('mailto:')) {
212-
logger.warn(`VAPID_SUBJECT="${ENV.VAPID.SUBJECT}" does not use mailto: format — iOS/Safari push notifications will fail. Use mailto:you@example.com`)
212+
logger.warn(`VAPID_SUBJECT="${ENV.VAPID.SUBJECT}" does not use mailto: format — iOS/Safari push notifications will fail`)
213213
}
214214

215-
const vapidSubject = ENV.VAPID.SUBJECT || 'mailto:push@localhost'
216-
217215
notificationService.configureVapid({
218216
publicKey: ENV.VAPID.PUBLIC_KEY,
219217
privateKey: ENV.VAPID.PRIVATE_KEY,
220-
subject: vapidSubject,
218+
subject: ENV.VAPID.SUBJECT || 'mailto:push@localhost',
221219
})
222220
sseAggregator.onEvent((directory, event) => {
223221
notificationService.handleSSEEvent(directory, event).catch((err) => {
224222
logger.error('Push notification dispatch error:', err)
225223
})
226224
})
227-
logger.info('Push notifications enabled')
228-
} else {
229-
logger.info('Push notifications disabled (no VAPID keys configured)')
230225
}
231226

232227
app.route('/api/auth', createAuthRoutes(auth))

backend/src/routes/notifications.ts

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import { Hono } from "hono";
22
import { z } from "zod";
33
import { PushSubscriptionRequestSchema } from "@opencode-manager/shared/schemas";
44
import type { NotificationService } from "../services/notification";
5-
import { logger } from "../utils/logger";
6-
import { sseAggregator } from "../services/sse-aggregator";
75

86
export function createNotificationRoutes(
97
notificationService: NotificationService
@@ -32,8 +30,6 @@ export function createNotificationRoutes(
3230

3331
const { endpoint, keys, deviceName } = parsed.data;
3432

35-
logger.info(`[push:subscribe] user="${userId}" device="${deviceName ?? "unknown"}" endpoint="${endpoint}"`);
36-
3733
const subscription = notificationService.saveSubscription(
3834
userId,
3935
endpoint,
@@ -94,39 +90,9 @@ export function createNotificationRoutes(
9490
);
9591
}
9692

97-
const results = await notificationService.sendTestNotification(userId);
98-
99-
return c.json({ success: true, devicesNotified: subscriptions.length, diagnostics: results });
100-
});
93+
await notificationService.sendTestNotification(userId);
10194

102-
app.get("/debug", (c) => {
103-
const userId = c.req.query('userId') || 'default'
104-
const subscriptions = notificationService.getSubscriptions(userId);
105-
const clients = sseAggregator.getClientVisibilityDetails();
106-
const hasVisibleClients = sseAggregator.hasVisibleClients();
107-
const vapidDetails = notificationService.getVapidDetails();
108-
109-
return c.json({
110-
vapidConfigured: notificationService.isConfigured(),
111-
vapidPublicKeyPrefix: vapidDetails?.publicKey.slice(0, 20) + "...",
112-
vapidSubject: vapidDetails?.subject,
113-
vapidPublicKeyLength: vapidDetails?.publicKey.length,
114-
vapidPrivateKeyLength: vapidDetails?.privateKeyLength,
115-
serverTime: new Date().toISOString(),
116-
serverTimestamp: Date.now(),
117-
hasVisibleClients,
118-
sseClients: clients,
119-
subscriptions: subscriptions.map(sub => ({
120-
id: sub.id,
121-
endpoint: sub.endpoint,
122-
endpointDomain: new URL(sub.endpoint).hostname,
123-
deviceName: sub.deviceName,
124-
createdAt: new Date(sub.createdAt).toISOString(),
125-
lastUsedAt: sub.lastUsedAt ? new Date(sub.lastUsedAt).toISOString() : null,
126-
p256dhLength: sub.p256dh.length,
127-
authLength: sub.auth.length,
128-
})),
129-
});
95+
return c.json({ success: true, devicesNotified: subscriptions.length });
13096
});
13197

13298
return app;

backend/src/services/notification.ts

Lines changed: 10 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -81,22 +81,12 @@ export class NotificationService {
8181
configureVapid(config: VapidConfig): void {
8282
this.vapidConfig = config;
8383
webpush.setVapidDetails(config.subject, config.publicKey, config.privateKey);
84-
logger.info(`VAPID configured — subject="${config.subject}" publicKeyLength=${config.publicKey.length} privateKeyLength=${config.privateKey.length}`);
8584
}
8685

8786
getVapidPublicKey(): string | null {
8887
return this.vapidConfig?.publicKey ?? null;
8988
}
9089

91-
getVapidDetails(): { publicKey: string; privateKeyLength: number; subject: string } | null {
92-
if (!this.vapidConfig) return null;
93-
return {
94-
publicKey: this.vapidConfig.publicKey,
95-
privateKeyLength: this.vapidConfig.privateKey.length,
96-
subject: this.vapidConfig.subject,
97-
};
98-
}
99-
10090
isConfigured(): boolean {
10191
return this.vapidConfig !== null;
10292
}
@@ -136,8 +126,6 @@ export class NotificationService {
136126
last_used_at: number | null;
137127
};
138128

139-
logger.info(`Saved push subscription for user ${userId}`);
140-
141129
return {
142130
id: row.id,
143131
userId: row.user_id,
@@ -212,39 +200,21 @@ export class NotificationService {
212200
event: SSEEvent
213201
): Promise<void> {
214202
const config = EVENT_CONFIG[event.type];
215-
if (!config) {
216-
logger.debug(`[push] Ignoring SSE event type="${event.type}" (no notification config)`);
217-
return;
218-
}
203+
if (!config) return;
219204

220-
logger.info(`[push] Processing SSE event type="${event.type}" for directory="${_directory}"`);
205+
if (this.hasActiveSSEClients()) return;
221206

222-
if (this.hasActiveSSEClients()) {
223-
logger.info(`[push] Skipping push — active visible SSE clients detected`);
224-
return;
225-
}
226-
227-
if (!this.isConfigured()) {
228-
logger.warn(`[push] Skipping push — VAPID not configured`);
229-
return;
230-
}
207+
if (!this.isConfigured()) return;
231208

232209
const userIds = this.getAllUserIds();
233-
logger.info(`[push] Found ${userIds.length} user(s) with push subscriptions`);
234210

235211
for (const userId of userIds) {
236212
const settings = this.settingsService.getSettings(userId);
237213
const notifPrefs =
238214
settings.preferences.notifications ?? DEFAULT_NOTIFICATION_PREFERENCES;
239215

240-
if (!notifPrefs.enabled) {
241-
logger.info(`[push] Skipping user="${userId}" — notifications disabled`);
242-
continue;
243-
}
244-
if (!notifPrefs.events[config.preferencesKey]) {
245-
logger.info(`[push] Skipping user="${userId}" — event "${config.preferencesKey}" disabled`);
246-
continue;
247-
}
216+
if (!notifPrefs.enabled) continue;
217+
if (!notifPrefs.events[config.preferencesKey]) continue;
248218

249219
const sessionId = event.properties.sessionID as string | undefined;
250220

@@ -271,86 +241,56 @@ export class NotificationService {
271241
},
272242
};
273243

274-
logger.info(`[push] Sending push to user="${userId}" title="${payload.title}"`);
275244
await this.sendToUser(userId, payload);
276245
}
277246
}
278247

279-
async sendTestNotification(userId: string): Promise<{ sent: number; failed: number; results: Array<{ endpoint: string; status: string; statusCode?: number; error?: string }> }> {
280-
const subscriptions = this.getSubscriptions(userId);
281-
logger.info(`[push:test] Sending test notification to user="${userId}" (${subscriptions.length} subscription(s))`);
282-
for (const sub of subscriptions) {
283-
logger.info(`[push:test] Subscription endpoint: ${sub.endpoint}`);
284-
}
285-
const results = await this.sendToUserWithResults(userId, {
248+
async sendTestNotification(userId: string): Promise<void> {
249+
await this.sendToUser(userId, {
286250
title: "Test Notification",
287251
body: "Push notifications are working correctly",
288252
tag: "test",
289253
data: { eventType: "test", url: "/" },
290254
});
291-
logger.info(`[push:test] Results: ${JSON.stringify(results)}`);
292-
return results;
293255
}
294256

295257
private async sendToUser(
296258
userId: string,
297259
payload: PushNotificationPayload
298260
): Promise<void> {
299-
await this.sendToUserWithResults(userId, payload);
300-
}
301-
302-
private async sendToUserWithResults(
303-
userId: string,
304-
payload: PushNotificationPayload
305-
): Promise<{ sent: number; failed: number; results: Array<{ endpoint: string; status: string; statusCode?: number; error?: string }> }> {
306261
const subscriptions = this.getSubscriptions(userId);
307262
const expiredEndpoints: string[] = [];
308-
const results: Array<{ endpoint: string; status: string; statusCode?: number; error?: string }> = [];
309-
310-
logger.info(`[push] Delivering to ${subscriptions.length} subscription(s) for user="${userId}"`);
311263

312264
await Promise.allSettled(
313265
subscriptions.map(async (sub) => {
314-
const endpointPreview = sub.endpoint.slice(0, 80);
315266
try {
316-
const response = await webpush.sendNotification(
267+
await webpush.sendNotification(
317268
{
318269
endpoint: sub.endpoint,
319270
keys: { p256dh: sub.p256dh, auth: sub.auth },
320271
},
321272
JSON.stringify(payload)
322273
);
323274

324-
logger.info(`[push] Success for ${endpointPreview}... — statusCode=${response.statusCode} headers=${JSON.stringify(response.headers)}`);
325-
results.push({ endpoint: endpointPreview, status: "success", statusCode: response.statusCode });
326-
327275
this.db
328276
.prepare(
329277
"UPDATE push_subscriptions SET last_used_at = ? WHERE id = ?"
330278
)
331279
.run(Date.now(), sub.id);
332280
} catch (error) {
333281
const statusCode = (error as { statusCode?: number }).statusCode;
334-
const body = (error as { body?: string }).body;
335-
const message = (error as Error).message;
336-
337-
logger.error(`[push] Failed for ${endpointPreview}... — statusCode=${statusCode} body="${body}" message="${message}"`);
338-
results.push({ endpoint: endpointPreview, status: "failed", statusCode, error: body ?? message });
339282

340283
if (statusCode === 404 || statusCode === 410) {
341284
expiredEndpoints.push(sub.endpoint);
285+
} else {
286+
logger.error(`Push delivery failed for ${sub.endpoint.slice(0, 50)}:`, error);
342287
}
343288
}
344289
})
345290
);
346291

347292
for (const endpoint of expiredEndpoints) {
348293
this.removeSubscription(endpoint);
349-
logger.info(`[push] Removed expired push subscription: ${endpoint.slice(0, 50)}...`);
350294
}
351-
352-
const sent = results.filter(r => r.status === "success").length;
353-
const failed = results.filter(r => r.status === "failed").length;
354-
return { sent, failed, results };
355295
}
356296
}

backend/src/services/sse-aggregator.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -370,14 +370,6 @@ class SSEAggregator {
370370
return false
371371
}
372372

373-
getClientVisibilityDetails(): Array<{ id: string; visible: boolean; directories: string[] }> {
374-
return Array.from(this.clients.values()).map(client => ({
375-
id: client.id,
376-
visible: client.visible,
377-
directories: Array.from(client.directories),
378-
}))
379-
}
380-
381373
getActiveDirectories(): string[] {
382374
return Array.from(this.connections.keys())
383375
}

frontend/src/sw.ts

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,36 +22,24 @@ interface PushNotificationData {
2222
};
2323
}
2424

25-
console.warn("[sw] Service worker loaded");
26-
2725
self.addEventListener("activate", (event) => {
28-
console.warn("[sw] Service worker activated");
2926
event.waitUntil(self.clients.claim());
3027
});
3128

3229
self.addEventListener("install", () => {
33-
console.warn("[sw] Service worker installed");
3430
self.skipWaiting();
3531
});
3632

3733
self.addEventListener("push", (event: PushEvent) => {
38-
console.warn("[sw:push] Push event received", { hasData: !!event.data });
39-
40-
if (!event.data) {
41-
console.warn("[sw:push] No data in push event, ignoring");
42-
return;
43-
}
34+
if (!event.data) return;
4435

4536
let payload: PushNotificationData;
4637
try {
4738
payload = event.data.json() as PushNotificationData;
48-
console.warn("[sw:push] Parsed payload", JSON.stringify(payload));
49-
} catch (parseError) {
50-
const rawText = event.data.text();
51-
console.warn("[sw:push] Failed to parse JSON, using raw text", rawText, parseError);
39+
} catch {
5240
payload = {
5341
title: "OpenCode Manager",
54-
body: rawText,
42+
body: event.data.text(),
5543
data: { eventType: "unknown" },
5644
};
5745
}
@@ -65,22 +53,10 @@ self.addEventListener("push", (event: PushEvent) => {
6553
requireInteraction: isHighPriority(payload.data?.eventType),
6654
};
6755

68-
console.warn("[sw:push] Showing notification", { title: payload.title, options: JSON.stringify(options) });
69-
70-
const notificationPromise = self.registration
71-
.showNotification(payload.title, options)
72-
.then(() => {
73-
console.warn("[sw:push] showNotification resolved successfully");
74-
})
75-
.catch((err: unknown) => {
76-
console.error("[sw:push] showNotification FAILED", err);
77-
});
78-
79-
event.waitUntil(notificationPromise);
56+
event.waitUntil(self.registration.showNotification(payload.title, options));
8057
});
8158

8259
self.addEventListener("notificationclick", (event: NotificationEvent) => {
83-
console.warn("[sw:click] Notification clicked", event.notification.data);
8460
event.notification.close();
8561

8662
const url = (event.notification.data?.url as string) ?? "/";

0 commit comments

Comments
 (0)