49 lines
2.0 KiB
JavaScript
49 lines
2.0 KiB
JavaScript
/**
|
|||
|
|
* Result callback for in-app browser actions.
|
||
|
|
*
|
||
|
|
* The client that owns the browser view posts here with the outcome of a
|
||
|
|
* request it received over the event stream. Only the request id is trusted to
|
||
|
|
* correlate; an unknown id is accepted with `matched: false` rather than an
|
||
|
|
* error, because a client answering after a timeout has done nothing wrong.
|
||
|
|
*/
|
||
|
|
export function registerBrowserControlRoutes(app, { express, broker }) {
|
||
|
|
// Claiming is separate from answering so that a client learns whether it may
|
||
|
|
// act *before* it acts. Deciding by whose result arrives first would be too
|
||
|
|
// late: by then every client has already clicked.
|
||
|
|
app.post('/api/browser-control/claim', express.json({ limit: '4kb' }), (req, res) => {
|
||
|
|
const requestId = typeof req.body?.requestId === 'string' ? req.body.requestId.trim() : '';
|
||
|
|
if (!requestId) {
|
||
|
|
res.status(400).json({ error: 'requestId is required' });
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
res.json({ granted: broker.claim(requestId) });
|
||
|
|
});
|
||
|
|
|
||
|
|
// This server attaches body parsing per route rather than globally. Without
|
||
|
|
// it `req.body` is undefined here, the client's result is rejected, and the
|
||
|
|
// agent sees an unexplained timeout instead of its answer. A page snapshot
|
||
|
|
// carries the visible text plus every interactive element, so the limit is
|
||
|
|
// sized for that rather than for a small control message.
|
||
|
|
app.post('/api/browser-control/result', express.json({ limit: '2mb' }), (req, res) => {
|
||
|
|
const body = req.body;
|
||
|
|
if (!body || typeof body !== 'object') {
|
||
|
|
res.status(400).json({ error: 'A JSON body is required' });
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const requestId = typeof body.requestId === 'string' ? body.requestId.trim() : '';
|
||
|
|
if (!requestId) {
|
||
|
|
res.status(400).json({ error: 'requestId is required' });
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const matched = broker.resolve(requestId, {
|
||
|
|
ok: body.ok === true,
|
||
|
|
data: body.data ?? null,
|
||
|
|
error: typeof body.error === 'string' ? body.error : '',
|
||
|
|
});
|
||
|
|
|
||
|
|
res.json({ matched });
|
||
|
|
});
|
||
|
|
}
|