Files
notification-wakeup-service/src/services/pushService.ts
Jose Olarte III f12dd03725 chore(logging): normalize wakeup flow observability with timings and summaries
Standardize console prefixes across scheduler, push, refresh, register,
auth, and debug endpoints. Add pass-level scheduler summaries, elapsed-time
logs, and masked-token-only push failure messages while reducing per-device
noise in scheduler loops.
2026-05-21 19:18:28 +08:00

109 lines
2.8 KiB
TypeScript

import { db, type StoredRow } from "../db/fcmTokens.js";
import { errorMessage, formatElapsedMs } from "../util/formatElapsed.js";
import { maskToken } from "../util/maskToken.js";
import { messaging } from "./firebase.js";
const MS_PRODUCTION = 23 * 60 * 60 * 1000;
const MS_TEST = 10 * 60 * 1000;
export function notifyThresholdMs(testMode?: boolean): number {
return testMode === true ? MS_TEST : MS_PRODUCTION;
}
/** Epoch ms when the device may receive another push (diagnostics only). */
export function computeNextEligibleAt(row: {
lastNotifiedAt?: number;
testMode?: boolean;
}): number {
const threshold = notifyThresholdMs(row.testMode);
if (row.lastNotifiedAt === undefined) {
return Date.now();
}
return row.lastNotifiedAt + threshold;
}
function lastNotifiedMs(row: StoredRow | undefined): number | undefined {
const v = row?.lastNotifiedAt;
if (v === undefined) return undefined;
if (typeof v === "number") return Number.isNaN(v) ? undefined : v;
return undefined;
}
function stringifyData(
payload: Record<string, unknown>
): Record<string, string> {
const out: Record<string, string> = {};
for (const [k, v] of Object.entries(payload)) {
out[k] = v === undefined || v === null ? "" : String(v);
}
return out;
}
/**
* Sends an FCM data message if the token is outside the dedupe window
* (23h production, 10m test).
*/
export async function sendPushToDevice(
fcmToken: string,
payload: Record<string, unknown> = {}
): Promise<"sent" | "skipped" | "failed"> {
const suffix = maskToken(fcmToken);
const row = await db.getByFcmToken(fcmToken);
const now = Date.now();
const last = lastNotifiedMs(row);
if (
last !== undefined &&
now - last < notifyThresholdMs(row?.testMode)
) {
return "skipped";
}
const sendStarted = Date.now();
console.log("[Push] Send attempt, token suffix:", suffix);
try {
const data: Record<string, string> = {
...stringifyData(payload),
type: "WAKEUP_PING",
};
await messaging.send({
token: fcmToken,
apns: {
headers: {
"apns-push-type": "background",
"apns-priority": "5",
},
payload: {
aps: {
contentAvailable: true,
},
},
},
data,
});
const persisted = await db.getByFcmToken(fcmToken);
if (persisted !== undefined) {
await db.update(persisted.id, { lastNotifiedAt: Date.now() });
}
console.log(
"[Push] Send completed in",
formatElapsedMs(Date.now() - sendStarted) + ",",
"token suffix:",
suffix
);
return "sent";
} catch (err) {
console.error(
"[Push] Send failed in",
formatElapsedMs(Date.now() - sendStarted) + ",",
"token suffix:",
suffix + ":",
errorMessage(err)
);
return "failed";
}
}