import redis from "redis" ; import { RelayJsonRpc } from "@walletconnect/relay-api" ; import { Logger } from "pino" ; import { generateChildLogger } from "@walletconnect/logger" ; import { safeJsonParse , safeJsonStringify } from "@walletconnect/safe-json" ; import config from "./config" ; import { sha256 } from "./utils" ; import { REDIS_CONTEXT , SIX_HOURS } from "./constants" ; import { Notification , LegacySocketMessage } from "./types" ; export class RedisService { public client : any = redis . createClient ( config . redis ) ; public context = REDIS_CONTEXT ; constructor ( public logger ) { this . logger = generateChildLogger ( logger , this . context ) ; this . initialize ( ) ; } public setMessage ( params ) { return new Promise ( ( resolve , reject ) => { const { topic , message , ttl } = params ; this . logger . debug ( `Setting Message` ) ; this . logger . trace ( { type : "method" , method : "setMessage" , params } ) ; const key = `message:${ topic }` ; const hash = sha256 ( message ) ; const val = `${ hash }:${ message }` ; this . client . sadd ( key , val , ( err ) => { if ( err ) reject ( err ) ; this . client . expire ( key , ttl , ( err ) => { if ( err ) reject ( err ) ; resolve ( ) ; } ) ; } ) ; } ) ; } public async getMessage ( topic , hash ) { this . logger . debug ( `Getting Message` ) ; this . logger . trace ( { type : "method" , method : "getMessage" , topic } ) ; return ( await this . sscan ( `message:${ topic }` , "MATCH" , `${ hash }:*` ) ) [ 0 ] ?. split ( ":" ) [ 1 ] ; } public getMessages ( topic ) { return new Promise ( ( resolve , reject ) => { this . logger . debug ( `Getting Message` ) ; this . logger . trace ( { type : "method" , method : "getMessage" , topic } ) ; this . client . smembers ( `message:${ topic }` , ( err , res ) => { if ( err ) reject ( err ) ; const messages = [ ] ; if ( typeof res !== "undefined" && res . length ) { res . forEach ( ( m ) => { if ( m != null ) messages . push ( m . split ( ":" ) [ 1 ] ) ; } ) ; } resolve ( messages ) ; } ) ; } ) ; } public deleteMessage ( topic , hash ) { return new Promise ( async ( resolve , reject ) => { this . logger . debug ( `Deleting Message` ) ; this . logger . trace ( { type : "method" , method : "deleteMessage" , topic } ) ; const res = await this . sscan ( `message:${ topic }` , "MATCH" , `${ hash }:*` ) ; if ( typeof res !== "undefined" && res . length ) { this . client . srem ( `message:${ topic }` , res [ 0 ] , ( err ) => { if ( err ) reject ( err ) ; resolve ( ) ; } ) ; } } ) ; } public setLegacyCached ( message ) { return new Promise ( ( resolve , reject ) => { this . logger . debug ( `Setting Legacy Cached` ) ; this . logger . trace ( { type : "method" , method : "setLegacyCached" , message } ) ; this . client . lpush ( [ `legacy:${ message . topic }` , safeJsonStringify ( message ) ] , ( err , res ) => { if ( err ) reject ( err ) ; this . client . expire ( [ `legacy:${ message . topic }` , SIX_HOURS ] , ( err , res ) => { if ( err ) reject ( err ) ; resolve ( ) ; } ) ; } ) ; } ) ; } public getLegacyCached ( topic ) { return new Promise ( ( resolve , reject ) => { this . client . lrange ( `legacy:${ topic }` , 0 , - 1 , ( err , res ) => { if ( err ) reject ( err ) ; const messages = [ ] ; if ( typeof res !== "undefined" && res . length ) { res . forEach ( ( data ) => { const message = safeJsonParse ( data ) ; messages . push ( message ) ; } ) ; } this . client . del ( `legacy:${ topic }` ) ; this . logger . debug ( `Getting Legacy Published` ) ; this . logger . trace ( { type : "method" , method : "getLegacyCached" , topic , messages } ) ; resolve ( messages ) ; } ) ; } ) ; } public setNotification ( notification ) { return new Promise ( ( resolve , reject ) => { this . logger . debug ( `Setting Notification` ) ; this . logger . trace ( { type : "method" , method : "setNotification" , notification } ) ; this . client . lpush ( [ `notification:${ notification . topic }` , safeJsonStringify ( notification ) ] , ( err ) => { if ( err ) reject ( err ) ; resolve ( ) ; } ) ; } ) ; } public getNotification ( topic ) { return new Promise ( ( resolve , reject ) => { this . client . lrange ( [ `notification:${ topic }` , 0 , - 1 ] , ( err , res ) => { if ( err ) reject ( err ) ; const notifications = [ ] ; if ( typeof res !== "undefined" && res . length ) { res . forEach ( ( item ) => { const notification = safeJsonParse ( item ) ; notifications . push ( notification ) ; } ) ; } this . logger . debug ( `Getting Notification` ) ; this . logger . trace ( { type : "method" , method : "getNotification" , topic , notifications } ) ; resolve ( notifications ) ; } ) ; } ) ; } public setPendingRequest ( topic , id , message ) { return new Promise ( ( resolve , reject ) => { const key = `pending:${ id }` ; const hash = sha256 ( message ) ; const val = `${ topic }:${ hash }` ; this . logger . debug ( `Setting Pending Request` ) ; this . logger . trace ( { type : "method" , method : "setPendingRequest" , topic , id , message } ) ; this . client . set ( key , val , ( err ) => { if ( err ) reject ( err ) ; this . client . expire ( key , config . REDIS_MAX_TTL , ( err ) => { if ( err ) reject ( err ) ; resolve ( ) ; } ) ; } ) ; } ) ; } public getPendingRequest ( id ) { return new Promise ( ( resolve , reject ) => { this . client . get ( `pending:${ id }` , ( err , data ) => { if ( err ) reject ( err ) ; this . logger . debug ( `Getting Pending Request` ) ; this . logger . trace ( { type : "method" , method : "getPendingRequest" , id , data } ) ; resolve ( data ) ; } ) ; } ) ; } public deletePendingRequest ( id ) { return new Promise ( ( resolve , reject ) => { this . logger . debug ( `Deleting Pending Request` ) ; this . logger . trace ( { type : "method" , method : "deletePendingRequest" , id } ) ; this . client . del ( `pending:${ id }` , ( err ) => { if ( err ) reject ( err ) ; resolve ( ) ; } ) ; } ) ; } private initialize ( ) { this . logger . trace ( `Initialized` ) ; } private sscanAsync ( key , match , pattern , cursor ) { return new Promise ( ( resolve , reject ) => { this . client . sscan ( key , cursor , match , pattern , ( err , result ) => { if ( err ) reject ( err ) ; resolve ( result ) ; } ) ; } ) ; } private async sscan ( key , match = "" , pattern = "" , cursor = "0" ) { const messages = [ ] ; const [ nextCursor , values ] = await this . sscanAsync ( key , match , pattern , cursor ) ; if ( typeof values !== "undefined" && values . length ) { values . forEach ( ( message ) => { if ( ! message ) return ; messages . push ( message ) ; } ) ; } if ( nextCursor == "0" ) { return messages ; } return [ ... messages , ... ( await this . sscan ( key , match , pattern , nextCursor ) ) ] ; } }