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.
109 lines
2.8 KiB
TypeScript
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";
|
|
}
|
|
}
|