Skip to content

Commit ba2d72b

Browse files
add socketio support
1 parent c47f3f0 commit ba2d72b

File tree

4 files changed

+444
-3
lines changed

4 files changed

+444
-3
lines changed

package-lock.json

Lines changed: 137 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"glob": "^13.0.0",
6060
"js-yaml": "^4.1.1",
6161
"prettier": "^3.7.4",
62+
"socket.io-client": "^4.8.1",
6263
"tracer": "^1.3.0"
6364
},
6465
"devDependencies": {

src/providers/maestro.ts

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import os from 'node:os';
88
import { glob } from 'glob';
99
import * as yaml from 'js-yaml';
1010
import archiver from 'archiver';
11+
import { io, Socket } from 'socket.io-client';
1112
import TestingBotError from '../models/testingbot_error';
1213
import utils from '../utils';
1314
import 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+
4046
export 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

Comments
 (0)