@@ -8,6 +8,7 @@ import os from 'node:os';
88import { glob } from 'glob' ;
99import * as yaml from 'js-yaml' ;
1010import archiver from 'archiver' ;
11+ import { io , Socket } from 'socket.io-client' ;
1112import TestingBotError from '../models/testingbot_error' ;
1213import utils from '../utils' ;
1314import Upload from '../upload' ;
@@ -37,6 +38,11 @@ export interface MaestroResult {
3738 runs : MaestroRunInfo [ ] ;
3839}
3940
41+ export interface MaestroSocketMessage {
42+ id : number ;
43+ payload : string ;
44+ }
45+
4046export default class Maestro {
4147 private readonly URL = 'https://api.testingbot.com/v1/app-automate/maestro' ;
4248 private readonly POLL_INTERVAL_MS = 5000 ;
@@ -51,6 +57,9 @@ export default class Maestro {
5157 private activeRunIds : number [ ] = [ ] ;
5258 private isShuttingDown = false ;
5359 private signalHandler : ( ( ) => void ) | null = null ;
60+ private socket : Socket | null = null ;
61+ private updateServer : string | null = null ;
62+ private updateKey : string | null = null ;
5463
5564 public constructor ( credentials : Credentials , options : MaestroOptions ) {
5665 this . credentials = credentials ;
@@ -181,17 +190,22 @@ export default class Maestro {
181190 // Set up signal handlers before waiting for completion
182191 this . setupSignalHandlers ( ) ;
183192
193+ // Connect to real-time update server (unless --quiet is specified)
194+ this . connectToUpdateServer ( ) ;
195+
184196 if ( ! this . options . quiet ) {
185197 logger . info ( 'Waiting for test results...' ) ;
186198 }
187199 const result = await this . waitForCompletion ( ) ;
188200
189- // Clean up signal handlers
201+ // Clean up
202+ this . disconnectFromUpdateServer ( ) ;
190203 this . removeSignalHandlers ( ) ;
191204
192205 return result ;
193206 } catch ( error ) {
194- // Clean up signal handlers on error
207+ // Clean up on error
208+ this . disconnectFromUpdateServer ( ) ;
195209 this . removeSignalHandlers ( ) ;
196210
197211 logger . error ( error instanceof Error ? error . message : error ) ;
@@ -489,6 +503,13 @@ export default class Maestro {
489503 utils . checkForUpdate ( latestVersion ) ;
490504
491505 const result = response . data ;
506+
507+ // Capture real-time update server info
508+ if ( result . update_server && result . update_key ) {
509+ this . updateServer = result . update_server ;
510+ this . updateKey = result . update_key ;
511+ }
512+
492513 if ( result . success === false ) {
493514 // API returns errors as an array
494515 const errorMessage =
@@ -871,4 +892,76 @@ export default class Maestro {
871892 } ) ;
872893 }
873894 }
895+
896+ private connectToUpdateServer ( ) : void {
897+ if ( ! this . updateServer || ! this . updateKey || this . options . quiet ) {
898+ return ;
899+ }
900+
901+ try {
902+ this . socket = io ( this . updateServer , {
903+ transports : [ 'websocket' ] ,
904+ reconnection : true ,
905+ reconnectionAttempts : 3 ,
906+ reconnectionDelay : 1000 ,
907+ timeout : 10000 ,
908+ } ) ;
909+
910+ this . socket . on ( 'connect' , ( ) => {
911+ // Join the room for this test run
912+ this . socket ?. emit ( 'join' , this . updateKey ) ;
913+ } ) ;
914+
915+ this . socket . on ( 'maestro_data' , ( data : string ) => {
916+ this . handleMaestroData ( data ) ;
917+ } ) ;
918+
919+ this . socket . on ( 'maestro_error' , ( data : string ) => {
920+ this . handleMaestroError ( data ) ;
921+ } ) ;
922+
923+ this . socket . on ( 'connect_error' , ( ) => {
924+ // Silently fail - real-time updates are optional
925+ this . disconnectFromUpdateServer ( ) ;
926+ } ) ;
927+ } catch {
928+ // Socket connection failed, continue without real-time updates
929+ this . socket = null ;
930+ }
931+ }
932+
933+ private disconnectFromUpdateServer ( ) : void {
934+ if ( this . socket ) {
935+ this . socket . disconnect ( ) ;
936+ this . socket = null ;
937+ }
938+ }
939+
940+ private handleMaestroData ( data : string ) : void {
941+ try {
942+ const message : MaestroSocketMessage = JSON . parse ( data ) ;
943+ if ( message . payload ) {
944+ // Clear the status line before printing output
945+ this . clearLine ( ) ;
946+ // Print the Maestro output, trimming trailing newlines
947+ process . stdout . write ( message . payload ) ;
948+ }
949+ } catch {
950+ // Invalid JSON, ignore
951+ }
952+ }
953+
954+ private handleMaestroError ( data : string ) : void {
955+ try {
956+ const message : MaestroSocketMessage = JSON . parse ( data ) ;
957+ if ( message . payload ) {
958+ // Clear the status line before printing error
959+ this . clearLine ( ) ;
960+ // Print the error output
961+ process . stderr . write ( message . payload ) ;
962+ }
963+ } catch {
964+ // Invalid JSON, ignore
965+ }
966+ }
874967}
0 commit comments