import * as semver from 'semver' ; import * as errors from '../infrastructure/errors' ; import * as server from '../model/server' ; interface AccessKeyJson { id : string ; name : string ; accessUrl : string ; } interface ServerConfigJson { name : string ; metricsEnabled : boolean ; serverId : string ; createdTimestampMs : number ; portForNewAccessKeys : number ; hostnameForAccessKeys : string ; version : string ; accessKeyDataLimit ? : server . DataLimit ; } interface DataUsageByAccessKeyJson { bytesTransferredByUserId : { [ accessKeyId ] : number ; } ; } function makeAccessKeyModel ( apiAccessKey ) { return apiAccessKey as server . AccessKey ; } export class ShadowboxServer implements server . Server { private managementApiAddress : string ; private serverConfig : ServerConfigJson ; constructor ( private readonly id ) { } getId ( ) { return this . id ; } listAccessKeys ( ) { console . info ( 'Listing access keys' ) ; return this . apiRequest < { accessKeys : AccessKeyJson [ ] ; } > ( 'access-keys' ) . then ( ( response ) => { return response . accessKeys . map ( makeAccessKeyModel ) ; } ) ; } async addAccessKey ( ) { console . info ( 'Adding access key' ) ; return makeAccessKeyModel ( await this . apiRequest < AccessKeyJson > ( 'access-keys' , { method : 'POST' } ) ) ; } renameAccessKey ( accessKeyId , name ) { console . info ( 'Renaming access key' ) ; const body = new FormData ( ) ; body . append ( 'name' , name ) ; return this . apiRequest < void > ( 'access-keys/' + accessKeyId + '/name' , { method : 'PUT' , body } ) ; } removeAccessKey ( accessKeyId ) { console . info ( 'Removing access key' ) ; return this . apiRequest < void > ( 'access-keys/' + accessKeyId , { method : 'DELETE' } ) ; } async setDefaultDataLimit ( limit ) { console . info ( `Setting server default data limit: ${ JSON . stringify ( limit ) }` ) ; const requestOptions = { method : 'PUT' , headers : new Headers ( { 'Content-Type' : 'application/json' } ) , body : JSON . stringify ( { limit } ) } ; await this . apiRequest < void > ( this . getDefaultDataLimitPath ( ) , requestOptions ) ; this . serverConfig . accessKeyDataLimit = limit ; } async removeDefaultDataLimit ( ) { console . info ( `Removing server default data limit` ) ; await this . apiRequest < void > ( this . getDefaultDataLimitPath ( ) , { method : 'DELETE' } ) ; delete this . serverConfig . accessKeyDataLimit ; } getDefaultDataLimit ( ) { return this . serverConfig . accessKeyDataLimit ; } private getDefaultDataLimitPath ( ) { const version = this . getVersion ( ) ; if ( semver . gte ( version , '1.4.0' ) ) { return 'server/access-key-data-limit' ; } return 'experimental/access-key-data-limit' ; } async setAccessKeyDataLimit ( keyId , limit ) { console . info ( `Setting data limit of ${ limit . bytes } bytes for access key ${ keyId }` ) ; const requestOptions = { method : 'PUT' , headers : new Headers ( { 'Content-Type' : 'application/json' } ) , body : JSON . stringify ( { limit } ) } ; await this . apiRequest < void > ( `access-keys/${ keyId }/data-limit` , requestOptions ) ; } async removeAccessKeyDataLimit ( keyId ) { console . info ( `Removing data limit from access key ${ keyId }` ) ; await this . apiRequest < void > ( `access-keys/${ keyId }/data-limit` , { method : 'DELETE' } ) ; } async getDataUsage ( ) { const jsonResponse = await this . apiRequest < DataUsageByAccessKeyJson > ( 'metrics/transfer' ) ; const usageMap = new Map < server . AccessKeyId , number > ( ) ; for ( const [ accessKeyId , bytes ] of Object . entries ( jsonResponse . bytesTransferredByUserId ) ) { usageMap . set ( accessKeyId , bytes ?? 0 ) ; } return usageMap ; } getName ( ) { return this . serverConfig ?. name ; } setName ( name ) { console . info ( 'Setting server name' ) ; const requestOptions = { method : 'PUT' , headers : new Headers ( { 'Content-Type' : 'application/json' } ) , body : JSON . stringify ( { name } ) } ; return this . apiRequest < void > ( 'name' , requestOptions ) . then ( ( ) => { this . serverConfig . name = name ; } ) ; } getVersion ( ) { return this . serverConfig . version ; } getMetricsEnabled ( ) { return this . serverConfig . metricsEnabled ; } setMetricsEnabled ( metricsEnabled ) { const action = metricsEnabled ? 'Enabling' : 'Disabling' ; console . info ( `${ action } metrics` ) ; const requestOptions = { method : 'PUT' , headers : new Headers ( { 'Content-Type' : 'application/json' } ) , body : JSON . stringify ( { metricsEnabled } ) } ; return this . apiRequest < void > ( 'metrics/enabled' , requestOptions ) . then ( ( ) => { this . serverConfig . metricsEnabled = metricsEnabled ; } ) ; } getMetricsId ( ) { return this . serverConfig . serverId ; } isHealthy ( timeoutMs = 30000 ) { return new Promise < boolean > ( ( fulfill , reject ) => { this . getServerConfig ( ) . then ( ( serverConfig ) => { this . serverConfig = serverConfig ; fulfill ( true ) ; } , ( e ) => { fulfill ( false ) ; } ) ; setTimeout ( ( ) => { fulfill ( false ) ; } , timeoutMs ) ; } ) ; } getCreatedDate ( ) { return new Date ( this . serverConfig . createdTimestampMs ) ; } async setHostnameForAccessKeys ( hostname ) { console . info ( `setHostname ${ hostname }` ) ; this . serverConfig . hostnameForAccessKeys = hostname ; const requestOptions = { method : 'PUT' , headers : new Headers ( { 'Content-Type' : 'application/json' } ) , body : JSON . stringify ( { hostname } ) } ; return this . apiRequest < void > ( 'server/hostname-for-access-keys' , requestOptions ) . then ( ( ) => { this . serverConfig . hostnameForAccessKeys = hostname ; } ) ; } getHostnameForAccessKeys ( ) { try { return this . serverConfig ?. hostnameForAccessKeys ?? new URL ( this . managementApiAddress ) . hostname ; } catch ( e ) { return '' ; } } getPortForNewAccessKeys ( ) { try { if ( typeof this . serverConfig . portForNewAccessKeys !== 'number' ) { return undefined ; } return this . serverConfig . portForNewAccessKeys ; } catch ( e ) { return undefined ; } } setPortForNewAccessKeys ( newPort ) { console . info ( `setPortForNewAccessKeys: ${ newPort }` ) ; const requestOptions = { method : 'PUT' , headers : new Headers ( { 'Content-Type' : 'application/json' } ) , body : JSON . stringify ( { "port" : newPort } ) } ; return this . apiRequest < void > ( 'server/port-for-new-access-keys' , requestOptions ) . then ( ( ) => { this . serverConfig . portForNewAccessKeys = newPort ; } ) ; } private async getServerConfig ( ) { console . info ( 'Retrieving server configuration' ) ; return await this . apiRequest < ServerConfigJson > ( 'server' ) ; } protected setManagementApiUrl ( apiAddress ) { this . managementApiAddress = apiAddress ; } getManagementApiUrl ( ) { return this . managementApiAddress ; } private apiRequest < T > ( path , options ? ) { try { let apiAddress = this . managementApiAddress ; if ( ! apiAddress ) { const msg = 'Management API address unavailable' ; console . error ( msg ) ; throw new Error ( msg ) ; } if ( ! apiAddress . endsWith ( '/' ) ) { apiAddress += '/' ; } const url = apiAddress + path ; return fetch ( url , options ) . then ( ( response ) => { if ( ! response . ok ) { throw new errors . ServerApiError ( `API request to ${ path } failed with status ${ response . status }` , response ) ; } return response . text ( ) ; } , ( error ) => { throw new errors . ServerApiError ( `API request to ${ path } failed due to network error` ) ; } ) . then ( ( body ) => { if ( ! body ) { return ; } return JSON . parse ( body ) ; } ) ; } catch ( error ) { return Promise . reject ( error ) ; } } }