import { randomId } from "@toruslabs/openlogin-utils" ; import { Duplex } from "readable-stream" ; import SafeEventEmitter from "./safeEventEmitter" ; import SerializableError from "./serializableError" ; export type Json = boolean | number | string | null | { [ property ] : Json ; } | Json [ ] ; export type JRPCVersion = "2.0" ; export type JRPCId = number | string | void ; export type ConsoleLike = Pick < Console , "log" | "warn" | "error" | "debug" | "info" | "trace" > ; export interface JRPCBase { jsonrpc ? : JRPCVersion ; id ? : JRPCId ; } export function serializeError ( error ) { return error . toString ( ) ; } export interface JRPCResponse < T > extends JRPCBase { result ? : T ; error ? : any ; } export const getRpcPromiseCallback = ( resolve , reject , unwrapResult = true ) => ( error , response ) : void => { if ( error || response . error ) { reject ( error || response . error ) ; } else if ( ! unwrapResult || Array . isArray ( response ) ) { resolve ( response ) ; } else { resolve ( response . result ) ; } } ; export interface JRPCRequest < T > extends JRPCBase { method : string ; params ? : T ; } export type JRPCEngineNextCallback = ( cb ? : ( done : ( error ? : Error ) => void ) => void ) => void ; export type JRPCEngineEndCallback = ( error ? : Error ) => void ; export type JRPCEngineReturnHandler = ( done : ( error ? : Error ) => void ) => void ; interface IdMapValue { req : JRPCRequest < unknown > ; res : JRPCResponse < unknown > ; next : JRPCEngineNextCallback ; end : JRPCEngineEndCallback ; } interface IdMap { [ requestId ] : IdMapValue ; } export type JRPCMiddleware < T , U > = ( req : JRPCRequest < T > , res : JRPCResponse < U > , next : JRPCEngineNextCallback , end : JRPCEngineEndCallback ) => void ; export function createErrorMiddleware ( log ) { return ( req , res , next , end ) => { try { if ( typeof req . method !== "string" || ! req . method ) { res . error = new SerializableError ( { code : - 32603 , message : "invalid method" } ) ; end ( ) ; return ; } next ( ( done ) => { const { error } = res ; if ( ! error ) { return done ( ) ; } log . error ( `OpenLogin - RPC Error: ${ error . message }` , error ) ; return done ( ) ; } ) ; } catch ( error ) { log . error ( `OpenLogin - RPC Error thrown: ${ error . message }` , error ) ; res . error = new SerializableError ( { code : - 32603 , message : error . message } ) ; end ( ) ; } } ; } export function createStreamMiddleware ( ) { const idMap = { } ; function readNoop ( ) { return false ; } const events = new SafeEventEmitter ( ) ; function processResponse ( res ) { const context = idMap [ res . id as unknown as string ] ; if ( ! context ) { throw new Error ( `StreamMiddleware - Unknown response id "${ res . id }"` ) ; } delete idMap [ res . id as unknown as string ] ; Object . assign ( context . res , res ) ; setTimeout ( context . end ) ; } function processNotification ( res ) { events . emit ( "notification" , res ) ; } function processMessage ( res , _encoding , cb ) { let err ; try { const isNotification = ! res . id ; if ( isNotification ) { processNotification ( res as unknown as JRPCRequest < unknown > ) ; } else { processResponse ( res ) ; } } catch ( _err ) { err = _err ; } cb ( err ) ; } const stream = new Duplex ( { objectMode : true , read : readNoop , write : processMessage , } ) ; const middleware = ( req , res , next , end ) => { stream . push ( req ) ; idMap [ req . id as unknown as string ] = { req , res , next , end } ; } ; return { events , middleware , stream } ; } type ScaffoldMiddlewareHandler < T , U > = JRPCMiddleware < T , U > | Json ; export function createScaffoldMiddleware ( handlers ) { return ( req , res , next , end ) => { const handler = handlers [ req . method ] ; if ( handler === undefined ) { return next ( ) ; } if ( typeof handler === "function" ) { return handler ( req , res , next , end ) ; } res . result = handler ; return end ( ) ; } ; } export function createIdRemapMiddleware ( ) { return ( req , res , next , _end ) => { const originalId = req . id ; const newId = randomId ( ) ; req . id = newId ; res . id = newId ; next ( ( done ) => { req . id = originalId ; res . id = originalId ; done ( ) ; } ) ; } ; } export function createLoggerMiddleware ( logger ) { return ( req , res , next , _ ) => { logger . debug ( "REQ" , req , "RES" , res ) ; next ( ) ; } ; } export type AsyncJRPCEngineNextCallback = ( ) => Promise < void > ; type Maybe < T > = Partial < T > | null | undefined ; export interface JRPCSuccess < T > extends JRPCBase { result : Maybe < T > ; } export interface JRPCError { code : number ; message : string ; data ? : unknown ; stack ? : string ; } export interface PendingJRPCResponse < T > extends JRPCBase { result ? : T ; error ? : Error | JRPCError ; } export interface JRPCFailure extends JRPCBase { error : JRPCError ; } export type AsyncJRPCMiddleware < T , U > = ( req : JRPCRequest < T > , res : PendingJRPCResponse < U > , next : AsyncJRPCEngineNextCallback ) => Promise < void > ; type ReturnHandlerCallback = ( error : null | Error ) => void ; export function createAsyncMiddleware < T , U > ( asyncMiddleware ) { return async ( req , res , next , end ) => { let resolveNextPromise ; const nextPromise = new Promise < void > ( ( resolve ) => { resolveNextPromise = resolve ; } ) ; let returnHandlerCallback = null ; let nextWasCalled = false ; const asyncNext = async ( ) => { nextWasCalled = true ; next ( ( runReturnHandlersCallback ) => { returnHandlerCallback = runReturnHandlersCallback ; resolveNextPromise ( ) ; } ) ; await nextPromise ; } ; try { await asyncMiddleware ( req , res , asyncNext ) ; if ( nextWasCalled ) { await nextPromise ; ( returnHandlerCallback as ReturnHandlerCallback ) ( null ) ; } else { end ( null ) ; } } catch ( error ) { if ( returnHandlerCallback ) { ( returnHandlerCallback as ReturnHandlerCallback ) ( error ) ; } else { end ( error ) ; } } } ; }