"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getUserTokens = getUserTokens; exports.batchResponseToResults = batchResponseToResults; exports.sendPushToUser = sendPushToUser; const pruneTokens_1 = require("./pruneTokens"); const log_1 = require("../log"); /** * One canonical FCM-token reader and one canonical sender for the whole codebase. * * Before this module, ~10 files re-implemented "read the user's tokens" and ~19 re-implemented * "send to each token, log failures, prune the dead ones". This collapses both into a single, * unit-tested unit. Callers keep what is genuinely theirs — gating (quiet hours / per-notif * toggles) and any in-app `notification_queue` write; this module only touches FCM. */ /** * Merge a user's FCM tokens from the legacy `fcmToken` field and the `fcmTokens` subcollection, * de-duplicated, order-stable (legacy first). Pass `userData` if the user doc is already loaded to * avoid a redundant read. */ async function getUserTokens(db, uid, userData) { const tokens = []; const data = userData !== null && userData !== void 0 ? userData : (await db.collection('users').doc(uid).get()).data(); const legacy = data === null || data === void 0 ? void 0 : data.fcmToken; if (typeof legacy === 'string' && legacy.length > 0) tokens.push(legacy); const snap = await db.collection('users').doc(uid).collection('fcmTokens').get(); snap.docs.forEach((d) => { var _a; const t = (_a = d.data()) === null || _a === void 0 ? void 0 : _a.token; if (typeof t === 'string' && t.length > 0 && !tokens.includes(t)) tokens.push(t); }); return tokens; } /** * Adapt a multicast `BatchResponse` to the `PromiseSettledResult[]` shape `pruneDeadTokens` * consumes (index `i` still maps to `tokens[i]`). Exported for unit testing the dead-token path. */ function batchResponseToResults(batch) { return batch.responses.map((r) => r.success ? { status: 'fulfilled', value: r.messageId } : { status: 'rejected', reason: r.error }); } /** * Send one push to every FCM token a user has, in a single batched `sendEachForMulticast` * round-trip, then prune any tokens FCM reports as permanently dead. Best-effort: never throws; * returns a per-user summary. A whole-batch failure (auth/quota, not a per-token death) is logged * and does not prune anything. */ async function sendPushToUser(db, messaging, uid, content, userData) { const tokens = await getUserTokens(db, uid, userData); if (tokens.length === 0) return { sent: 0, failed: 0, pruned: 0 }; const message = Object.assign(Object.assign({ tokens, notification: content.notification }, (content.data ? { data: content.data } : {})), (content.android ? { android: content.android } : {})); let batch; try { batch = await messaging.sendEachForMulticast(message); } catch (e) { log_1.logger.warn(`[push] sendEachForMulticast failed for uid=${uid}`, { error: String(e) }); return { sent: 0, failed: tokens.length, pruned: 0 }; } batch.responses.forEach((r, i) => { var _a; if (!r.success) { log_1.logger.warn(`[push] FCM send failed uid=${uid} token=${(0, log_1.redactToken)(tokens[i])}`, { code: (_a = r.error) === null || _a === void 0 ? void 0 : _a.code, }); } }); const pruned = await (0, pruneTokens_1.pruneDeadTokens)(db, uid, tokens, batchResponseToResults(batch)); return { sent: batch.successCount, failed: batch.failureCount, pruned }; } //# sourceMappingURL=push.js.map