81 lines
4.0 KiB
JavaScript
81 lines
4.0 KiB
JavaScript
const { createClient } = require('redis')
|
||
|
||
// یک کلاینتِ Redis مشترک برای کل اپ (presence، کش، rate-limit، session) — Redis
|
||
// اختیاریه: اگه سرورش در دسترس نباشه (چه موقع boot، چه بعداً قطع بشه)، هیچجای اپ
|
||
// نباید کرش کنه یا هنگ کنه. برای همین getRedisClient() بهجای خودِ کلاینت، فقط وقتی
|
||
// واقعاً "ready"ه اون رو برمیگردونه؛ صدازنندهها همیشه باید مسیرِ fallback
|
||
// (مستقیم Mongo/memory) رو هم داشته باشن، دقیقاً عین چیزی که توی lastOnline.js و
|
||
// middlewares/index.js پیاده شده
|
||
let client = null
|
||
let isReady = false
|
||
|
||
function buildRedisUrl () {
|
||
if (process.env.REDIS_URL) return process.env.REDIS_URL
|
||
const host = process.env.REDIS_HOST || '127.0.0.1'
|
||
const port = process.env.REDIS_PORT || '6379'
|
||
const password = process.env.REDIS_PASSWORD || ''
|
||
const db = process.env.REDIS_DB || '0'
|
||
const auth = password ? `:${encodeURIComponent(password)}@` : ''
|
||
return `redis://${auth}${host}:${port}/${db}`
|
||
}
|
||
|
||
function getClient () {
|
||
if (client) return client
|
||
|
||
client = createClient({
|
||
url: buildRedisUrl(),
|
||
socket: {
|
||
// بکآف نمایی با سقف ۱۰ ثانیه — تا ابد retry میکنه (وقتی سرورِ Redis بعداً بالا
|
||
// اومد خودش وصل میشه)، ولی هیچوقت کل اپ رو برای اتصال بلاک نمیکنه چون
|
||
// connectRedis() پایین await نمیشه
|
||
reconnectStrategy: (retries) => Math.min(retries * 200, 10000),
|
||
// بدون این، وقتی REDIS_HOST به یه آدرسِ اشتباه/غیرقابلدسترس (نه صرفاً «رد شد»،
|
||
// بلکه ساکت هیچ پاسخی نمیده) اشاره کنه، تلاشِ اتصال میتونه تا timeoutِ پیشفرضِ
|
||
// TCP سطحِ سیستمعامل (روی بعضی سیستمها ۱۰۰+ ثانیه) طول بکشه — و هر درخواستی که
|
||
// منتظرِ همون تلاشِ اتصاله (مثلاً session store) به همون اندازه کند میشه
|
||
connectTimeout: 5000
|
||
}
|
||
})
|
||
|
||
// کتابخونهی redis v4+: بدون این listener، هر خطای اتصال کل پروسهی Node رو
|
||
// کرش میکنه (unhandled 'error' event) — این مهمترین خطِ این فایله
|
||
client.on('error', (err) => {
|
||
isReady = false
|
||
console.error('[redis] connection error:', err.message)
|
||
})
|
||
client.on('ready', () => {
|
||
isReady = true
|
||
console.log('[redis] connected')
|
||
})
|
||
client.on('end', () => {
|
||
isReady = false
|
||
})
|
||
|
||
return client
|
||
}
|
||
|
||
// عمداً await نمیشه توی boot — اگه Redis در دسترس نباشه، بوتشدنِ کل سرور نباید
|
||
// معطلِ چندینبار retry بمونه؛ اتصال توی بکگراند انجام میشه و هر وقت آماده شد
|
||
// isReady=true میشه
|
||
function connectRedis () {
|
||
const c = getClient()
|
||
c.connect().catch((err) => {
|
||
console.error('[redis] initial connection failed — app continues without Redis:', err.message)
|
||
})
|
||
}
|
||
|
||
// همهجای اپ باید از همین تابع استفاده کنن، نه مستقیم از getRawClient() — چون فقط
|
||
// وقتی واقعاً متصله چیزی برمیگردونه
|
||
function getRedisClient () {
|
||
return isReady ? client : null
|
||
}
|
||
|
||
// فقط برای jaهایی مثل connect-redis که خودشون client رو نگه میدارن و باید همون
|
||
// اول (حتی قبل از وصلشدن) بهش رفرنس بدیم — چون کتابخونهی redis v4 دستورهای
|
||
// قبل از اتصال رو صف میکنه (offline queue)، نه اینکه فوراً خطا بده
|
||
function getRawClient () {
|
||
return getClient()
|
||
}
|
||
|
||
module.exports = { connectRedis, getRedisClient, getRawClient }
|