full
This commit is contained in:
100
utils/pushNotification.js
Normal file
100
utils/pushNotification.js
Normal file
@@ -0,0 +1,100 @@
|
||||
const admin = require('firebase-admin')
|
||||
// firebase-admin v12+ دیگه admin.messaging(app) رو صادر نمیکنه — باید از سابماژول جدا بگیریم
|
||||
const { getMessaging } = require('firebase-admin/messaging')
|
||||
const UserModel = require('../models/UserModel')
|
||||
|
||||
let firebaseApp = null
|
||||
let warnedMissingCredential = false
|
||||
|
||||
function getFirebaseApp () {
|
||||
if (firebaseApp) return firebaseApp
|
||||
const raw = process.env.FIREBASE_SERVICE_ACCOUNT_BASE64
|
||||
if (!raw) {
|
||||
// بدون این هشدار، ارسال پوش کاملاً بیصدا هیچکاری نمیکنه (نوتیف داخلبرنامهای و
|
||||
// سوکت درست کار میکنن، فقط پوش واقعاً نمیره) و تشخیصش خیلی سخت میشه — یکبار در طول
|
||||
// عمر پروسه لاگ میکنیم تا توی لاگ سرور واضح دیده بشه
|
||||
if (!warnedMissingCredential) {
|
||||
warnedMissingCredential = true
|
||||
console.error('[pushNotification] FIREBASE_SERVICE_ACCOUNT_BASE64 تنظیم نشده — ارسال پوش نوتیفیکیشن غیرفعاله (نوتیف داخلبرنامهای همچنان کار میکنه)')
|
||||
}
|
||||
return null
|
||||
}
|
||||
try {
|
||||
const serviceAccount = JSON.parse(Buffer.from(raw, 'base64').toString('utf8'))
|
||||
// firebase-admin v12+ آبجکت credential رو دیگه تودرتو صادر نمیکنه (admin.cert مستقیم)؛
|
||||
// نسخههای قدیمیتر هنوز admin.credential.cert دارن — هر دو رو پشتیبانی میکنیم
|
||||
const cert = admin.credential?.cert ?? admin.cert
|
||||
firebaseApp = admin.initializeApp({
|
||||
credential: cert(serviceAccount)
|
||||
})
|
||||
return firebaseApp
|
||||
} catch (error) {
|
||||
console.error('[pushNotification] پیکربندی FIREBASE_SERVICE_ACCOUNT_BASE64 نامعتبر است:', error.message)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/** ارسال پوش به یه لیست توکن مشخص — پایهی مسیرهای دستهجمعی (فالوورها، پیامهای همگانی پنل) */
|
||||
async function sendPushToTokens (tokens, { title, description, type, data, image } = {}) {
|
||||
const app = getFirebaseApp()
|
||||
const cleanTokens = (tokens || []).filter(Boolean)
|
||||
if (!app || cleanTokens.length === 0) return { successCount: 0, invalidTokens: [] }
|
||||
|
||||
const message = {
|
||||
// notification.image: عکس/آواتار نمایش دادهشده توی خودِ نوتیف (اندروید: بزرگآیکون/
|
||||
// big-picture، iOS: پیوست) — عین رفتار واتساپ/اینستاگرام برای نوتیف پیام
|
||||
notification: { title, body: description, ...(image ? { image } : {}) },
|
||||
data: Object.fromEntries(
|
||||
Object.entries({ type, ...data }).map(([k, v]) => [k, String(v ?? '')])
|
||||
),
|
||||
tokens: cleanTokens
|
||||
}
|
||||
|
||||
const invalidTokens = []
|
||||
let successCount = 0
|
||||
|
||||
// FCM حداکثر ۵۰۰ توکن در هر multicast قبول میکنه
|
||||
for (let i = 0; i < cleanTokens.length; i += 500) {
|
||||
const chunk = cleanTokens.slice(i, i + 500)
|
||||
try {
|
||||
const messaging = admin.messaging ? admin.messaging(app) : getMessaging(app)
|
||||
const res = await messaging.sendEachForMulticast({ ...message, tokens: chunk })
|
||||
successCount += res.successCount
|
||||
res.responses.forEach((r, idx) => {
|
||||
if (!r.success && r.error?.code === 'messaging/registration-token-not-registered') {
|
||||
invalidTokens.push(chunk[idx])
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('[pushNotification] خطا در ارسال دستهای:', error.message)
|
||||
}
|
||||
}
|
||||
|
||||
return { successCount, invalidTokens }
|
||||
}
|
||||
|
||||
/** ارسال پوش به یک کاربر مشخص — توکنهاش رو از دیتابیس میخونه و توکنهای نامعتبر رو پاک میکنه */
|
||||
async function sendPushToUser (userId, { title, description, type, data, image } = {}) {
|
||||
try {
|
||||
const user = await UserModel.findById(userId).select('fcm_tokens fcm_web_tokens').lean()
|
||||
const tokens = [...(user?.fcm_tokens || []), ...(user?.fcm_web_tokens || [])]
|
||||
if (tokens.length === 0) return
|
||||
|
||||
const { invalidTokens } = await sendPushToTokens(tokens, { title, description, type, data, image })
|
||||
if (invalidTokens.length > 0) {
|
||||
await UserModel.updateOne(
|
||||
{ _id: userId },
|
||||
{
|
||||
$pull: {
|
||||
fcm_tokens: { $in: invalidTokens },
|
||||
fcm_web_tokens: { $in: invalidTokens }
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[pushNotification] sendPushToUser خطا:', error.message)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { sendPushToUser, sendPushToTokens, getFirebaseApp }
|
||||
Reference in New Issue
Block a user