94 lines
2.8 KiB
JavaScript
94 lines
2.8 KiB
JavaScript
const crypto = require("crypto")
|
|
const axios = require("axios")
|
|
const { getRedisClient } = require("./client")
|
|
|
|
const IMAGE_KEY_PREFIX = "cache:deal_create:image:"
|
|
const DEFAULT_TTL_SECONDS = 5 * 60
|
|
const DEFAULT_MAX_IMAGE_BYTES = 8 * 1024 * 1024
|
|
|
|
function getMaxImageBytes() {
|
|
const raw = Number(process.env.LINK_PREVIEW_MAX_IMAGE_BYTES)
|
|
if (!Number.isFinite(raw) || raw < 256 * 1024) return DEFAULT_MAX_IMAGE_BYTES
|
|
return Math.floor(raw)
|
|
}
|
|
|
|
function createRedisClient() {
|
|
return getRedisClient()
|
|
}
|
|
|
|
function normalizeUrl(rawUrl) {
|
|
if (!rawUrl || typeof rawUrl !== "string") return null
|
|
try {
|
|
const url = new URL(rawUrl)
|
|
if (!["http:", "https:"].includes(url.protocol)) return null
|
|
const hostname = url.hostname.toLowerCase()
|
|
const pathname = url.pathname || "/"
|
|
const search = url.search || ""
|
|
return `${url.protocol}//${hostname}${pathname}${search}`
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function buildKey(normalizedUrl) {
|
|
const hash = crypto.createHash("sha1").update(normalizedUrl).digest("hex")
|
|
return `${IMAGE_KEY_PREFIX}${hash}`
|
|
}
|
|
|
|
async function cacheImageFromUrl(rawUrl, { ttlSeconds = DEFAULT_TTL_SECONDS } = {}) {
|
|
const normalized = normalizeUrl(rawUrl)
|
|
if (!normalized) return null
|
|
const maxImageBytes = getMaxImageBytes()
|
|
|
|
const key = buildKey(normalized)
|
|
const redis = createRedisClient()
|
|
|
|
try {
|
|
const cached = await redis.get(key)
|
|
if (cached) return { key: key.replace(IMAGE_KEY_PREFIX, ""), cached: true }
|
|
|
|
const response = await axios.get(normalized, {
|
|
responseType: "arraybuffer",
|
|
timeout: 15000,
|
|
maxContentLength: maxImageBytes,
|
|
maxBodyLength: maxImageBytes,
|
|
validateStatus: (status) => status >= 200 && status < 300,
|
|
})
|
|
|
|
const contentType = String(response.headers?.["content-type"] || "")
|
|
if (!contentType.startsWith("image/")) return null
|
|
|
|
const buffer = Buffer.from(response.data || [])
|
|
if (!buffer.length || buffer.length > maxImageBytes) return null
|
|
|
|
const payload = JSON.stringify({
|
|
ct: contentType,
|
|
b64: buffer.toString("base64"),
|
|
})
|
|
await redis.set(key, payload, "EX", Number(ttlSeconds) || DEFAULT_TTL_SECONDS)
|
|
return { key: key.replace(IMAGE_KEY_PREFIX, ""), cached: false }
|
|
} catch {
|
|
return null
|
|
} finally {}
|
|
}
|
|
|
|
async function getCachedImageByKey(hashKey) {
|
|
if (!hashKey || typeof hashKey !== "string") return null
|
|
const redis = createRedisClient()
|
|
const key = `${IMAGE_KEY_PREFIX}${hashKey}`
|
|
try {
|
|
const raw = await redis.get(key)
|
|
if (!raw) return null
|
|
const parsed = JSON.parse(raw)
|
|
if (!parsed?.ct || !parsed?.b64) return null
|
|
return { contentType: parsed.ct, buffer: Buffer.from(parsed.b64, "base64") }
|
|
} catch {
|
|
return null
|
|
} finally {}
|
|
}
|
|
|
|
module.exports = {
|
|
cacheImageFromUrl,
|
|
getCachedImageByKey,
|
|
}
|