This commit is contained in:
payacom
2026-09-24 21:03:27 +03:30
parent 7f0ddfde70
commit 806471241e
43 changed files with 2166 additions and 144 deletions

View File

@@ -34,18 +34,33 @@ function getFirebaseApp () {
}
}
/** ارسال پوش به یه لیست توکن مشخص — پایه‌ی مسیرهای دسته‌جمعی (فالوورها، پیام‌های همگانی پنل) */
async function sendPushToTokens (tokens, { title, description, type, data, image } = {}) {
/**
* ارسال پوش به یه لیست توکن مشخص — پایه‌ی مسیرهای دسته‌جمعی (فالوورها، پیام‌های همگانی پنل)
* dataOnly: اگه true باشه، هیچ بلاک notification فرستاده نمی‌شه — فقط data (رشته‌ای‌شده).
* دلیلش: react-native-firebase وقتی پیام یه notification داشته باشه، خودِ سیستم‌عامل
* (نه کدِ اپ) پیش‌فرض نمایشش می‌ده و setBackgroundMessageHandler برای اون پیام اصلاً صدا
* زده نمی‌شه — یعنی هیچ‌وقت نمی‌شه آیکونِ بزرگ (آواتار) رو ازش کنترل کرد. فقط برای
* توکن‌های موبایل (sendPushToUser) استفاده می‌شه؛ وب دست‌نخورده می‌مونه (already working)
*/
async function sendPushToTokens (tokens, { title, description, type, data, image, dataOnly } = {}) {
const app = getFirebaseApp()
const cleanTokens = (tokens || []).filter(Boolean)
if (!app || cleanTokens.length === 0) return { successCount: 0, invalidTokens: [] }
const dataPayload = { type, ...data }
if (dataOnly) {
// notifee (index.js پس‌زمینه/foreground) از همین فیلدها نوتیف غنی می‌سازه
dataPayload.title = title || ''
dataPayload.description = description || ''
if (image) dataPayload.image = image
}
const message = {
// notification.image: عکس/آواتار نمایش داده‌شده توی خودِ نوتیف (اندروید: بزرگ‌آیکون/
// big-picture، iOS: پیوست) — عین رفتار واتساپ/اینستاگرام برای نوتیف پیام
notification: { title, body: description, ...(image ? { image } : {}) },
// notification.image: عکس/آواتار نمایش داده‌شده توی خودِ نوتیف (وب/آی‌اواس، و اندرویدِ
// dataOnly:false) — عین رفتار واتساپ/اینستاگرام برای نوتیف پیام
...(dataOnly ? {} : { notification: { title, body: description, ...(image ? { image } : {}) } }),
data: Object.fromEntries(
Object.entries({ type, ...data }).map(([k, v]) => [k, String(v ?? '')])
Object.entries(dataPayload).map(([k, v]) => [k, String(v ?? '')])
),
tokens: cleanTokens
}
@@ -73,21 +88,34 @@ async function sendPushToTokens (tokens, { title, description, type, data, image
return { successCount, invalidTokens }
}
/** ارسال پوش به یک کاربر مشخص — توکن‌هاش رو از دیتابیس می‌خونه و توکن‌های نامعتبر رو پاک می‌کنه */
/**
* ارسال پوش به یک کاربر مشخص — توکن‌هاش رو از دیتابیس می‌خونه و توکن‌های نامعتبر رو پاک می‌کنه.
* موبایل و وب جدا ارسال می‌شن: موبایل dataOnly (نوتیفِ غنی رو notifee توی index.js/App
* می‌سازه)، وب عین قبل با بلاک notification (رفتارِ فعلاً درستِ وب دست‌نخورده می‌مونه)
*/
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 mobileTokens = user?.fcm_tokens || []
const webTokens = user?.fcm_web_tokens || []
if (mobileTokens.length === 0 && webTokens.length === 0) return
const { invalidTokens } = await sendPushToTokens(tokens, { title, description, type, data, image })
if (invalidTokens.length > 0) {
const [mobileResult, webResult] = await Promise.all([
mobileTokens.length
? sendPushToTokens(mobileTokens, { title, description, type, data, image, dataOnly: true })
: Promise.resolve({ invalidTokens: [] }),
webTokens.length
? sendPushToTokens(webTokens, { title, description, type, data, image })
: Promise.resolve({ invalidTokens: [] })
])
if (mobileResult.invalidTokens.length > 0 || webResult.invalidTokens.length > 0) {
await UserModel.updateOne(
{ _id: userId },
{
$pull: {
fcm_tokens: { $in: invalidTokens },
fcm_web_tokens: { $in: invalidTokens }
fcm_tokens: { $in: mobileResult.invalidTokens },
fcm_web_tokens: { $in: webResult.invalidTokens }
}
}
)