UNPKG

@appium/base-driver

Version:

Base driver class for Appium drivers

191 lines (175 loc) 5.34 kB
import _ from 'lodash'; import log from './logger'; import {errors} from '../protocol'; export {handleIdempotency} from './idempotency'; import {match} from 'path-to-regexp'; import {util} from '@appium/support'; import {calcSignature} from '../helpers/session'; /** * * @param {import('express').Request} req * @param {import('express').Response} res * @param {import('express').NextFunction} next * @returns {any} */ export function allowCrossDomain(req, res, next) { try { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, OPTIONS, DELETE'); res.header( 'Access-Control-Allow-Headers', 'Cache-Control, Pragma, Origin, X-Requested-With, Content-Type, Accept, User-Agent' ); // need to respond 200 to OPTIONS if ('OPTIONS' === req.method) { return res.sendStatus(200); } } catch (err) { log.error(`Unexpected error: ${err.stack}`); } next(); } /** * @param {string} basePath * @returns {import('express').RequestHandler} */ export function allowCrossDomainAsyncExecute(basePath) { return (req, res, next) => { // there are two paths for async responses, so cover both // https://regex101.com/r/txYiEz/1 const receiveAsyncResponseRegExp = new RegExp( `${_.escapeRegExp(basePath)}/session/[a-f0-9-]+/(appium/)?receive_async_response` ); if (!receiveAsyncResponseRegExp.test(req.url)) { return next(); } allowCrossDomain(req, res, next); }; } /** * * @param {string} basePath * @returns {import('express').RequestHandler} */ export function fixPythonContentType(basePath) { return (req, res, next) => { // hack because python client library gives us wrong content-type if ( new RegExp(`^${_.escapeRegExp(basePath)}`).test(req.path) && /^Python/.test(req.headers['user-agent'] ?? '') ) { if (req.headers['content-type'] === 'application/x-www-form-urlencoded') { req.headers['content-type'] = 'application/json; charset=utf-8'; } } next(); }; } /** * * @param {import('express').Request} req * @param {import('express').Response} res * @param {import('express').NextFunction} next * @returns {any} */ export function handleLogContext(req, res, next) { const requestId = util.uuidV4(); const sessionId = SESSION_ID_PATTERN.exec(req.url)?.[1]; const sessionInfo = sessionId ? {sessionId, sessionSignature: calcSignature(sessionId)} : {}; log.updateAsyncContext({requestId, ...sessionInfo}, true); return next(); } /** * * @param {import('express').Request} req * @param {import('express').Response} res * @param {import('express').NextFunction} next * @returns {any} */ export function defaultToJSONContentType(req, res, next) { if (!req.headers['content-type']) { req.headers['content-type'] = 'application/json; charset=utf-8'; } next(); } /** * * @param {import('@appium/types').StringRecord<import('@appium/types').WSServer>} webSocketsMapping * @returns {import('express').RequestHandler} */ export function handleUpgrade(webSocketsMapping) { return (req, res, next) => { if (!req.headers?.upgrade || _.toLower(req.headers.upgrade) !== 'websocket') { return next(); } let currentPathname; try { currentPathname = new URL(req.url ?? '').pathname; } catch { currentPathname = req.url ?? ''; } for (const [pathname, wsServer] of _.toPairs(webSocketsMapping)) { if (match(pathname)(currentPathname)) { return wsServer.handleUpgrade(req, req.socket, Buffer.from(''), (ws) => { wsServer.emit('connection', ws, req); }); } } log.info(`Did not match the websocket upgrade request at ${currentPathname} to any known route`); next(); }; } /** * @param {Error} err * @param {import('express').Request} req * @param {import('express').Response} res * @param {import('express').NextFunction} next */ export function catchAllHandler(err, req, res, next) { if (res.headersSent) { return next(err); } log.error(`Uncaught error: ${err.message}`); log.error('Sending generic error response'); const error = errors.UnknownError; res.status(error.w3cStatus()).json( patchWithSessionId(req, { status: error.code(), value: { error: error.error(), message: `An unknown server-side error occurred while processing the command: ${err.message}`, stacktrace: err.stack, }, }) ); log.error(err); } /** * @param {import('express').Request} req * @param {import('express').Response} res */ export function catch404Handler(req, res) { log.debug(`No route found for ${req.url}`); const error = errors.UnknownCommandError; res.status(error.w3cStatus()).json( patchWithSessionId(req, { status: error.code(), value: { error: error.error(), message: 'The requested resource could not be found, or a request was ' + 'received using an HTTP method that is not supported by the mapped ' + 'resource', stacktrace: '', }, }) ); } const SESSION_ID_PATTERN = /\/session\/([^/]+)/; function patchWithSessionId(req, body) { const match = SESSION_ID_PATTERN.exec(req.url); if (match) { body.sessionId = match[1]; } return body; }