import * as fs from "fs" ; import { ProxyType } from "./bst-proxy" ; import { LoggingHelper } from "../core/logging-helper" ; import { LambdaConfig } from "./lambda-config" ; import { SourceNameGenerator } from "../external/source-name-generator" ; import { SpokesClient } from "../external/spokes" ; import { BstMessages } from "../external/messages" ; const Logger = "CONFIG" ; const BSTDirectoryName = ".bst" ; /**
 * Handles setting up initial configuration and ongoing state of BST
 */ export class BSTConfig { public configuration : any = null ; public process : any = null ; /**
     * Loads the configuration
     * Done all synchronously as this is done first thing at startup and everything waits on it
     *    createSource: call source api to create a soruce
     */ public static async load ( createSource ? ) { createSource = typeof createSource === "undefined" ? true : createSource ; await BSTConfig . bootstrapIfNeeded ( createSource ) ; let config = undefined ; if ( fs . existsSync ( BSTConfig . configPath ( ) ) ) { let data = fs . readFileSync ( BSTConfig . configPath ( ) ) ; config = JSON . parse ( data . toString ( ) ) ; } let bstConfig = new BSTConfig ( ) ; bstConfig . loadFromJSON ( config ) ; return bstConfig ; } public getMessages ( ) { const bstMessages = this . configuration . bstMessages ; if ( ! bstMessages ) return undefined ; const messages = bstMessages . messages ; if ( ! messages ) return undefined ; const result = { } ; if ( messages . customMessages && messages . customMessages . length ) { const randomMessage = Math . floor ( Math . random ( ) * messages . customMessages . length ) ; result . customMessage = messages . customMessages [ randomMessage ] ; } if ( messages . tips && messages . tips . length ) { const randomTip = Math . floor ( Math . random ( ) * messages . tips . length ) ; result . tip = messages . tips [ randomTip ] ; } return result ; } public save ( ) { BSTConfig . saveConfig ( this . configuration ) ; } public static getBstVersion ( ) { const packageInfo = require ( "../../package.json" ) ; return packageInfo . version ; } public sourceID ( ) { return this . configuration . sourceID ; } public secretKey ( ) { return this . configuration . secretKey ; } public applicationID ( ) { return this . configuration . applicationID ; } public updateApplicationID ( applicationID ) { this . configuration . applicationID = applicationID ; this . commit ( ) ; } public updateVirtualDeviceToken ( virtualDeviceToken ) { this . configuration . virtualDeviceToken = virtualDeviceToken ; this . commit ( ) ; } public updateMessages ( messages ) { this . configuration . messages = messages ; this . commit ( ) ; } public deleteSession ( ) { if ( fs . existsSync ( BSTConfig . sessionPath ( ) ) ) { fs . unlinkSync ( BSTConfig . sessionPath ( ) ) ; } } public saveSession ( session ) { const sessionBuffer = Buffer . from ( JSON . stringify ( session , null , 4 ) + "\n" ) ; fs . writeFileSync ( BSTConfig . sessionPath ( ) , sessionBuffer ) ; } public loadSession ( ) { if ( ! fs . existsSync ( BSTConfig . sessionPath ( ) ) ) { return null ; } const data = fs . readFileSync ( BSTConfig . sessionPath ( ) ) ; return JSON . parse ( data . toString ( ) ) ; } public virtualDeviceToken ( ) { return this . configuration . virtualDeviceToken ; } public commit ( ) { let configBuffer = Buffer . from ( JSON . stringify ( this . configuration , null , 4 ) + "\n" ) ; fs . writeFileSync ( BSTConfig . configPath ( ) , configBuffer ) ; } private loadFromJSON ( config ) { this . configuration = config ; } private static configDirectory ( ) { return getUserHome ( ) + "/" + BSTDirectoryName ; } private static configPath ( ) { return BSTConfig . configDirectory ( ) + "/config" ; } private static sessionPath ( ) { return BSTConfig . configDirectory ( ) + "/session" ; } /**
     * Creates a new configuration file if one does not exist
     *   createSource: call source api to create a source
     */ private static async bootstrapIfNeeded ( createSource ? ) { createSource = typeof createSource === "undefined" ? true : createSource ; let directory = BSTConfig . configDirectory ( ) ; if ( ! fs . existsSync ( directory ) ) { fs . mkdirSync ( directory ) ; } if ( ! fs . existsSync ( BSTConfig . configPath ( ) ) ) { LoggingHelper . info ( Logger , "No configuration. Creating one: " + BSTConfig . configPath ( ) ) ; let configJSON = { } ; if ( createSource ) { configJSON = await BSTConfig . createConfig ( ) ; } configJSON . bstMessages = await this . fetchMessages ( ) ; BSTConfig . saveConfig ( configJSON ) ; } else { let data = fs . readFileSync ( BSTConfig . configPath ( ) ) ; let config = JSON . parse ( data . toString ( ) ) ; if ( createSource && ( ! config . sourceID || ! config . version ) ) { await BSTConfig . updateConfig ( config ) ; } if ( config . bstMessages && config . bstMessages . fetched ) { const difference = ( new Date ( ) ) . getTime ( ) - config . bstMessages . fetched ; if ( difference > 1000 * 3600 * 24 ) { config . bstMessages = await this . fetchMessages ( ) ; } } else { config . bstMessages = await this . fetchMessages ( ) ; } BSTConfig . saveConfig ( config ) ; } } private static async updateConfig ( config ) { const previousKey = config . nodeID || config . secretKey ; const generatedConfig = await BSTConfig . createConfig ( previousKey , config . sourceID ) ; config . sourceID = generatedConfig . sourceID ; config . secretKey = generatedConfig . secretKey ; config . version = generatedConfig . version ; delete config . nodeID ; } private static saveConfig ( config ) { let configBuffer = Buffer . from ( JSON . stringify ( config , null , 4 ) + "\n" ) ; fs . writeFileSync ( BSTConfig . configPath ( ) , configBuffer ) ; } private static async createConfig ( nodeID ? , sourceID ? ) { const lambdaConfig = LambdaConfig . defaultConfig ( ) . lambdaDeploy ; const pipeInfo = await BSTConfig . createExternalResources ( nodeID , sourceID ) ; const bstMessages = await this . fetchMessages ( ) ; return { "sourceID" : pipeInfo . endPoint . name , "secretKey" : pipeInfo . uuid , "lambdaDeploy" : lambdaConfig , "version" : this . getBstVersion ( ) , "bstMessages" : bstMessages , } ; } private static async createSpokesPipe ( id , secretKey ) { const spokesClient = new SpokesClient ( id , secretKey ) ; const isUUIDUnassigned = await spokesClient . verifyUUIDisNew ( ) ; if ( isUUIDUnassigned ) { return spokesClient . createPipe ( ) ; } return { endPoint : { name : id , } , uuid : secretKey , } ; } private static async createSource ( secretKey ? , sourceID ? ) { const sourceNameGenerator = new SourceNameGenerator ( ) ; let id ; let key ; if ( ! secretKey ) { const generatedKey = await sourceNameGenerator . callService ( ) ; id = generatedKey . id ; key = generatedKey . secretKey ; } if ( secretKey && ! sourceID ) { const generatedKey = await sourceNameGenerator . callService ( ) ; id = generatedKey . id ; key = secretKey ; } if ( sourceID && secretKey ) { id = sourceID ; key = secretKey ; } try { await sourceNameGenerator . createDashboardSource ( id , key ) ; } catch ( e ) { if ( e . statusCode !== 403 ) { throw ( e ) ; } } return { id , key , } ; } private static async createExternalResources ( secretKey ? , sourceID ? ) { const sourceData = await this . createSource ( secretKey , sourceID ) ; const pipe = await this . createSpokesPipe ( sourceData . id , sourceData . key ) ; return pipe ; } private static async fetchMessages ( ) { try { const bstMessages = new BstMessages ( ) ; const messages = await bstMessages . callService ( ) ; return { messages , fetched : new Date ( ) . getTime ( ) , } ; } catch ( error ) { if ( process . env . DISPLAY_INTERNAL_ERROR ) { console . log ( "error" , error ) ; } } return undefined ; } } export class BSTProcess { public port : number ; public proxyType : ProxyType ; public pid : number ; public constructor ( ) { } /**
     * Returns the running process, if any
     */ public static running ( ) { let process = null ; if ( fs . existsSync ( BSTProcess . processPath ( ) ) ) { let data = fs . readFileSync ( BSTProcess . processPath ( ) ) ; let json = JSON . parse ( data . toString ( ) ) ; if ( BSTProcess . isRunning ( json . pid ) ) { process = new BSTProcess ( ) ; process . loadJSON ( json ) ; } } return process ; } private static isRunning ( pid ) { try { process . kill ( pid , 0 ) ; return true ; } catch ( e ) { return e . code === "EPERM" ; } } public static processPath ( ) { return getUserHome ( ) + "/" + BSTDirectoryName + "/process" ; } public static run ( port , proxyType , pid ) { let process = new BSTProcess ( ) ; process . port = port ; process . proxyType = proxyType ; process . pid = pid ; let json = process . json ( ) ; let jsonBuffer = Buffer . from ( JSON . stringify ( json , undefined , 4 ) + "\n" ) ; fs . writeFileSync ( BSTProcess . processPath ( ) , jsonBuffer ) ; return process ; } public kill ( ) { try { process . kill ( this . pid , "SIGKILL" ) ; return true ; } catch ( e ) { console . error ( "Error killing process[" + this . pid + "] Message: " + e . message ) ; return false ; } } private loadJSON ( json ) { this . port = json . port ; this . proxyType = json . proxyType ; this . pid = json . pid ; } private json ( ) { return { "port" : this . port , "type" : this . proxyType , "pid" : this . pid } ; } } function getUserHome ( ) { return process . env [ ( process . platform === "win32" ) ? "USERPROFILE" : "HOME" ] ; }