diff --git a/server/src/routes/index.ts b/server/src/routes/index.ts new file mode 100644 index 00000000..ff6f8402 --- /dev/null +++ b/server/src/routes/index.ts @@ -0,0 +1,9 @@ +import { router as record } from './record'; +import { router as workflow } from './workflow'; +import { router as storage } from './storage'; + +export { + record, + workflow, + storage, +}; diff --git a/server/src/routes/record.ts b/server/src/routes/record.ts new file mode 100644 index 00000000..da62bf8e --- /dev/null +++ b/server/src/routes/record.ts @@ -0,0 +1,113 @@ +/** + * RESTful API endpoints handling remote browser recording sessions. + */ +import { Router } from 'express'; + +import { + initializeRemoteBrowserForRecording, + destroyRemoteBrowser, + getActiveBrowserId, + interpretWholeWorkflow, + stopRunningInterpretation, + getRemoteBrowserCurrentUrl, getRemoteBrowserCurrentTabs, +} from '../browser-management/controller' +import { chromium } from "playwright"; +import logger from "../logger"; + +export const router = Router(); + +/** + * Logs information about remote browser recording session. + */ +router.all('/', (req, res, next) => { + logger.log('debug', `The record API was invoked: ${req.url}`) + next() // pass control to the next handler +}) + +/** + * GET endpoint for starting the remote browser recording session. + * returns session's id + */ +router.get('/start', (req, res) => { + const id = initializeRemoteBrowserForRecording({ + browser: chromium, + launchOptions: { + headless: true, + } + }); + return res.send(id); +}); + +/** + * POST endpoint for starting the remote browser recording session accepting browser launch options. + * returns session's id + */ +router.post('/start', (req, res) => { + const id = initializeRemoteBrowserForRecording({ + browser: chromium, + launchOptions: req.body, + }); + return res.send(id); +}); + +/** + * GET endpoint for terminating the remote browser recording session. + * returns whether the termination was successful + */ +router.get('/stop/:browserId', async (req, res) => { + const success = await destroyRemoteBrowser(req.params.browserId); + return res.send(success); +}); + +/** + * GET endpoint for getting the id of the active remote browser. + */ +router.get('/active', (req, res) => { + const id = getActiveBrowserId(); + return res.send(id); +}); + +/** + * GET endpoint for getting the current url of the active remote browser. + */ +router.get('/active/url', (req, res) => { + const id = getActiveBrowserId(); + if (id) { + const url = getRemoteBrowserCurrentUrl(id); + return res.send(url); + } + return res.send(null); +}); + +/** + * GET endpoint for getting the current tabs of the active remote browser. + */ +router.get('/active/tabs', (req, res) => { + const id = getActiveBrowserId(); + if (id) { + const hosts = getRemoteBrowserCurrentTabs(id); + return res.send(hosts); + } + return res.send([]); +}); + +/** + * GET endpoint for starting an interpretation of the currently generated workflow. + */ +router.get('/interpret', async (req, res) => { + try { + await interpretWholeWorkflow(); + return res.send('interpretation done'); + } catch (e) { + return res.send('interpretation done'); + return res.status(400); + } +}); + +/** + * GET endpoint for stopping an ongoing interpretation of the currently generated workflow. + */ +router.get('/interpret/stop', async (req, res) => { + await stopRunningInterpretation(); + return res.send('interpretation stopped'); +}); diff --git a/server/src/routes/workflow.ts b/server/src/routes/workflow.ts new file mode 100644 index 00000000..73d8f490 --- /dev/null +++ b/server/src/routes/workflow.ts @@ -0,0 +1,123 @@ +/** + * RESTful API endpoints handling currently generated workflow management. + */ + +import { Router } from 'express'; +import logger from "../logger"; +import { browserPool } from "../server"; +import { readFile } from "../workflow-management/storage"; + +export const router = Router(); + +/** + * Logs information about workflow API. + */ +router.all('/', (req, res, next) => { + logger.log('debug', `The workflow API was invoked: ${req.url}`) + next() // pass control to the next handler +}) + +/** + * GET endpoint for a recording linked to a remote browser instance. + * returns session's id + */ +router.get('/:browserId', (req, res) => { + const activeBrowser = browserPool.getRemoteBrowser(req.params.browserId); + let workflowFile = null; + if (activeBrowser && activeBrowser.generator) { + workflowFile = activeBrowser.generator.getWorkflowFile(); + } + return res.send(workflowFile); +}); + +/** + * Get endpoint returning the parameter array of the recording associated with the browserId browser instance. + */ +router.get('/params/:browserId', (req, res) => { + const activeBrowser = browserPool.getRemoteBrowser(req.params.browserId); + let params = null; + if (activeBrowser && activeBrowser.generator) { + params = activeBrowser.generator.getParams(); + } + return res.send(params); +}); + +/** + * DELETE endpoint for deleting a pair from the generated workflow. + */ +router.delete('/pair/:index', (req, res) => { + const id = browserPool.getActiveBrowserId(); + if (id) { + const browser = browserPool.getRemoteBrowser(id); + if (browser) { + browser.generator?.removePairFromWorkflow(parseInt(req.params.index)); + const workflowFile = browser.generator?.getWorkflowFile(); + return res.send(workflowFile); + } + } + return res.send(null); +}); + +/** + * POST endpoint for adding a pair to the generated workflow. + */ +router.post('/pair/:index', (req, res) => { + const id = browserPool.getActiveBrowserId(); + if (id) { + const browser = browserPool.getRemoteBrowser(id); + logger.log('debug', `Adding pair to workflow`); + if (browser) { + logger.log('debug', `Adding pair to workflow: ${JSON.stringify(req.body)}`); + if (req.body.pair) { + browser.generator?.addPairToWorkflow(parseInt(req.params.index), req.body.pair); + const workflowFile = browser.generator?.getWorkflowFile(); + return res.send(workflowFile); + } + } + } + return res.send(null); +}); + +/** + * PUT endpoint for updating a pair in the generated workflow. + */ +router.put('/pair/:index', (req, res) => { + const id = browserPool.getActiveBrowserId(); + if (id) { + const browser = browserPool.getRemoteBrowser(id); + logger.log('debug', `Updating pair in workflow`); + if (browser) { + logger.log('debug', `New value: ${JSON.stringify(req.body)}`); + if (req.body.pair) { + browser.generator?.updatePairInWorkflow(parseInt(req.params.index), req.body.pair); + const workflowFile = browser.generator?.getWorkflowFile(); + return res.send(workflowFile); + } + } + } + return res.send(null); +}); + +/** + * PUT endpoint for updating the currently generated workflow file from the one in the storage. + */ +router.put('/:browserId/:fileName', async (req, res) => { + try { + const browser = browserPool.getRemoteBrowser(req.params.browserId); + logger.log('debug', `Updating workflow file`); + if (browser && browser.generator) { + const recording = await readFile(`./../storage/recordings/${req.params.fileName}.waw.json`) + const parsedRecording = JSON.parse(recording); + if (parsedRecording.recording) { + browser.generator?.updateWorkflowFile(parsedRecording.recording, parsedRecording.recording_meta); + const workflowFile = browser.generator?.getWorkflowFile(); + return res.send(workflowFile); + } + } + return res.send(null); + } catch (e) { + const { message } = e as Error; + logger.log('info', `Error while reading a recording with name: ${req.params.fileName}.waw.json`); + return res.send(null); + } +});