/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @format */ import { ClientResponseType, ExecuteMessage, FlipperServerForServerAddOn, ServerAddOnStartDetails, } from 'flipper-common'; import {assertNotNull} from '../comms/Utilities'; import {StateMachine} from '../utils/StateMachine'; import {ServerAddOn} from './ServerAddOn'; type TState = | 'inactive' | 'starting' | 'active' | 'fatal' | 'stopping' | 'zombie'; export class ServerAddOnManager { public readonly state = new StateMachine('inactive'); private _serverAddOn?: ServerAddOn; constructor( public readonly pluginName: string, details: ServerAddOnStartDetails, initialOwner: string, flipperServer: FlipperServerForServerAddOn, ) { this.startServerAddOn(details, initialOwner, flipperServer); } sendExpectResponse(message: ExecuteMessage): Promise { if (!this.state.is('active')) { console.info( 'StateAddOnManager.sendExpectResponse -> error: server add-on is not active, Current state:', this.state.currentState, ); throw new Error( 'StateAddOnManager.sendExpectResponse -> error: server add-on is not active', ); } assertNotNull( this._serverAddOn, 'StateAddOnManager.sendExpectResponse -> _serverAddOn is undefined', ); return this._serverAddOn.connection.sendExpectResponse(message); } async addOwner(owner: string) { if (this.state.is('starting')) { await this.state.wait(['active', 'fatal']); } if (!this.state.is('active')) { console.info( 'StateAddOnManager.addOwner -> error: server add-on is not active, Current state:', this.state.currentState, ); throw new Error( 'StateAddOnManager.addOwner -> error: server add-on is not active', ); } assertNotNull( this._serverAddOn, 'StateAddOnManager.addOwner -> _serverAddOn is undefined', ); this._serverAddOn.addOwner(owner); } async removeOwner(owner: string) { if (this.state.is(['stopping', 'inactive'])) { return this.state.wait(['zombie', 'inactive']); } if (this.state.is('starting')) { await this.state.wait(['active', 'fatal']); } if (!this.state.is('active')) { console.debug( 'StateAddOnManager.removeOwner -> error: server add-on failed to start, Current state:', this.state.currentState, ); return; } assertNotNull( this._serverAddOn, 'StateAddOnManager.addOwner -> _serverAddOn is undefined', ); const stopping = this._serverAddOn.removeOwner(owner); if (stopping) { this.state.set('stopping'); try { await stopping; this.state.set('inactive'); } catch (e) { this.state.set('zombie'); console.error( 'ServerAddOnManager.removeOwner -> server add-on failed to clean up', this.pluginName, e, ); throw e; } } } private async startServerAddOn( details: ServerAddOnStartDetails, initialOwner: string, flipperServer: FlipperServerForServerAddOn, ) { try { this.state.set('starting'); this._serverAddOn = await ServerAddOn.start( this.pluginName, details, initialOwner, flipperServer, ); this.state.set('active'); } catch (e) { this.state.set('fatal', e); console.error( 'StateAddOnManager.startServerAddOn -> error', this.pluginName, details, initialOwner, e, ); } } }