2024-06-05 04:24:49 +05:30
|
|
|
import Interpreter, { WorkflowFile } from "@wbr-project/wbr-interpret";
|
|
|
|
|
import logger from "../../logger";
|
|
|
|
|
import { Socket } from "socket.io";
|
|
|
|
|
import { Page } from "playwright";
|
|
|
|
|
import { InterpreterSettings } from "../../types";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This class implements the main interpretation functions.
|
|
|
|
|
* It holds some information about the current interpretation process and
|
|
|
|
|
* registers to some events to allow the client (frontend) to interact with the interpreter.
|
|
|
|
|
* It uses the [@wbr-project/wbr-interpret](https://www.npmjs.com/package/@wbr-project/wbr-interpret)
|
|
|
|
|
* library to interpret the workflow.
|
|
|
|
|
* @category WorkflowManagement
|
|
|
|
|
*/
|
|
|
|
|
export class WorkflowInterpreter {
|
|
|
|
|
/**
|
|
|
|
|
* Socket.io socket instance enabling communication with the client (frontend) side.
|
|
|
|
|
* @private
|
|
|
|
|
*/
|
|
|
|
|
private socket : Socket;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* True if the interpretation is paused.
|
|
|
|
|
*/
|
|
|
|
|
public interpretationIsPaused: boolean = false;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The instance of the {@link Interpreter} class used to interpret the workflow.
|
|
|
|
|
* From @wbr-project/wbr-interpret.
|
|
|
|
|
* @private
|
|
|
|
|
*/
|
|
|
|
|
private interpreter: Interpreter | null = null;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* An id of the currently interpreted pair in the workflow.
|
|
|
|
|
* @private
|
|
|
|
|
*/
|
|
|
|
|
private activeId: number | null = null;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* An array of debug messages emitted by the {@link Interpreter}.
|
|
|
|
|
*/
|
|
|
|
|
public debugMessages: string[] = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* An array of all the serializable data extracted from the run.
|
|
|
|
|
*/
|
|
|
|
|
public serializableData: string[] = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* An array of all the binary data extracted from the run.
|
|
|
|
|
*/
|
|
|
|
|
public binaryData: {mimetype: string, data: string}[] = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* An array of id's of the pairs from the workflow that are about to be paused.
|
|
|
|
|
* As "breakpoints".
|
|
|
|
|
* @private
|
|
|
|
|
*/
|
|
|
|
|
private breakpoints: boolean[] = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Callback to resume the interpretation after a pause.
|
|
|
|
|
* @private
|
|
|
|
|
*/
|
|
|
|
|
private interpretationResume: (() => void) | null = null;
|
|
|
|
|
|
2024-06-05 04:28:43 +05:30
|
|
|
/**
|
|
|
|
|
* A public constructor taking a socket instance for communication with the client.
|
|
|
|
|
* @param socket Socket.io socket instance enabling communication with the client (frontend) side.
|
|
|
|
|
* @constructor
|
|
|
|
|
*/
|
|
|
|
|
constructor (socket: Socket) {
|
|
|
|
|
this.socket = socket;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-05 04:29:55 +05:30
|
|
|
/**
|
|
|
|
|
* Subscribes to the events that are used to control the interpretation.
|
|
|
|
|
* The events are pause, resume, step and breakpoints.
|
|
|
|
|
* Step is used to interpret a single pair and pause on the other matched pair.
|
|
|
|
|
* @returns void
|
|
|
|
|
*/
|
|
|
|
|
public subscribeToPausing = () => {
|
|
|
|
|
this.socket.on('pause', () => {
|
|
|
|
|
this.interpretationIsPaused = true;
|
|
|
|
|
});
|
2024-06-05 04:30:26 +05:30
|
|
|
this.socket.on('resume', () => {
|
|
|
|
|
this.interpretationIsPaused = false;
|
|
|
|
|
if (this.interpretationResume) {
|
|
|
|
|
this.interpretationResume();
|
|
|
|
|
this.socket.emit('log', '----- The interpretation has been resumed -----', false);
|
|
|
|
|
} else {
|
|
|
|
|
logger.log('debug',"Resume called but no resume function is set");
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-06-05 04:30:44 +05:30
|
|
|
this.socket.on('step', () => {
|
|
|
|
|
if (this.interpretationResume) {
|
|
|
|
|
this.interpretationResume();
|
|
|
|
|
} else {
|
|
|
|
|
logger.log('debug', "Step called but no resume function is set");
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-06-05 04:30:59 +05:30
|
|
|
this.socket.on('breakpoints', (data: boolean[]) => {
|
|
|
|
|
logger.log('debug', "Setting breakpoints: " + data);
|
|
|
|
|
this.breakpoints = data
|
|
|
|
|
});
|
2024-06-05 04:29:55 +05:30
|
|
|
}
|
2024-06-05 04:32:28 +05:30
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sets up the instance of {@link Interpreter} and interprets
|
|
|
|
|
* the workflow inside the recording editor.
|
|
|
|
|
* Cleans up this interpreter instance after the interpretation is finished.
|
|
|
|
|
* @param workflow The workflow to interpret.
|
|
|
|
|
* @param page The page instance used to interact with the browser.
|
|
|
|
|
* @param updatePageOnPause A callback to update the page after a pause.
|
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
*/
|
|
|
|
|
public interpretRecordingInEditor = async (
|
|
|
|
|
workflow: WorkflowFile,
|
|
|
|
|
page: Page,
|
|
|
|
|
updatePageOnPause: (page: Page) => void,
|
|
|
|
|
settings: InterpreterSettings,
|
|
|
|
|
) => {
|
|
|
|
|
const params = settings.params ? settings.params : null;
|
|
|
|
|
delete settings.params;
|
|
|
|
|
const options = {
|
|
|
|
|
...settings,
|
|
|
|
|
debugChannel: {
|
|
|
|
|
activeId: (id: any) => {
|
|
|
|
|
this.activeId = id;
|
|
|
|
|
this.socket.emit('activePairId', id);
|
|
|
|
|
},
|
|
|
|
|
debugMessage: (msg: any) => {
|
|
|
|
|
this.debugMessages.push(`[${new Date().toLocaleString()}] ` + msg);
|
|
|
|
|
this.socket.emit('log', msg)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
serializableCallback: (data: any) => {
|
|
|
|
|
this.socket.emit('serializableCallback', data);
|
|
|
|
|
},
|
|
|
|
|
binaryCallback: (data: string, mimetype: string) => {
|
|
|
|
|
this.socket.emit('binaryCallback', {data, mimetype});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const interpreter = new Interpreter(workflow, options);
|
|
|
|
|
this.interpreter = interpreter;
|
|
|
|
|
|
|
|
|
|
interpreter.on('flag', async (page, resume) => {
|
|
|
|
|
if (this.activeId !== null && this.breakpoints[this.activeId]) {
|
|
|
|
|
logger.log('debug',`breakpoint hit id: ${this.activeId}`);
|
|
|
|
|
this.socket.emit('breakpointHit');
|
|
|
|
|
this.interpretationIsPaused = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.interpretationIsPaused) {
|
|
|
|
|
this.interpretationResume = resume;
|
|
|
|
|
logger.log('debug',`Paused inside of flag: ${page.url()}`);
|
|
|
|
|
updatePageOnPause(page);
|
|
|
|
|
this.socket.emit('log', '----- The interpretation has been paused -----', false);
|
|
|
|
|
} else {
|
|
|
|
|
resume();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.socket.emit('log', '----- Starting the interpretation -----', false);
|
|
|
|
|
|
|
|
|
|
const status = await interpreter.run(page, params);
|
|
|
|
|
|
|
|
|
|
this.socket.emit('log', `----- The interpretation finished with status: ${status} -----`, false);
|
|
|
|
|
|
|
|
|
|
logger.log('debug',`Interpretation finished`);
|
|
|
|
|
this.interpreter = null;
|
|
|
|
|
this.socket.emit('activePairId', -1);
|
|
|
|
|
this.interpretationIsPaused = false;
|
|
|
|
|
this.interpretationResume = null;
|
|
|
|
|
this.socket.emit('finished');
|
|
|
|
|
};
|
|
|
|
|
|
2024-06-05 04:33:07 +05:30
|
|
|
/**
|
|
|
|
|
* Stops the current process of the interpretation of the workflow.
|
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
*/
|
|
|
|
|
public stopInterpretation = async () => {
|
|
|
|
|
if (this.interpreter) {
|
|
|
|
|
logger.log('info', 'Stopping the interpretation.');
|
|
|
|
|
await this.interpreter.stop();
|
|
|
|
|
this.socket.emit('log', '----- The interpretation has been stopped -----', false);
|
|
|
|
|
} else {
|
|
|
|
|
logger.log('error', 'Cannot stop: No active interpretation.');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-06-05 04:32:28 +05:30
|
|
|
|
2024-06-05 04:24:49 +05:30
|
|
|
}
|