import env from "../environment" import * as Redis from "ioredis" const SLOT_REFRESH_MS = 2000 const CONNECT_TIMEOUT_MS = 10000 export const SEPARATOR = "-" /** * These Redis databases help us to segment up a Redis keyspace by prepending the * specified database name onto the cache key. This means that a single real Redis database * can be split up a bit; allowing us to use scans on small databases to find some particular * keys within. * If writing a very large volume of keys is expected (say 10K+) then it is better to keep these out * of the default keyspace and use a separate one - the SelectableDatabase can be used for this. */ export enum Databases { PW_RESETS = "pwReset", VERIFICATIONS = "verification", INVITATIONS = "invitation", DEV_LOCKS = "devLocks", DEBOUNCE = "debounce", SESSIONS = "session", USER_CACHE = "users", FLAGS = "flags", APP_METADATA = "appMetadata", QUERY_VARS = "queryVars", LICENSES = "license", GENERIC_CACHE = "data_cache", WRITE_THROUGH = "writeThrough", LOCKS = "locks", SOCKET_IO = "socket_io", } /** * These define the numeric Redis databases that can be access with the SELECT command - * (https://redis.io/commands/select/). By default a Redis server/cluster will have 16 selectable * databases, increasing this count increases the amount of CPU/memory required to run the server. * Ideally new Redis keyspaces should be used sparingly, only when absolutely necessary for performance * to be maintained. Generally a keyspace can grow to be very large is scans are not needed or desired, * but if you need to walk through all values in a database periodically then a separate selectable * keyspace should be used. */ export enum SelectableDatabase { DEFAULT = 0, SOCKET_IO = 1, RATE_LIMITING = 2, UNUSED_2 = 3, UNUSED_3 = 4, UNUSED_4 = 5, UNUSED_5 = 6, UNUSED_6 = 7, UNUSED_7 = 8, UNUSED_8 = 9, UNUSED_9 = 10, UNUSED_10 = 11, UNUSED_11 = 12, UNUSED_12 = 13, UNUSED_13 = 14, UNUSED_14 = 15, } export function getRedisConnectionDetails() { let password = env.REDIS_PASSWORD let url: string[] | string = env.REDIS_URL.split("//") // get rid of the protocol url = url.length > 1 ? url[1] : url[0] // check for a password etc url = url.split("@") if (url.length > 1) { // get the password password = url[0].split(":")[1] url = url[1] } else { url = url[0] } const [host, port] = url.split(":") return { host, password, port: parseInt(port), } } export function getRedisOptions() { const { host, password, port } = getRedisConnectionDetails() let redisOpts: Redis.RedisOptions = { connectTimeout: CONNECT_TIMEOUT_MS, port: port, host, password, } let opts: Redis.ClusterOptions | Redis.RedisOptions = redisOpts if (env.REDIS_CLUSTERED) { opts = { connectTimeout: CONNECT_TIMEOUT_MS, redisOptions: { ...redisOpts, tls: {}, }, slotsRefreshTimeout: SLOT_REFRESH_MS, dnsLookup: (address: string, callback: any) => callback(null, address), } as Redis.ClusterOptions } return opts } export function addDbPrefix(db: string, key: string) { if (key.includes(db)) { return key } return `${db}${SEPARATOR}${key}` } export function removeDbPrefix(key: string) { let parts = key.split(SEPARATOR) if (parts.length >= 2) { parts.shift() return parts.join(SEPARATOR) } else { // return the only part return parts[0] } }