import { EventEmitter } from 'events' ; import logdown from 'logdown' ; import { AccessTokenData , AccessTokenStore , AuthAPI , Context , Cookie , InvalidTokenError , LoginData , MissingCookieError , RegisterData , } from './auth/' ; import { AccountAPI } from './account/AccountAPI' ; import { AssetAPI } from './asset/' ; import { Backend } from './env/' ; import { BroadcastAPI } from './broadcast/' ; import { ClientAPI , ClientType } from './client/' ; import { ConnectionAPI } from './connection/' ; import { ConversationAPI } from './conversation/' ; import { CookieStore } from './auth/CookieStore' ; import { GiphyAPI } from './giphy/' ; import { BackendError , HttpClient } from './http/' ; import { NotificationAPI } from './notification/' ; import { ObfuscationUtil } from './obfuscation/' ; import { OnConnect , WebSocketClient } from './tcp/' ; import { SelfAPI } from './self/' ; import { ServiceProviderAPI } from './serviceProvider' ; import { ServicesAPI } from './services' ; import { FeatureAPI , IdentityProviderAPI , LegalHoldAPI , MemberAPI , PaymentAPI , BillingAPI , ServiceAPI , TeamAPI , TeamConversationAPI , TeamInvitationAPI , } from './team/' ; import { UserAPI } from './user/' ; import type { Config } from './Config' ; import { TeamSearchAPI } from './team/search' ; import { parseAccessToken } from './auth/parseAccessToken' ; import { ScimAPI } from './team/scim/ScimAPI' ; const { version } = require ( '../package.json' ) ; enum TOPIC { ACCESS_TOKEN_REFRESH = 'APIClient.TOPIC.ACCESS_TOKEN_REFRESH' , COOKIE_REFRESH = 'APIClient.TOPIC.COOKIE_REFRESH' , /** Event being sent when logout is done. */ ON_LOGOUT = 'APIClient.TOPIC.ON_LOGOUT' } const defaultConfig = { urls : Backend . PRODUCTION , } ; export interface APIClient { on ( event , listener ) : this ; on ( event , listener ) : this ; on ( event , listener ) : this ; } export class APIClient extends EventEmitter { private readonly logger : logdown . Logger ; public account : { api : AccountAPI ; } ; public asset : { api : AssetAPI ; } ; public auth : { api : AuthAPI ; } ; public broadcast : { api : BroadcastAPI ; } ; public client : { api : ClientAPI ; } ; public connection : { api : ConnectionAPI ; } ; public conversation : { api : ConversationAPI ; } ; public giphy : { api : GiphyAPI ; } ; public notification : { api : NotificationAPI ; } ; public self : { api : SelfAPI ; } ; public services : { api : ServicesAPI ; } ; public serviceProvider : { api : ServiceProviderAPI ; } ; public teams : { conversation : { api : TeamConversationAPI ; } ; feature : { api : FeatureAPI ; } ; identityProvider : { api : IdentityProviderAPI ; } ; invitation : { api : TeamInvitationAPI ; } ; legalhold : { api : LegalHoldAPI ; } ; member : { api : MemberAPI ; } ; payment : { api : PaymentAPI ; } ; billing : { api : BillingAPI ; } ; scim : { api : ScimAPI ; } ; search : { api : TeamSearchAPI ; } ; service : { api : ServiceAPI ; } ; team : { api : TeamAPI ; } ; } ; public user : { api : UserAPI ; } ; private readonly accessTokenStore : AccessTokenStore ; public context ? : Context ; public transport : { http : HttpClient ; ws : WebSocketClient ; } ; public config : Config ; public static BACKEND = Backend ; public static readonly TOPIC = TOPIC ; public static VERSION = version ; constructor ( config ? ) { super ( ) ; this . config = { ... defaultConfig , ... config } ; this . accessTokenStore = new AccessTokenStore ( ) ; this . accessTokenStore . on ( AccessTokenStore . TOPIC . ACCESS_TOKEN_REFRESH , ( accessToken ) => this . emit ( APIClient . TOPIC . ACCESS_TOKEN_REFRESH , accessToken ) ) ; CookieStore . emitter . on ( CookieStore . TOPIC . COOKIE_REFRESH , ( cookie ? ) => this . emit ( APIClient . TOPIC . COOKIE_REFRESH , cookie ) ) ; this . logger = logdown ( '@wireapp/api-client/Client' , { logger : console , markdown : false , } ) ; const httpClient = new HttpClient ( this . config , this . accessTokenStore ) ; const webSocket = new WebSocketClient ( this . config . urls . ws , httpClient ) ; const onInvalidCredentials = async ( error ) => { try { await this . logout ( { skipLogoutRequest : true } ) ; } finally { this . emit ( APIClient . TOPIC . ON_LOGOUT , error ) ; } } ; webSocket . on ( WebSocketClient . TOPIC . ON_INVALID_TOKEN , onInvalidCredentials ) ; httpClient . on ( HttpClient . TOPIC . ON_INVALID_TOKEN , onInvalidCredentials ) ; this . transport = { http : httpClient , ws : webSocket , } ; this . account = { api : new AccountAPI ( this . transport . http ) , } ; this . asset = { api : new AssetAPI ( this . transport . http ) , } ; this . auth = { api : new AuthAPI ( this . transport . http ) , } ; this . services = { api : new ServicesAPI ( this . transport . http ) , } ; this . broadcast = { api : new BroadcastAPI ( this . transport . http ) , } ; this . client = { api : new ClientAPI ( this . transport . http ) , } ; this . connection = { api : new ConnectionAPI ( this . transport . http ) , } ; this . conversation = { api : new ConversationAPI ( this . transport . http ) , } ; this . giphy = { api : new GiphyAPI ( this . transport . http ) , } ; this . notification = { api : new NotificationAPI ( this . transport . http ) , } ; this . self = { api : new SelfAPI ( this . transport . http ) , } ; this . serviceProvider = { api : new ServiceProviderAPI ( this . transport . http ) , } ; this . teams = { conversation : { api : new TeamConversationAPI ( this . transport . http ) , } , feature : { api : new FeatureAPI ( this . transport . http ) , } , identityProvider : { api : new IdentityProviderAPI ( this . transport . http ) , } , invitation : { api : new TeamInvitationAPI ( this . transport . http ) , } , legalhold : { api : new LegalHoldAPI ( this . transport . http ) , } , member : { api : new MemberAPI ( this . transport . http ) , } , payment : { api : new PaymentAPI ( this . transport . http ) , } , billing : { api : new BillingAPI ( this . transport . http ) , } , scim : { api : new ScimAPI ( this . transport . http ) , } , search : { api : new TeamSearchAPI ( this . transport . http ) , } , service : { api : new ServiceAPI ( this . transport . http ) , } , team : { api : new TeamAPI ( this . transport . http ) , } , } ; this . user = { api : new UserAPI ( this . transport . http ) , } ; } public async init ( clientType = ClientType . NONE , cookie ? ) { CookieStore . setCookie ( cookie ) ; const initialAccessToken = await this . transport . http . refreshAccessToken ( ) ; const context = await this . createContext ( initialAccessToken . user , clientType ) ; await this . accessTokenStore . updateToken ( initialAccessToken ) ; return context ; } public async login ( loginData ) { if ( this . context ) { await this . logout ( ) ; } const accessToken = await this . auth . api . postLogin ( loginData ) ; this . logger . info ( `Saved initial access token. It will expire in "${ accessToken . expires_in }" seconds.` , ObfuscationUtil . obfuscateAccessToken ( accessToken ) ) ; await this . accessTokenStore . updateToken ( accessToken ) ; return this . createContext ( accessToken . user , loginData . clientType ) ; } public async loginWithToken ( accessTokenString , clientType = ClientType . NONE ) { const { userId } = parseAccessToken ( accessTokenString ) ; const accessTokenData = { access_token : accessTokenString , expires_in : 1 , token_type : 'Bearer' , user : userId , } ; await this . accessTokenStore . updateToken ( accessTokenData ) ; return this . createContext ( userId , clientType ) ; } public async register ( userAccount , clientType = ClientType . PERMANENT ) { if ( this . context ) { await this . logout ( ) ; } const user = await this . auth . api . postRegister ( userAccount ) ; await this . createContext ( user . id , clientType ) ; return this . init ( clientType , CookieStore . getCookie ( ) ) ; } public async logout ( options = { skipLogoutRequest : false } ) { try { this . disconnect ( 'Closed by client logout' ) ; if ( ! options . skipLogoutRequest ) { await this . auth . api . postLogout ( ) ; } } catch ( error ) { this . logger . warn ( error ) ; } CookieStore . deleteCookie ( ) ; await this . accessTokenStore . delete ( ) ; delete this . context ; } public connect ( onConnect ? ) { return this . transport . ws . connect ( this . context ?. clientId , onConnect ) ; } private async createContext ( userId , clientType ) { let selfDomain = undefined ; try { const self = await this . self . api . getSelf ( ) ; selfDomain = self . qualified_id ?. domain ; this . logger . info ( `Got self domain "${ selfDomain }"` ) ; } catch ( error ) { this . logger . warn ( 'Could not get self user:' , ( error as BackendError ) . message ) ; } this . context = this . context ? { ... this . context , clientType , domain : selfDomain } : { clientType , userId , domain : selfDomain } ; return this . context ; } public disconnect ( reason ? ) { this . transport . ws . disconnect ( reason ) ; } public get clientId ( ) : string | undefined { return this . context ?. clientId || undefined ; } public get userId ( ) : string | undefined { return this . context ?. userId || undefined ; } public get domain ( ) : string | undefined { return this . context ?. domain || undefined ; } /** Should be used in cases where the user ID is MANDATORY. */ public get validatedUserId ( ) : string { if ( this . userId ) { return this . userId ; } throw new Error ( 'No valid user ID.' ) ; } /** Should be used in cases where the client ID is MANDATORY. */ public get validatedClientId ( ) : string { if ( this . clientId ) { return this . clientId ; } throw new Error ( 'No valid client ID.' ) ; } }