@ -6,12 +6,12 @@ const url = require('url');
const dotenv = require ( 'dotenv' ) ;
const dotenv = require ( 'dotenv' ) ;
const express = require ( 'express' ) ;
const express = require ( 'express' ) ;
const Redis = require ( 'ioredis' ) ;
const { JSDOM } = require ( 'jsdom' ) ;
const { JSDOM } = require ( 'jsdom' ) ;
const log = require ( 'npmlog' ) ;
const log = require ( 'npmlog' ) ;
const pg = require ( 'pg' ) ;
const pg = require ( 'pg' ) ;
const dbUrlToConfig = require ( 'pg-connection-string' ) . parse ;
const dbUrlToConfig = require ( 'pg-connection-string' ) . parse ;
const metrics = require ( 'prom-client' ) ;
const metrics = require ( 'prom-client' ) ;
const redis = require ( 'redis' ) ;
const uuid = require ( 'uuid' ) ;
const uuid = require ( 'uuid' ) ;
const WebSocket = require ( 'ws' ) ;
const WebSocket = require ( 'ws' ) ;
@ -24,30 +24,12 @@ dotenv.config({
log . level = process . env . LOG _LEVEL || 'verbose' ;
log . level = process . env . LOG _LEVEL || 'verbose' ;
/ * *
/ * *
* @ param { Object . < string , any > } defaultConfig
* @ param { Object . < string , any > } config
* @ param { string } redisUrl
* /
* /
const redisUrlToClient = async ( defaultConfig , redisUrl ) => {
const createRedisClient = async ( config ) => {
const config = defaultConfig ;
const { redisParams , redisUrl } = config ;
const client = new Redis ( redisUrl , redisParams ) ;
let client ;
if ( ! redisUrl ) {
client = redis . createClient ( config ) ;
} else if ( redisUrl . startsWith ( 'unix://' ) ) {
client = redis . createClient ( Object . assign ( config , {
socket : {
path : redisUrl . slice ( 7 ) ,
} ,
} ) ) ;
} else {
client = redis . createClient ( Object . assign ( config , {
url : redisUrl ,
} ) ) ;
}
client . on ( 'error' , ( err ) => log . error ( 'Redis Client Error!' , err ) ) ;
client . on ( 'error' , ( err ) => log . error ( 'Redis Client Error!' , err ) ) ;
await client . connect ( ) ;
return client ;
return client ;
} ;
} ;
@ -147,23 +129,22 @@ const pgConfigFromEnv = (env) => {
* @ returns { Object . < string , any > } configuration for the Redis connection
* @ returns { Object . < string , any > } configuration for the Redis connection
* /
* /
const redisConfigFromEnv = ( env ) => {
const redisConfigFromEnv = ( env ) => {
const redisNamespace = env . REDIS _NAMESPACE || null ;
// ioredis *can* transparently add prefixes for us, but it doesn't *in some cases*,
// which means we can't use it. But this is something that should be looked into.
const redisPrefix = env . REDIS _NAMESPACE ? ` ${ env . REDIS _NAMESPACE } : ` : '' ;
const redisParams = {
const redisParams = {
socket : {
host : env . REDIS _HOST || '127.0.0.1' ,
host : env . REDIS _HOST || '127.0.0.1' ,
port : env . REDIS _PORT || 6379 ,
port : env . REDIS _PORT || 6379 ,
db : env . REDIS _DB || 0 ,
} ,
database : env . REDIS _DB || 0 ,
password : env . REDIS _PASSWORD || undefined ,
password : env . REDIS _PASSWORD || undefined ,
} ;
} ;
if ( redisNamespace ) {
// redisParams.path takes precedence over host and port.
redisParams . namespace = redisNamespace ;
if ( env . REDIS _URL && env . REDIS _URL . startsWith ( 'unix://' ) ) {
redisParams . path = env . REDIS _URL . slice ( 7 ) ;
}
}
const redisPrefix = redisNamespace ? ` ${ redisNamespace } : ` : '' ;
return {
return {
redisParams ,
redisParams ,
redisPrefix ,
redisPrefix ,
@ -179,15 +160,15 @@ const startServer = async () => {
const pgPool = new pg . Pool ( pgConfigFromEnv ( process . env ) ) ;
const pgPool = new pg . Pool ( pgConfigFromEnv ( process . env ) ) ;
const server = http . createServer ( app ) ;
const server = http . createServer ( app ) ;
const { redisParams , redisUrl , redisPrefix } = redisConfigFromEnv ( process . env ) ;
/ * *
/ * *
* @ type { Object . < string , Array . < function ( Object < string , any > ) : void >> }
* @ type { Object . < string , Array . < function ( Object < string , any > ) : void >> }
* /
* /
const subs = { } ;
const subs = { } ;
const redisSubscribeClient = await redisUrlToClient ( redisParams , redisUrl ) ;
const redisConfig = redisConfigFromEnv ( process . env ) ;
const redisClient = await redisUrlToClient ( redisParams , redisUrl ) ;
const redisSubscribeClient = await createRedisClient ( redisConfig ) ;
const redisClient = await createRedisClient ( redisConfig ) ;
const { redisPrefix } = redisConfig ;
// Collect metrics from Node.js
// Collect metrics from Node.js
metrics . collectDefaultMetrics ( ) ;
metrics . collectDefaultMetrics ( ) ;
@ -277,13 +258,13 @@ const startServer = async () => {
} ;
} ;
/ * *
/ * *
* @ param { string } message
* @ param { string } channel
* @ param { string } channel
* @ param { string } message
* /
* /
const onRedisMessage = ( message, channel ) => {
const onRedisMessage = ( channel, message ) => {
const callbacks = subs [ channel ] ;
const callbacks = subs [ channel ] ;
log . silly ( ` New message on channel ${ channel} ` ) ;
log . silly ( ` New message on channel ${ redisPrefix} ${ channel} ` ) ;
if ( ! callbacks ) {
if ( ! callbacks ) {
return ;
return ;
@ -294,6 +275,7 @@ const startServer = async () => {
callbacks . forEach ( callback => callback ( json ) ) ;
callbacks . forEach ( callback => callback ( json ) ) ;
} ;
} ;
redisSubscribeClient . on ( "message" , onRedisMessage ) ;
/ * *
/ * *
* @ callback SubscriptionListener
* @ callback SubscriptionListener
@ -312,8 +294,14 @@ const startServer = async () => {
if ( subs [ channel ] . length === 0 ) {
if ( subs [ channel ] . length === 0 ) {
log . verbose ( ` Subscribe ${ channel } ` ) ;
log . verbose ( ` Subscribe ${ channel } ` ) ;
redisSubscribeClient . subscribe ( channel , onRedisMessage ) ;
redisSubscribeClient . subscribe ( channel , ( err , count ) => {
redisSubscriptions . inc ( ) ;
if ( err ) {
log . error ( ` Error subscribing to ${ channel } ` ) ;
}
else {
redisSubscriptions . set ( count ) ;
}
} ) ;
}
}
subs [ channel ] . push ( callback ) ;
subs [ channel ] . push ( callback ) ;
@ -334,8 +322,14 @@ const startServer = async () => {
if ( subs [ channel ] . length === 0 ) {
if ( subs [ channel ] . length === 0 ) {
log . verbose ( ` Unsubscribe ${ channel } ` ) ;
log . verbose ( ` Unsubscribe ${ channel } ` ) ;
redisSubscribeClient . unsubscribe ( channel ) ;
redisSubscribeClient . unsubscribe ( channel , ( err , count ) => {
redisSubscriptions . dec ( ) ;
if ( err ) {
log . error ( ` Error unsubscribing to ${ channel } ` ) ;
}
else {
redisSubscriptions . set ( count ) ;
}
} ) ;
delete subs [ channel ] ;
delete subs [ channel ] ;
}
}
} ;
} ;