import { Network , SubscriptionCallback , SubscriptionHandler , SubscriptionResult , SubscriptionStart , isSubscriptionHandler , isSubscriptionStart , } from '@aragon/connect-types' ; import { ConnectionContext } from '../types' ; import { ErrorInvalidConnector , ErrorInvalidApp } from '../errors' ; import App from '../entities/App' ; type AppConnectContext = { app : App ; config : object ; connector : string ; ipfs : ConnectionContext [ 'ipfs' ] ; network : Network ; orgConnector : ConnectionContext [ 'orgConnector' ] ; verbose : boolean ; } ; type AugmentedApp < T > = App & T & { _app : App ; _connectedApp : T ; } ; type ConnectorDeclaration < Config > = string | [ string , Config | undefined ] | undefined ; type CreateAppConnectorCallback < ConnectedApp , Config > = ( context : { config : Config ; } & AppConnectContext ) => ConnectedApp | Promise < ConnectedApp > ; function normalizeConnectorConfig < Config extends object > ( connector ) { if ( Array . isArray ( connector ) ) { return [ connector [ 0 ] , connector [ 1 ] ?? ( { } as Config ) ] ; } if ( typeof connector === 'string' ) { return [ connector , { } as Config ] ; } throw new ErrorInvalidConnector ( 'The connector should be passed as a string or an array.' ) ; } function isApp ( app ) { return ( typeof app === 'object' && 'name' in ( app as object ) && 'address' in ( app as object ) && 'appId' in ( app as object ) && 'version' in ( app as object ) ) ; } async function getProxiedApp < ConnectedApp extends object , Config extends object > ( app , connector , callback ) { app = ( await app ) as App ; if ( ! isApp ( app ) ) { throw new ErrorInvalidApp ( `App connector: the passed value doesn’t appear to be an App.` ) ; } const { connection } = app . organization ; const { orgConnector } = connection ; const [ connectorName , connectorConfig ] = normalizeConnectorConfig < Config > ( connector || orgConnector . name ) ; const connectedApp = await callback ( { app , config : connectorConfig , connector : connectorName , ipfs : connection . ipfs , network : orgConnector . network , orgConnector , verbose : connection . verbose , } ) ; const boundMethods = new WeakMap ( ) ; const proxiedApp = new Proxy ( connectedApp , { get : ( target , key ) => { const isAppProperty = ( connectedApp as any ) [ key as keyof ConnectedApp ] === undefined ; const instance = ( isAppProperty ? app : connectedApp ) as any ; if ( typeof instance [ key ] === 'function' && ! boundMethods . has ( instance [ key ] ) ) { instance [ key ] = instance [ key ] . bind ( instance ) ; boundMethods . set ( instance [ key ] , true ) ; } return instance [ key ] ; } , } ) as AugmentedApp < ConnectedApp > ; proxiedApp . _app = app ; proxiedApp . _connectedApp = connectedApp ; return proxiedApp ; } export function createAppConnector < ConnectedApp extends object , Config extends object > ( callback ) { return function connect ( appOrSubscribe , connector ? ) : Promise < AugmentedApp < ConnectedApp > > | SubscriptionStart < AugmentedApp < ConnectedApp > > { if ( isSubscriptionStart ( appOrSubscribe ) ) { return ( subscriptionCb ) : SubscriptionHandler => { let cancelled = false ; const subscriptionHandler = appOrSubscribe ( ( error , app ? ) => { if ( error !== null ) { subscriptionCb ( error ) ; return ; } getProxiedApp < ConnectedApp , Config > ( app , connector , callback ) . then ( ( app ) => { if ( cancelled ) { return ; } subscriptionCb ( null , app ) ; } ) . catch ( ( err ) => { if ( ! cancelled ) { subscriptionCb ( err ) ; } } ) ; } ) ; return { unsubscribe ( ) { cancelled = true ; subscriptionHandler . unsubscribe ( ) ; } , } ; } ; } if ( isSubscriptionHandler ( appOrSubscribe ) ) { throw new ErrorInvalidApp ( `App connector: the value doesn’t appear to be an App or an app subscription.` ) ; } return getProxiedApp < ConnectedApp , Config > ( appOrSubscribe , connector , callback ) ; } ; }