import * as path from 'path' ; import * as express from 'express' ; import { Application } from 'express' ; import SoundConfig from './SoundConfig' ; import BalenaAudio from 'balena-audio' ; import * as asyncHandler from 'express-async-handler' ; import { constants } from './constants' ; import { restartDevice , rebootDevice , shutdownDevice } from './utils' ; import { getSdk , BalenaSDK } from 'balena-sdk' ; import * as fs from 'fs' ; const VERSION = fs . readFileSync ( 'VERSION' , 'utf-8' ) ; export default class SoundAPI { private api : Application ; private sdk : BalenaSDK ; constructor ( public config , public audioBlock ) { this . sdk = getSdk ( { apiUrl : 'https://api.balena-cloud.com/' } ) ; this . sdk . auth . logout ( ) ; this . sdk . auth . loginWithToken ( process . env . BALENA_API_KEY ! ) ; this . api = express ( ) ; this . api . use ( express . json ( ) ) ; this . api . get ( '/ping' , ( _req , res ) => res . send ( 'OK' ) ) ; this . api . get ( '/config' , ( _req , res ) => res . json ( this . config ) ) ; for ( const [ key , value ] of Object . entries ( this . config ) ) { this . api . get ( `/${ key }` , ( _req , res ) => res . send ( this . config [ key ] ) ) ; if ( typeof value === 'object' ) { for ( const [ subKey ] of Object . entries ( < any > value ) ) { this . api . get ( `/${ key }/${ subKey }` , ( _req , res ) => res . send ( this . config [ key ] [ subKey ] ) ) ; } } } this . api . get ( '/version' , ( _req , res ) => res . send ( VERSION ) ) ; this . api . post ( '/mode' , ( req , res ) => { let updated = this . config . setMode ( req . body . mode ) ; res . json ( { mode : this . config . mode , updated } ) ; } ) ; this . api . get ( '/audio' , asyncHandler ( async ( _req , res ) => res . json ( await this . audioBlock . getInfo ( ) ) ) ) ; this . api . get ( '/audio/volume' , asyncHandler ( async ( _req , res ) => res . json ( await this . audioBlock . getVolume ( ) ) ) ) ; this . api . post ( '/audio/volume' , asyncHandler ( async ( req , res ) => res . json ( await this . audioBlock . setVolume ( req . body . volume ) ) ) ) ; this . api . get ( '/audio/sinks' , asyncHandler ( async ( _req , res ) => res . json ( stringify ( await this . audioBlock . getSinks ( ) ) ) ) ) ; this . api . post ( '/device/restart' , asyncHandler ( async ( _req , res ) => res . json ( await restartDevice ( ) ) ) ) ; this . api . post ( '/device/reboot' , asyncHandler ( async ( _req , res ) => res . json ( await rebootDevice ( ) ) ) ) ; this . api . post ( '/device/shutdown' , asyncHandler ( async ( _req , res ) => res . json ( await shutdownDevice ( ) ) ) ) ; this . api . post ( '/device/dtoverlay' , asyncHandler ( async ( req , res ) => { const { dtoverlay } = req . body ; try { console . log ( `Applying BALENA_HOST_CONFIG_dtoverlay=${ dtoverlay }...` ) ; await this . sdk . models . device . configVar . set ( process . env . BALENA_DEVICE_UUID ! , 'BALENA_HOST_CONFIG_dtoverlay' , dtoverlay ) ; res . json ( { status : 'OK' } ) ; } catch ( error ) { console . log ( error ) ; res . json ( { error : error } ) ; } } ) ) ; this . api . get ( '/support' , asyncHandler ( async ( _req , res ) => { res . json ( { version : VERSION , config : this . config , audio : await this . audioBlock . getInfo ( ) , sinks : stringify ( await this . audioBlock . getSinks ( ) ) , volume : await this . audioBlock . getVolume ( ) , constants : constants } ) ; } ) ) ; this . api . use ( '/' , express . static ( path . join ( __dirname , 'ui' ) ) ) ; this . api . use ( ( err , _req , res , _next ) => { res . status ( 500 ) . json ( { error : err . message } ) ; } ) ; } public async listen ( port ) { return new Promise ( ( resolve ) => { this . api . listen ( port , ( ) => { console . log ( `Sound supervisor listening on port ${ port }` ) ; return resolve ( ) ; } ) ; } ) ; } } function stringify ( value ) { return JSON . parse ( JSON . stringify ( value , ( _ , v ) => typeof v === 'bigint' ? `${ v }n` : v ) ) ; }