import { Transport } from './Transport' ; import { Handler , callHandlers } from './Handler' ; import { DEFAULT_PARAM } from './Constants' ; const signalNotConnected = ( ) => { throw new Error ( 'Slot not connected' ) ; } ; interface SlotConfig { noBuffer ? : boolean ; autoReconnect ? : boolean ; } export const defaultSlotConfig = { noBuffer : false , autoReconnect : true } ; const getNotConnectedSlot = ( config ) : Slot < any , any > => Object . assign ( ( ) => signalNotConnected ( ) , { config , lazy : ( ) => signalNotConnected , on : ( ) => signalNotConnected , slotName : 'Not connected' } ) ; export type LazyCallback = ( param : string ) => void ; export type Unsubscribe = ( ) => void ; const LOCAL_TRANSPORT = 'LOCAL_TRANSPORT' ; type TransportHandlers = { [ param ] : Handler < any , any > [ ] ; } ; type Handlers = { [ handlerKey ] : TransportHandlers ; } ; const getParamHandlers = ( param , handlers ) : Handler < any , any > [ ] => Object . keys ( handlers ) . reduce ( ( paramHandlers , transportKey ) => { return paramHandlers . concat ( handlers [ transportKey ] [ param ] || [ ] ) ; } , [ ] as Handler < any , any > [ ] ) ; const findAllUsedParams = ( handlers ) : string [ ] => Object . keys ( handlers ) . reduce ( ( params , transportKey ) => { const transportHandlers = handlers [ transportKey ] ; const registeredParams = Object . keys ( transportHandlers ) . filter ( param => ( transportHandlers [ param ] || [ ] ) . length > 0 ) ; const paramsMaybeDuplicate = [ ... params , ... registeredParams ] ; const paramsUniq = [ ... new Set ( paramsMaybeDuplicate ) ] ; return paramsUniq ; } , [ ] as string [ ] ) ; /**
 * Represents an event shared by two modules.
 *
 * A module can trigger the event by calling the slot. This will return a promise,
 * which will be resolved with the response sent by the other module if applicable.
 *
 * The slot can also be subscribed to, by using the `on` property.
 */ export interface Slot < RequestData = null , ResponseData = void > { ( param , requestData ) : Promise < ResponseData > ; ( requestData ) : Promise < ResponseData > ; on ( param , handler ) : Unsubscribe ; on ( handler ) : Unsubscribe ; lazy ( connect , disconnect ) : Unsubscribe ; config ? : SlotConfig ; slotName : string ; } /**
 * A shorthand function used to declare slots in event bus object literals
 * It returns a fake slot, that will throw if triggered or subscribed to.
 * Slots need to be connected in order to be functional.
 */ export function slot < RequestData = void , ResponseData = void > ( config = defaultSlotConfig ) { return getNotConnectedSlot ( config ) ; } export function connectSlot < T = void , T2 = void > ( slotName , transports , config = { } ) { const handlers = transports . reduce ( ( acc , _t , ix ) => ( { ... acc , [ ix ] : { } } ) , { [ LOCAL_TRANSPORT ] : { } } ) ; interface HandlerConnected { registered : Promise < void > ; onRegister : ( ) => void ; } interface RemoteHandlersConnected { [ transportKey ] : { [ param ] : HandlerConnected ; } ; } const remoteHandlersConnected = transports . reduce ( ( acc , _t , transportKey ) => ( { ... acc , [ transportKey ] : { } } ) , { } ) ; const awaitHandlerRegistration = ( transportKey , param ) => { let onHandlerRegistered = ( ) => { } ; const remoteHandlerRegistered = new Promise < void > ( resolve => onHandlerRegistered = resolve ) ; remoteHandlersConnected [ transportKey ] [ param ] = { registered : remoteHandlerRegistered , onRegister : onHandlerRegistered } ; } ; const lazyConnectCallbacks = [ ] ; const lazyDisonnectCallbacks = [ ] ; const callLazyConnectCallbacks = ( param ) => lazyConnectCallbacks . forEach ( c => c ( param ) ) ; const callLazyDisonnectCallbacks = ( param ) => lazyDisonnectCallbacks . forEach ( c => c ( param ) ) ; transports . forEach ( ( transport , transportKey ) => { const remoteHandlerRegistered = ( param = DEFAULT_PARAM , handler ) => { const paramHandlers = handlers [ transportKey ] [ param ] || [ ] ; handlers [ transportKey ] [ param ] = paramHandlers . concat ( handler ) ; if ( getParamHandlers ( param , handlers ) . length === 1 ) callLazyConnectCallbacks ( param ) ; if ( ! remoteHandlersConnected [ transportKey ] [ param ] ) { awaitHandlerRegistration ( String ( transportKey ) , param ) ; } remoteHandlersConnected [ transportKey ] [ param ] . onRegister ( ) ; } ; const remoteHandlerUnregistered = ( param = DEFAULT_PARAM , handler ) => { const paramHandlers = handlers [ transportKey ] [ param ] || [ ] ; const handlerIndex = paramHandlers . indexOf ( handler ) ; if ( handlerIndex > - 1 ) handlers [ transportKey ] [ param ] . splice ( handlerIndex , 1 ) ; if ( getParamHandlers ( param , handlers ) . length === 0 ) callLazyDisonnectCallbacks ( param ) ; awaitHandlerRegistration ( String ( transportKey ) , param ) ; } ; transport . addRemoteHandlerRegistrationCallback ( slotName , remoteHandlerRegistered ) ; transport . addRemoteHandlerUnregistrationCallback ( slotName , remoteHandlerUnregistered ) ; } ) ; function trigger ( data ) ; function trigger ( param , data ) ; function trigger ( firstArg , secondArg ? ) { const paramUsed = arguments . length === 2 ; const data = paramUsed ? secondArg : firstArg ; const param = paramUsed ? firstArg : DEFAULT_PARAM ; const callHandlersWithParameters = ( ) => { const allParamHandlers = getParamHandlers ( param , handlers ) ; return callHandlers ( data , allParamHandlers ) ; } ; if ( transports . length === 0 ) { return callHandlersWithParameters ( ) ; } const transportConnectionPromises = [ ] ; if ( config . autoReconnect ) { transports . forEach ( ( _t ) => { transportConnectionPromises . push ( _t . autoReconnect ( ) ) ; } ) ; } if ( config . noBuffer ) { return Promise . all ( transportConnectionPromises ) . then ( ( ) => { return callHandlersWithParameters ( ) ; } ) ; } else { transports . forEach ( ( _t , transportKey ) => { if ( ! remoteHandlersConnected [ transportKey ] [ param ] ) { awaitHandlerRegistration ( String ( transportKey ) , param ) ; } } ) ; const transportPromises = transports . reduce ( ( acc , _t , transportKey ) => [ ... acc , remoteHandlersConnected [ transportKey ] [ param ] . registered ] , [ ] ) ; return Promise . all ( transportPromises ) . then ( ( ) => { return callHandlersWithParameters ( ) ; } ) ; } } function lazy ( firstClientConnectCallback , lastClientDisconnectCallback ) { lazyConnectCallbacks . push ( firstClientConnectCallback ) ; lazyDisonnectCallbacks . push ( lastClientDisconnectCallback ) ; findAllUsedParams ( handlers ) . forEach ( firstClientConnectCallback ) ; return ( ) => { findAllUsedParams ( handlers ) . forEach ( lastClientDisconnectCallback ) ; const connectIx = lazyConnectCallbacks . indexOf ( firstClientConnectCallback ) ; if ( connectIx > - 1 ) lazyConnectCallbacks . splice ( connectIx , 1 ) ; const disconnectIx = lazyDisonnectCallbacks . indexOf ( lastClientDisconnectCallback ) ; if ( disconnectIx > - 1 ) lazyDisonnectCallbacks . splice ( disconnectIx , 1 ) ; } ; } function on ( handler ) ; function on ( param , handler ) ; function on ( paramOrHandler , handlerIfParam ? ) { let param = "" ; let handler = ( ) => new Promise < void > ( r => r ( ) ) ; if ( typeof paramOrHandler === 'string' ) { param = paramOrHandler ; handler = handlerIfParam || handler ; } else { param = DEFAULT_PARAM ; handler = paramOrHandler ; } transports . forEach ( t => t . registerHandler ( slotName , param , handler ) ) ; handlers [ LOCAL_TRANSPORT ] [ param ] = ( handlers [ LOCAL_TRANSPORT ] [ param ] || [ ] ) . concat ( handler ) ; const paramHandlers = getParamHandlers ( param , handlers ) ; if ( paramHandlers . length === 1 ) callLazyConnectCallbacks ( param ) ; return ( ) => { transports . forEach ( t => t . unregisterHandler ( slotName , param , handler ) ) ; const localParamHandlers = handlers [ LOCAL_TRANSPORT ] [ param ] || [ ] ; const ix = localParamHandlers . indexOf ( handler ) ; if ( ix !== - 1 ) handlers [ LOCAL_TRANSPORT ] [ param ] . splice ( ix , 1 ) ; const paramHandlers = getParamHandlers ( param , handlers ) ; if ( paramHandlers . length === 0 ) callLazyDisonnectCallbacks ( param ) ; } ; } return Object . assign ( trigger , { on , lazy , config , slotName } ) ; }