UNPKG

@appium/base-driver

Version:

Base driver class for Appium drivers

155 lines 6.25 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.handleIdempotency = void 0; exports.allowCrossDomain = allowCrossDomain; exports.allowCrossDomainAsyncExecute = allowCrossDomainAsyncExecute; exports.handleLogContext = handleLogContext; exports.defaultToJSONContentType = defaultToJSONContentType; exports.tryHandleWebSocketUpgrade = tryHandleWebSocketUpgrade; exports.handleUpgrade = handleUpgrade; exports.catchAllHandler = catchAllHandler; exports.catch404Handler = catch404Handler; const lodash_1 = __importDefault(require("lodash")); const logger_1 = require("./logger"); const protocol_1 = require("../protocol"); var idempotency_1 = require("./idempotency"); Object.defineProperty(exports, "handleIdempotency", { enumerable: true, get: function () { return idempotency_1.handleIdempotency; } }); const path_to_regexp_1 = require("path-to-regexp"); const support_1 = require("@appium/support"); const session_1 = require("../helpers/session"); const errors_1 = require("../protocol/errors"); const SESSION_ID_PATTERN = /\/session\/([^/]+)/; /** * Basic CORS middleware. * Sets permissive CORS headers and responds immediately to `OPTIONS` requests with 200. */ function allowCrossDomain(req, res, next) { 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'); if (req.method === 'OPTIONS') { res.sendStatus(200); return; } next(); } /** * CORS middleware for async execute response endpoints only. * Leaves other routes untouched but applies {@link allowCrossDomain} to async response URLs. * * @param basePath - Server base path (e.g. `/wd/hub` or `/`) * @returns Express request handler */ function allowCrossDomainAsyncExecute(basePath) { function allowCrossDomainAsyncExecuteHandler(req, res, next) { const receiveAsyncResponseRegExp = new RegExp(`${lodash_1.default.escapeRegExp(basePath)}/session/[a-f0-9-]+/(appium/)?receive_async_response`); if (!receiveAsyncResponseRegExp.test(req.url)) { next(); return; } allowCrossDomain(req, res, next); } return allowCrossDomainAsyncExecuteHandler; } /** * Populates the logger's async context with request and session metadata. * Derives `requestId`, optional session id/signature, and `isSensitive` flag from headers/URL. */ function handleLogContext(req, _res, next) { const requestId = fetchHeaderValue(req, 'x-request-id') || support_1.util.uuidV4(); const sessionId = SESSION_ID_PATTERN.exec(req.url)?.[1]; const sessionInfo = sessionId ? { sessionId, sessionSignature: (0, session_1.calcSignature)(sessionId) } : {}; const isSensitiveHeaderValue = fetchHeaderValue(req, 'x-appium-is-sensitive'); logger_1.log.updateAsyncContext({ requestId, ...sessionInfo, isSensitive: ['true', '1', 'yes'].includes(lodash_1.default.toLower(isSensitiveHeaderValue)), }, true); next(); } /** * Ensures requests default to JSON content-type when none is provided. */ function defaultToJSONContentType(req, _res, next) { if (!req.headers['content-type']) { req.headers['content-type'] = 'application/json; charset=utf-8'; } next(); } /** * Attempts to handle a WebSocket upgrade by matching the request path against registered handlers. * * @param req - Incoming HTTP request * @param socket - Network socket * @param head - First packet of the upgraded stream * @param webSocketsMapping - Path-to-WebSocket-server mapping * @returns `true` if the upgrade was handled; `false` otherwise */ function tryHandleWebSocketUpgrade(req, socket, head, webSocketsMapping) { if (lodash_1.default.toLower(req.headers?.upgrade) !== 'websocket') { return false; } let currentPathname; try { currentPathname = new URL(req.url ?? '', 'http://localhost').pathname; } catch { currentPathname = req.url ?? ''; } for (const [pathname, wsServer] of lodash_1.default.toPairs(webSocketsMapping)) { if ((0, path_to_regexp_1.match)(pathname)(currentPathname)) { wsServer.handleUpgrade(req, socket, head, (ws) => { wsServer.emit('connection', ws, req); }); return true; } } logger_1.log.info(`Did not match the websocket upgrade request at ${currentPathname} to any known route`); return false; } /** * Express middleware wrapper around {@link tryHandleWebSocketUpgrade}. * Delegates WebSocket upgrades to the mapping and falls through to `next()` otherwise. * * @param webSocketsMapping - Path-to-WebSocket-server mapping * @returns Express request handler */ function handleUpgrade(webSocketsMapping) { function handleUpgradeMiddleware(req, res, next) { if (tryHandleWebSocketUpgrade(req, req.socket, Buffer.from(''), webSocketsMapping)) { return; } next(); } return handleUpgradeMiddleware; } /** * Final error-handling middleware. * Logs uncaught errors and returns a W3C-formatted error response unless headers were already sent. */ function catchAllHandler(err, _req, res, next) { if (res.headersSent) { next(err); return; } logger_1.log.error(`Uncaught error: ${err.message}`); const [status, body] = (0, errors_1.getResponseForW3CError)(err); res.status(status).json(body); } /** * 404 handler for unmatched routes. * Logs a debug message and responds with `UnknownCommandError` in W3C format. */ function catch404Handler(req, res) { logger_1.log.debug(`No route found for ${req.url}`); const [status, body] = (0, errors_1.getResponseForW3CError)(new protocol_1.errors.UnknownCommandError()); res.status(status).json(body); } function fetchHeaderValue(req, name) { const value = req.headers[name]; return lodash_1.default.isArray(value) ? value[0] : value; } //# sourceMappingURL=middleware.js.map