{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "type": "pwa-node", "request": "launch", "name": "Launch Program", "skipFiles": [ "/**" ], "program": "${workspaceFolder}/src/server/index.js" } ] } Rammerhead Proxy

Rammerhead Proxy

Join the Discord server for updates or just chat about life

Notice: inactive sessions will be deleted after 3 days.

Notice 2: Treat every session id like an isolated incognito browser that belongs only to you. DO NOT SHARE THE SESSION ID. All logins that you make with the session id will be saved in that session. Anyone that has your session id or session url CAN ACCESS your logged in sites.

Enter URL
Session ID
Session ID Created on
(function () { const mod = (n, m) => ((n % m) + m) % m; const baseDictionary = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~-'; const shuffledIndicator = '_rhs'; const generateDictionary = function () { let str = ''; const split = baseDictionary.split(''); while (split.length > 0) { str += split.splice(Math.floor(Math.random() * split.length), 1)[0]; } return str; }; class StrShuffler { constructor(dictionary = generateDictionary()) { this.dictionary = dictionary; } shuffle(str) { if (str.startsWith(shuffledIndicator)) { return str; } let shuffledStr = ''; for (let i = 0; i < str.length; i++) { const char = str.charAt(i); const idx = baseDictionary.indexOf(char); if (char === '%' && str.length - i >= 3) { shuffledStr += char; shuffledStr += str.charAt(++i); shuffledStr += str.charAt(++i); } else if (idx === -1) { shuffledStr += char; } else { shuffledStr += this.dictionary.charAt(mod(idx + i, baseDictionary.length)); } } return shuffledIndicator + shuffledStr; } unshuffle(str) { if (!str.startsWith(shuffledIndicator)) { return str; } str = str.slice(shuffledIndicator.length); let unshuffledStr = ''; for (let i = 0; i < str.length; i++) { const char = str.charAt(i); const idx = this.dictionary.indexOf(char); if (char === '%' && str.length - i >= 3) { unshuffledStr += char; unshuffledStr += str.charAt(++i); unshuffledStr += str.charAt(++i); } else if (idx === -1) { unshuffledStr += char; } else { unshuffledStr += baseDictionary.charAt(mod(idx - i, baseDictionary.length)); } } return unshuffledStr; } } function setError(err) { var element = document.getElementById('error-text'); if (err) { element.style.display = 'block'; element.textContent = 'An error occurred: ' + err; } else { element.style.display = 'none'; element.textContent = ''; } } function getPassword() { var element = document.getElementById('session-password'); return element ? element.value : ''; } function get(url, callback, shush = false) { var pwd = getPassword(); if (pwd) { // really cheap way of adding a query parameter if (url.includes('?')) { url += '&pwd=' + pwd; } else { url += '?pwd=' + pwd; } } var request = new XMLHttpRequest(); request.open('GET', url, true); request.send(); request.onerror = function () { if (!shush) setError('Cannot communicate with the server'); }; request.onload = function () { if (request.status === 200) { callback(request.responseText); } else { if (!shush) setError( 'unexpected server response to not match "200". Server says "' + request.responseText + '"' ); } }; } var api = { needpassword(callback) { get('/needpassword', value => callback(value === 'true')); }, newsession(callback) { get('/newsession', callback); }, editsession(id, httpProxy, enableShuffling, callback) { get( '/editsession?id=' + encodeURIComponent(id) + (httpProxy ? '&httpProxy=' + encodeURIComponent(httpProxy) : '') + '&enableShuffling=' + (enableShuffling ? '1' : '0'), function (res) { if (res !== 'Success') return setError('unexpected response from server. received ' + res); callback(); } ); }, sessionexists(id, callback) { get('/sessionexists?id=' + encodeURIComponent(id), function (res) { if (res === 'exists') return callback(true); if (res === 'not found') return callback(false); setError('unexpected response from server. received' + res); }); }, deletesession(id, callback) { api.sessionexists(id, function (exists) { if (exists) { get('/deletesession?id=' + id, function (res) { if (res !== 'Success' && res !== 'not found') return setError('unexpected response from server. received ' + res); callback(); }); } else { callback(); } }); }, shuffleDict(id, callback) { get('/api/shuffleDict?id=' + encodeURIComponent(id), function (res) { callback(JSON.parse(res)); }); } }; var localStorageKey = 'rammerhead_sessionids'; var localStorageKeyDefault = 'rammerhead_default_sessionid'; var sessionIdsStore = { get() { var rawData = localStorage.getItem(localStorageKey); if (!rawData) return []; try { var data = JSON.parse(rawData); if (!Array.isArray(data)) throw 'getout'; return data; } catch (e) { return []; } }, set(data) { if (!data || !Array.isArray(data)) throw new TypeError('must be array'); localStorage.setItem(localStorageKey, JSON.stringify(data)); }, getDefault() { var sessionId = localStorage.getItem(localStorageKeyDefault); if (sessionId) { var data = sessionIdsStore.get(); data.filter(function (e) { return e.id === sessionId; }); if (data.length) return data[0]; } return null; }, setDefault(id) { localStorage.setItem(localStorageKeyDefault, id); } }; function renderSessionTable(data) { var tbody = document.querySelector('tbody'); while (tbody.firstChild && !tbody.firstChild.remove()); for (var i = 0; i < data.length; i++) { var tr = document.createElement('tr'); appendIntoTr(data[i].id); appendIntoTr(data[i].createdOn); var fillInBtn = document.createElement('button'); fillInBtn.textContent = 'Fill in existing session ID'; fillInBtn.className = 'btn btn-outline-primary'; fillInBtn.onclick = index(i, function (idx) { setError(); sessionIdsStore.setDefault(data[idx].id); loadSettings(data[idx]); }); appendIntoTr(fillInBtn); var deleteBtn = document.createElement('button'); deleteBtn.textContent = 'Delete'; deleteBtn.className = 'btn btn-outline-danger'; deleteBtn.onclick = index(i, function (idx) { setError(); api.deletesession(data[idx].id, function () { data.splice(idx, 1)[0]; sessionIdsStore.set(data); renderSessionTable(data); }); }); appendIntoTr(deleteBtn); tbody.appendChild(tr); } function appendIntoTr(stuff) { var td = document.createElement('td'); if (typeof stuff === 'object') { td.appendChild(stuff); } else { td.textContent = stuff; } tr.appendChild(td); } function index(i, func) { return func.bind(null, i); } } function loadSettings(session) { document.getElementById('session-id').value = session.id; document.getElementById('session-httpproxy').value = session.httpproxy || ''; document.getElementById('session-shuffling').checked = typeof session.enableShuffling === 'boolean' ? session.enableShuffling : true; } function loadSessions() { var sessions = sessionIdsStore.get(); var defaultSession = sessionIdsStore.getDefault(); if (defaultSession) loadSettings(defaultSession); renderSessionTable(sessions); } function addSession(id) { var data = sessionIdsStore.get(); data.unshift({ id: id, createdOn: new Date().toLocaleString() }); sessionIdsStore.set(data); renderSessionTable(data); } function editSession(id, httpproxy, enableShuffling) { var data = sessionIdsStore.get(); for (var i = 0; i < data.length; i++) { if (data[i].id === id) { data[i].httpproxy = httpproxy; data[i].enableShuffling = enableShuffling; sessionIdsStore.set(data); return; } } throw new TypeError('cannot find ' + id); } get('/mainport', function (data) { var defaultPort = window.__cpLocation.protocol === 'https:' ? 443 : 80; var currentPort = window.__cpLocation.port || defaultPort; var mainPort = data || defaultPort; if (currentPort != mainPort) window.__cpLocation.port = mainPort; }); api.needpassword(doNeed => { if (doNeed) { document.getElementById('password-wrapper').style.display = ''; } }); window.addEventListener('load', function () { loadSessions(); var showingAdvancedOptions = false; document.getElementById('session-advanced-toggle').onclick = function () { // eslint-disable-next-line no-cond-assign document.getElementById('session-advanced-container').style.display = (showingAdvancedOptions = !showingAdvancedOptions) ? 'block' : 'none'; }; document.getElementById('session-create-btn').onclick = function () { setError(); api.newsession(function (id) { addSession(id); document.getElementById('session-id').value = id; document.getElementById('session-httpproxy').value = ''; }); }; function go() { setError(); var id = document.getElementById('session-id').value; var httpproxy = document.getElementById('session-httpproxy').value; var enableShuffling = document.getElementById('session-shuffling').checked; var url = document.getElementById('session-url').value || 'https://www.google.com/'; if (!id) return setError('must generate a session id first'); api.sessionexists(id, function (value) { if (!value) return setError('session does not exist. try deleting or generating a new session'); api.editsession(id, httpproxy, enableShuffling, function () { editSession(id, httpproxy, enableShuffling); api.shuffleDict(id, function (shuffleDict) { if (!shuffleDict) { window.__cpLocation.href = '/' + id + '/' + url; } else { var shuffler = new StrShuffler(shuffleDict); window.__cpLocation.href = '/' + id + '/' + shuffler.shuffle(url); } }); }); }); } document.getElementById('session-go').onclick = go; document.getElementById('session-url').onkeydown = function (event) { if (event.key === 'Enter') go(); }; }); })(); body { background-color: rgb(255, 239, 231); font-family: Arial, Helvetica, sans-serif; } header { text-align: center; } header h1 { margin-top: 0; font-weight: bold; } header img { width: 200px; } .input-group-text, .disable-text { cursor: default; user-select: none; } table.table-bordered { border: 1px solid #c1c1c1; } table.table-bordered>thead>tr>th { border: 1px solid #c1c1c1; } table.table-bordered>tbody>tr>td { border: 1px solid #c1c1c1; } class RammerheadJSAbstractCache { constructor() { throw new TypeError('abstract method'); } async get(key) { throw new TypeError('abstract method'); } async set(key, value) { throw new TypeError('abstract method'); } } module.exports = RammerheadJSAbstractCache; const fs = require('fs'); const path = require('path'); const cluster = require('cluster'); const LRUCache = require('lru-cache'); class RammerheadJSFileCache { constructor(diskJsCachePath, jsCacheSize, maxItems, enableWorkerMode) { /** * this lru cache will be treated as such: key => jsFileSize * when cache item gets evicted, then it will call dispose(), which will * delete the actual file. * * * enableWorkerMode === true will create a system where * master will handle deleting, and writing to cache. * it will also tell workers what is and isn't cached. * * worker will ask master if file is cached, and retrieve it if it exists. * * ids are to keep track which read requests go to whose and resolve the corresponding * promises */ this.askPromises = {}; this.askPromiseId = 0; if (!fs.existsSync(diskJsCachePath)) { throw new TypeError('disk cache folder does not exist: ' + diskJsCachePath); } if (!fs.lstatSync(diskJsCachePath).isDirectory()) { throw new TypeError('disk cache folder must be a directory: ' + diskJsCachePath); } this.diskJsCachePath = diskJsCachePath; this.enableWorkerMode = enableWorkerMode; this.lruMarker = new LRUCache({ max: maxItems, maxSize: jsCacheSize, sizeCalculation: n => n || 1, dispose(_, key) { fs.unlinkSync(path.join(diskJsCachePath, key)); } }); // multiple workers doing this will cause chaos if (!enableWorkerMode || cluster.isMaster) { // reinsert already existing cache items, in size order, keeping biggest first (because more size = more compute required) // also, atime seems to not work reliably. see https://superuser.com/a/464737 const initFileList = []; for (const file of fs.readdirSync(diskJsCachePath)) { if (file === '.gitkeep') continue; const stat = fs.statSync(path.join(diskJsCachePath, file)); initFileList.push({ key: file, size: stat.size }); } initFileList.sort((a, b) => a.size - b.size); for (const file of initFileList) { if (!file.size) { // writing probably got interrupted. so we delete the corrupted file fs.unlinkSync(path.join(diskJsCachePath, file.key)); continue; } this.lruMarker.set(file.key, file.size, { noDisposeOnSet: true }); } } // handle communication between worker and master. if (enableWorkerMode) { // rjc = rh-js-cache if (cluster.isMaster) { cluster.on('fork', worker => { worker.on('message', msg => { if (msg.type !== 'rjc') return; if (!msg.key) throw new TypeError('missing key'); if (!msg.value) { // read request if (typeof msg.id !== 'number' || isNaN(msg.id)) throw new TypeError('missing id'); worker.send({ type: 'rjc', key: msg.key, id: msg.id, exists: !!this.lruMarker.get(msg.key) }); } else { // write request this.set(msg.key, msg.value); } }); }); } else { this.lruMarker = null; // make sure we never use this process.on('message', msg => { if (msg.type !== 'rjc') return; if (typeof msg.id !== 'number' || isNaN(msg.id)) throw new TypeError('missing id'); if (typeof msg.exists !== 'boolean') throw new TypeError('missing exists'); // read-request response this.askPromises[msg.id](msg.exists); }); } } } isWorker() { return this.enableWorkerMode && cluster.isWorker; } async askMasterGet(key) { if (!this.isWorker()) { throw new TypeError('worker use only'); } const id = this.askPromiseId++; process.send({ type: 'rjc', id, key }); return await new Promise(resolve => { this.askPromises[id] = exists => { delete this.askPromises[id]; resolve(exists); }; }); } askMasterSet(key, value) { if (!this.isWorker()) { throw new TypeError('worker use only'); } process.send({ type: 'rjc', key, value }); } async get(key) { if (this.isWorker() ? await this.askMasterGet(key) : this.lruMarker?.get(key)) { return fs.readFileSync(path.join(this.diskJsCachePath, key), 'utf-8'); } return undefined; } set(key, value) { if (this.isWorker()) { this.askMasterSet(key, value); } else { this.lruMarker.set(key, value.length); fs.writeFileSync(path.join(this.diskJsCachePath, key), value, 'utf-8'); } } } module.exports = RammerheadJSFileCache; const LRUCache = require('lru-cache'); class RammerheadJSMemCache { constructor(jsCacheSize) { this.lru = new LRUCache({ maxSize: jsCacheSize, sizeCalculation: n => n.length || 1 }); } get(key) { this.lru.get(key); } set(key, value) { this.lru.set(key, value); } } module.exports = RammerheadJSMemCache; /** * @typedef {'disabled'|'debug'|'traffic'|'info'|'warn'|'error'} LoggingLevels */ const LOG_LEVELS = ['disabled', 'debug', 'traffic', 'info', 'warn', 'error']; function defaultGeneratePrefix(level) { return `[${new Date().toISOString()}] [${level.toUpperCase()}] `; } class RammerheadLogging { /** * @param {object} options * @param {LoggingLevels} options.logLevel - logLevel to initialize the logger with * @param {(data: string) => void} options.logger - expects the logger to automatically add a newline, just like what console.log does * @param {*} options.loggerThis - logger will be called with loggerThis binded * @param {(level: LoggingLevels) => string} options.generatePrefix - generates a prefix before every log. set to null to disable */ constructor({ logLevel = 'info', logger = console.log, loggerThis = console, generatePrefix = defaultGeneratePrefix } = {}) { this.logger = logger; this.loggerThis = loggerThis; this.generatePrefix = generatePrefix; /** * @private */ this._logRank = this._getLogRank(logLevel); } get logLevel() { return LOG_LEVELS[this._logRank]; } /** * logger() will be called based on this log level * @param {LoggingLevels} level */ set logLevel(level) { this._logRank = this._getLogRank(level); } callLogger(data) { this.logger.call(this.loggerThis, data); } /** * @param {LoggingLevels} level * @param {string} data */ log(level, data) { const rank = this._getLogRank(level); // the higher the log level, the more important it is. // ensure it's not disabled if (rank && this._logRank <= rank) { this.callLogger((this.generatePrefix ? this.generatePrefix(level) : '') + data); } } debug = (data) => this.log('debug', data); traffic = (data) => this.log('traffic', data); info = (data) => this.log('info', data); warn = (data) => this.log('warn', data); error = (data) => this.log('error', data); /** * @private * @param {LoggingLevels} level * @returns {number} */ _getLogRank(level) { const index = LOG_LEVELS.indexOf(level); if (index === -1) { throw new TypeError(`Invalid log level '${level}'. Valid log levels: ${LOG_LEVELS.join(', ')}`); } return index; } } module.exports = RammerheadLogging; const RammerheadLogging = require('./RammerheadLogging'); const RammerheadSession = require('./RammerheadSession'); const RammerheadSessionAbstractStore = require('./RammerheadSessionAbstractStore'); class RammerheadSessionMemoryStore extends RammerheadSessionAbstractStore { /** * @param {object} options * @param {RammerheadLogging|undefined} options.logger * @param {number|null} options.staleTimeout - if inactivity goes beyond this, then the session is deleted. null to disable * @param {number|null} options.maxToLive - if now - createdAt surpasses maxToLive, then the session is deleted. null to disable * @param {number} options.cleanupInterval - every cleanupInterval ms will run a cleanup check */ constructor({ logger = new RammerheadLogging({ logLevel: 'disabled' }), staleTimeout = 1000 * 60 * 30, // 30 minutes maxToLive = 1000 * 60 * 60 * 4, // 4 hours cleanupInterval = 1000 * 60 * 1 // 1 minute } = {}) { super(); this.logger = logger; this.mapStore = new Map(); setInterval(() => this._cleanupRun(staleTimeout, maxToLive), cleanupInterval).unref(); } /** * @returns {string[]} - list of session ids in store */ keys() { return Array.from(this.mapStore.keys()); } /** * @param {string} id * @returns {boolean} */ has(id) { const exists = this.mapStore.has(id); this.logger.debug(`(MemoryStore.has) ${id} ${exists}`); return exists; } /** * @param {string} id * @param {boolean} updateActiveTimestamp * @returns {RammerheadSession|undefined} */ get(id, updateActiveTimestamp = true) { if (!this.has(id)) return; this.logger.debug(`(MemoryStore.get) ${id} ${updateActiveTimestamp}`); const session = this.mapStore.get(id); if (updateActiveTimestamp) session.updateLastUsed(); return session; } /** * @param {string} id * @returns {RammerheadSession} */ add(id) { if (this.has(id)) throw new Error('the following session already exists: ' + id); this.logger.debug(`(MemoryStore.add) ${id}`); const session = new RammerheadSession({ id }); this.mapStore.set(id, session); return session; } /** * @param {string} id * @returns {boolean} - returns true when a delete operation is performed */ delete(id) { return this.mapStore.delete(id); } /** * @param {string} id * @param {string} serializedSession */ addSerializedSession(id, serializedSession) { this.logger.debug(`(MemoryStore.addSerializedSession) adding serialized session id ${id} to store`); const session = RammerheadSession.DeserializeSession(id, serializedSession); session.updateLastUsed(); this.mapStore.set(id, session); this.logger.debug(`(FileCache.addSerializedSession) added ${id}`); } /** * @private * @param {number|null} staleTimeout * @param {number|null} maxToLive */ _cleanupRun(staleTimeout, maxToLive) { this.logger.debug(`(MemoryStore._cleanupRun) cleanup run. Need to go through ${this.mapStore.size} sessions`); const now = Date.now(); for (const [sessionId, session] of this.mapStore) { if ( (staleTimeout && now - session.lastUsed > staleTimeout) || (maxToLive && now - session.createdAt > maxToLive) ) { this.mapStore.delete(sessionId); this.logger.debug(`(MemoryStore._cleanupRun) delete ${sessionId}`); } } this.logger.debug('(MemoryStore._cleanupRun) finished cleanup run'); } } module.exports = RammerheadSessionMemoryStore; const http = require('http'); const https = require('https'); const stream = require('stream'); const fs = require('fs'); const path = require('path'); const { getPathname } = require('testcafe-hammerhead/lib/utils/url'); const { Proxy } = require('testcafe-hammerhead'); const WebSocket = require('ws'); const httpResponse = require('../util/httpResponse'); const streamToString = require('../util/streamToString'); const URLPath = require('../util/URLPath'); const RammerheadLogging = require('../classes/RammerheadLogging'); const RammerheadJSMemCache = require('./RammerheadJSMemCache.js'); require('../util/fixCorsHeader'); require('../util/fixCorsMissingOriginHeader.js'); require('../util/fixWebsocket'); require('../util/addMoreErrorGuards'); require('../util/addUrlShuffling'); require('../util/patchAsyncResourceProcessor'); let addJSDiskCache = function (jsCache) { require('../util/addJSDiskCache')(jsCache); // modification only works once addJSDiskCache = () => { }; }; /** * taken directly from * https://github.com/DevExpress/testcafe-hammerhead/blob/47f8b6e370c37f2112fd7f56a3d493fbfcd7ec99/src/typings/proxy.d.ts#L1 * @typedef {object} ServerInfo * @property {string} hostname * @property {number} port * @property {number} crossDomainPort * @property {string} protocol * @property {string} domain * @property {boolean} cacheRequests * @property {any?} wss */ /** * taken directly from * https://github.com/DevExpress/testcafe-hammerhead/blob/47f8b6e370c37f2112fd7f56a3d493fbfcd7ec99/src/typings/proxy.d.ts#L39 * @typedef {object} ProxyOptions * @property {string} hostname * @property {number} port1 * @property {number} port2 * @property {object?} ssl * @property {boolean?} developmentMode * @property {boolean?} cache * @property {boolean?} disableHttp2 * @property {boolean?} disableCrossDomain * @property {boolean?} nativeAutomation */ /** * @typedef {object} RammerheadServerInfo * @property {string} hostname * @property {number} port * @property {'https:'|'http:'} protocol */ /** * @private * @typedef {import('./RammerheadSession')} RammerheadSession */ /** * wrapper for hammerhead's Proxy */ class RammerheadProxy extends Proxy { /** * * @param {object} options * @param {RammerheadLogging|undefined} options.logger * @param {(req: http.IncomingMessage) => string} options.loggerGetIP - use custom logic to get IP, either from headers or directly * @param {string} options.bindingAddress - hostname for proxy to bind to * @param {number} options.port - port for proxy to listen to * @param {number|null} options.crossDomainPort - crossDomain port to simulate cross origin requests. set to null * to disable using this. highly not recommended to disable this because it breaks sites that check for the origin header * @param {boolean} options.dontListen - avoid calling http.listen() if you need to use sticky-session to load balance * @param {http.ServerOptions} options.ssl - set to null to disable ssl * @param {(req: http.IncomingMessage) => RammerheadServerInfo} options.getServerInfo - force hammerhead to rewrite using specified * server info (server info includes hostname, port, and protocol). Useful for a reverse proxy setup like nginx where you * need to rewrite the hostname/port/protocol * @param {boolean} options.disableLocalStorageSync - disables localStorage syncing (default: false) * @param {import('../classes/RammerheadJSAbstractCache.js')} options.jsCache - js cache class. (default: memory class 50mb) * @param {boolean} options.disableHttp2 */ constructor({ loggerGetIP = (req) => req.socket.remoteAddress, logger = new RammerheadLogging({ logLevel: 'disabled' }), bindingAddress = '127.0.0.1', port = 8080, crossDomainPort = 8081, dontListen = false, ssl = null, getServerInfo = (req) => { const { hostname, port } = new URL('http://' + req.headers.host); return { hostname, port, protocol: req.socket.encrypted ? 'https:' : 'http:' }; }, disableLocalStorageSync = false, jsCache = new RammerheadJSMemCache(50 * 1024 * 1024), disableHttp2 = false } = {}) { // as of testcafe-hammerhead version 31.6.2, they put the code for starting the server in a separate "start()" // method. due to the proxy focused nature of rammerhead, and backwards-compatibility, there won't be a need for // start() super({ staticContentCaching: true }); if (!crossDomainPort) { const httpOrHttps = ssl ? https : http; const proxyHttpOrHttps = http; const originalProxyCreateServer = proxyHttpOrHttps.createServer; const originalCreateServer = httpOrHttps.createServer; // handle recursion case if proxyHttpOrHttps and httpOrHttps are the same let onlyOneHttpServer = null; // a hack to force testcafe-hammerhead's proxy library into using only one http port. // a downside to using only one proxy server is that crossdomain requests // will not be simulated correctly proxyHttpOrHttps.createServer = function (...args) { const emptyFunc = () => { }; if (onlyOneHttpServer) { // createServer for server1 already called. now we return a mock http server for server2 return { on: emptyFunc, listen: emptyFunc, close: emptyFunc }; } if (args.length !== 2) throw new Error('unexpected argument length coming from hammerhead'); return (onlyOneHttpServer = originalCreateServer(...args)); }; // now, we force the server to listen to a specific port and a binding address, regardless of what // hammerhead server.listen(anything) const originalListen = http.Server.prototype.listen; http.Server.prototype.listen = function (_proxyPort) { if (dontListen) return; originalListen.call(this, port, bindingAddress); }; // actual proxy initialization // the values don't matter (except for developmentMode), since we'll be rewriting serverInfo anyway super.start({ hostname: 'hostname', port1: 'port1', port2: 'port2', ssl, developmentMode: true }); // restore hooked functions to their original state proxyHttpOrHttps.createServer = originalProxyCreateServer; http.Server.prototype.listen = originalListen; } else { // just initialize the proxy as usual, since we don't need to do hacky stuff like the above. // we still need to make sure the proxy binds to the correct address though const originalListen = http.Server.prototype.listen; http.Server.prototype.listen = function (portArg) { if (dontListen) return; originalListen.call(this, portArg, bindingAddress); }; super.start({ hostname: 'doesntmatter', port1: port, port2: crossDomainPort }); this.crossDomainPort = crossDomainPort; http.Server.prototype.listen = originalListen; } this._setupRammerheadServiceRoutes(); this._setupLocalStorageServiceRoutes(disableLocalStorageSync); this.onRequestPipeline = []; this.onUpgradePipeline = []; this.websocketRoutes = []; this.rewriteServerHeaders = { 'permissions-policy': (headerValue) => headerValue && headerValue.replace(/sync-xhr/g, 'sync-yes'), 'feature-policy': (headerValue) => headerValue && headerValue.replace(/sync-xhr/g, 'sync-yes'), 'referrer-policy': () => 'no-referrer-when-downgrade', 'report-to': () => undefined, 'cross-origin-embedder-policy': () => undefined }; this.getServerInfo = getServerInfo; this.serverInfo1 = null; // make sure no one uses these serverInfo this.serverInfo2 = null; this.loggerGetIP = loggerGetIP; this.logger = logger; // this.disableHttp2 = disableHttp2; global.rhDisableHttp2 = disableHttp2; addJSDiskCache(jsCache); } start() { throw new TypeError('rammerhead does not need a start(). server will automatically start when constructor is initialized.'); } // add WS routing /** * since we have .GET and .POST, why not add in a .WS also * @param {string|RegExp} route - can be '/route/to/things' or /^\\/route\\/(this)|(that)\\/things$/ * @param {(ws: WebSocket, req: http.IncomingMessage) => WebSocket} handler - ws is the connection between the client and the server * @param {object} websocketOptions - read https://www.npmjs.com/package/ws for a list of Websocket.Server options. Note that * the { noServer: true } will always be applied * @returns {WebSocket.Server} */ WS(route, handler, websocketOptions = {}) { if (this.checkIsRoute(route)) { throw new TypeError('WS route already exists'); } const wsServer = new WebSocket.Server({ ...websocketOptions, noServer: true }); this.websocketRoutes.push({ route, handler, wsServer }); return wsServer; } unregisterWS(route) { if (!this.getWSRoute(route, true)) { throw new TypeError('websocket route does not exist'); } } /** * @param {string} path * @returns {{ route: string|RegExp, handler: (ws: WebSocket, req: http.IncomingMessage) => WebSocket, wsServer: WebSocket.Server}|null} */ getWSRoute(path, doDelete = false) { for (let i = 0; i < this.websocketRoutes.length; i++) { if ( (typeof this.websocketRoutes[i].route === 'string' && this.websocketRoutes[i].route === path) || (this.websocketRoutes[i] instanceof RegExp && this.websocketRoutes[i].route.test(path)) ) { const route = this.websocketRoutes[i]; if (doDelete) { this.websocketRoutes.splice(i, 1); i--; } return route; } } return null; } /** * @private */ _WSRouteHandler(req, socket, head) { const route = this.getWSRoute(req.url); if (route) { // RH stands for rammerhead. RHROUTE is a custom implementation by rammerhead that is // unrelated to hammerhead this.logger.traffic(`WSROUTE UPGRADE ${this.loggerGetIP(req)} ${req.url}`); route.wsServer.handleUpgrade(req, socket, head, (client, req) => { this.logger.traffic(`WSROUTE OPEN ${this.loggerGetIP(req)} ${req.url}`); client.once('close', () => { this.logger.traffic(`WSROUTE CLOSE ${this.loggerGetIP(req)} ${req.url}`); }); route.handler(client, req); }); return true; } } // manage pipelines // /** * @param {(req: http.IncomingMessage, * res: http.ServerResponse, * serverInfo: ServerInfo, * isRoute: boolean, * isWebsocket: boolean) => Promise} onRequest - return true to terminate handoff to proxy. * There is an isWebsocket even though there is an onUpgrade pipeline already. This is because hammerhead * processes the onUpgrade and then passes it directly to onRequest, but without the "head" Buffer argument. * The onUpgrade pipeline is to solve that lack of the "head" argument issue in case one needs it. * @param {boolean} beginning - whether to add it to the beginning of the pipeline */ addToOnRequestPipeline(onRequest, beginning = false) { if (beginning) { this.onRequestPipeline.push(onRequest); } else { this.onRequestPipeline.unshift(onRequest); } } /** * @param {(req: http.IncomingMessage, * socket: stream.Duplex, * head: Buffer, * serverInfo: ServerInfo, * isRoute: boolean) => Promise} onUpgrade - return true to terminate handoff to proxy * @param {boolean} beginning - whether to add it to the beginning of the pipeline */ addToOnUpgradePipeline(onUpgrade, beginning = false) { if (beginning) { this.onUpgradePipeline.push(onUpgrade); } else { this.onUpgradePipeline.unshift(onUpgrade); } } // override hammerhead's proxy functions to use the pipeline // checkIsRoute(req) { if (req instanceof RegExp) { return !!this.getWSRoute(req); } // code modified from // https://github.com/DevExpress/testcafe-hammerhead/blob/47f8b6e370c37f2112fd7f56a3d493fbfcd7ec99/src/proxy/router.ts#L104 const routerQuery = `${req.method} ${getPathname(req.url || '')}`; const route = this.routes.get(routerQuery); if (route) { return true; } for (const routeWithParams of this.routesWithParams) { const routeMatch = routerQuery.match(routeWithParams.re); if (routeMatch) { return true; } } return !!this.getWSRoute(req.url); } /** * @param {http.IncomingMessage} req * @param {http.ServerResponse} res * @param {ServerInfo} serverInfo */ async _onRequest(req, res, serverInfo) { serverInfo = this._rewriteServerInfo(req); const isWebsocket = res instanceof stream.Duplex; if (!isWebsocket) { // strip server headers const originalWriteHead = res.writeHead; const self = this; res.writeHead = function (statusCode, statusMessage, headers) { if (!headers) { headers = statusMessage; statusMessage = undefined; } if (headers) { const alreadyRewrittenHeaders = []; if (Array.isArray(headers)) { // [content-type, text/html, headerKey, headerValue, ...] for (let i = 0; i < headers.length - 1; i += 2) { const header = headers[i].toLowerCase(); if (header in self.rewriteServerHeaders) { alreadyRewrittenHeaders.push(header); headers[i + 1] = self.rewriteServerHeaders[header] && self.rewriteServerHeaders[header](headers[i + 1]); if (!headers[i + 1]) { headers.splice(i, 2); i -= 2; } } } for (const header in self.rewriteServerHeaders) { if (alreadyRewrittenHeaders.includes(header)) continue; // if user wants to add headers, they can do that here const value = self.rewriteServerHeaders[header] && self.rewriteServerHeaders[header](); if (value) { headers.push(header, value); } } } else { for (const header in headers) { if (header in self.rewriteServerHeaders) { alreadyRewrittenHeaders.push(header); headers[header] = self.rewriteServerHeaders[header] && self.rewriteServerHeaders[header](); if (!headers[header]) { delete headers[header]; } } } for (const header in self.rewriteServerHeaders) { if (alreadyRewrittenHeaders.includes(header)) continue; const value = self.rewriteServerHeaders[header] && self.rewriteServerHeaders[header](); if (value) { headers[header] = value; } } } } if (statusMessage) { originalWriteHead.call(this, statusCode, statusMessage, headers); } else { originalWriteHead.call(this, statusCode, headers); } }; } const isRoute = this.checkIsRoute(req); const ip = this.loggerGetIP(req); this.logger.traffic(`${isRoute ? 'ROUTE ' : ''}${ip} ${req.url}`); for (const handler of this.onRequestPipeline) { if ((await handler.call(this, req, res, serverInfo, isRoute, isWebsocket)) === true) { return; } } // hammerhead's routing does not support websockets. Allowing it // will result in an error thrown if (isRoute && isWebsocket) { httpResponse.badRequest(this.logger, req, res, ip, 'Rejected unsupported websocket request'); return; } super._onRequest(req, res, serverInfo); } /** * @param {http.IncomingMessage} req * @param {stream.Duplex} socket * @param {Buffer} head * @param {ServerInfo} serverInfo */ async _onUpgradeRequest(req, socket, head, serverInfo) { serverInfo = this._rewriteServerInfo(req); for (const handler of this.onUpgradePipeline) { const isRoute = this.checkIsRoute(req); if ((await handler.call(this, req, socket, head, serverInfo, isRoute)) === true) { return; } } if (this._WSRouteHandler(req, socket, head)) return; super._onUpgradeRequest(req, socket, head, serverInfo); } /** * @private * @param {http.IncomingMessage} req * @returns {ServerInfo} */ _rewriteServerInfo(req) { const serverInfo = this.getServerInfo(req); return { hostname: serverInfo.hostname, port: serverInfo.port, crossDomainPort: serverInfo.crossDomainPort || this.crossDomainPort || serverInfo.port, protocol: serverInfo.protocol, domain: `${serverInfo.protocol}//${serverInfo.hostname}:${serverInfo.port}`, cacheRequests: false }; } /** * @private */ _setupRammerheadServiceRoutes() { this.GET('/rammerhead.js', { content: fs.readFileSync( path.join(__dirname, '../client/rammerhead' + (process.env.DEVELOPMENT ? '.js' : '.min.js')) ), contentType: 'application/x-javascript' }); this.GET('/api/shuffleDict', (req, res) => { const { id } = new URLPath(req.url).getParams(); if (!id || !this.openSessions.has(id)) { return httpResponse.badRequest(this.logger, req, res, this.loggerGetIP(req), 'Invalid session id'); } res.end(JSON.stringify(this.openSessions.get(id).shuffleDict) || ''); }); } /** * @private */ _setupLocalStorageServiceRoutes(disableSync) { this.POST('/syncLocalStorage', async (req, res) => { if (disableSync) { res.writeHead(404); res.end('server disabled localStorage sync'); return; } const badRequest = (msg) => httpResponse.badRequest(this.logger, req, res, this.loggerGetIP(req), msg); const respondJson = (obj) => res.end(JSON.stringify(obj)); const { sessionId, origin } = new URLPath(req.url).getParams(); if (!sessionId || !this.openSessions.has(sessionId)) { return badRequest('Invalid session id'); } if (!origin) { return badRequest('Invalid origin'); } let parsed; try { parsed = JSON.parse(await streamToString(req)); } catch (e) { return badRequest('bad client body'); } const now = Date.now(); const session = this.openSessions.get(sessionId, false); if (!session.data.localStorage) session.data.localStorage = {}; switch (parsed.type) { case 'sync': if (parsed.fetch) { // client is syncing for the first time if (!session.data.localStorage[origin]) { // server does not have any data on origin, so create an empty record // and send an empty object back session.data.localStorage[origin] = { data: {}, timestamp: now }; return respondJson({ timestamp: now, data: {} }); } else { // server does have data, so send data back return respondJson({ timestamp: session.data.localStorage[origin].timestamp, data: session.data.localStorage[origin].data }); } } else { // sync server and client localStorage parsed.timestamp = parseInt(parsed.timestamp); if (isNaN(parsed.timestamp)) return badRequest('must specify valid timestamp'); if (parsed.timestamp > now) return badRequest('cannot specify timestamp in the future'); if (!parsed.data || typeof parsed.data !== 'object') return badRequest('data must be an object'); for (const prop in parsed.data) { if (typeof parsed.data[prop] !== 'string') { return badRequest('data[prop] must be a string'); } } if (!session.data.localStorage[origin]) { // server does not have data, so use client's session.data.localStorage[origin] = { data: parsed.data, timestamp: now }; return respondJson({}); } else if (session.data.localStorage[origin].timestamp <= parsed.timestamp) { // server data is either the same as client or outdated, but we // sync even if timestamps are the same in case the client changed the localStorage // without updating session.data.localStorage[origin].data = parsed.data; session.data.localStorage[origin].timestamp = parsed.timestamp; return respondJson({}); } else { // client data is stale return respondJson({ timestamp: session.data.localStorage[origin].timestamp, data: session.data.localStorage[origin].data }); } } case 'update': if (!session.data.localStorage[origin]) return badRequest('must perform sync first on a new origin'); if (!parsed.updateData || typeof parsed.updateData !== 'object') return badRequest('updateData must be an object'); for (const prop in parsed.updateData) { if (!parsed.updateData[prop] || typeof parsed.updateData[prop] !== 'string') return badRequest('updateData[prop] must be a non-empty string'); } for (const prop in parsed.updateData) { session.data.localStorage[origin].data[prop] = parsed.updateData[prop]; } session.data.localStorage[origin].timestamp = now; return respondJson({ timestamp: now }); default: return badRequest('unknown type ' + parsed.type); } }); } openSession() { throw new TypeError('unimplemented. please use a RammerheadSessionStore and use their .add() method'); } close() { super.close(); this.openSessions.close(); } /** * @param {string} route * @param {StaticContent | (req: http.IncomingMessage, res: http.ServerResponse) => void} handler */ GET(route, handler) { if (route === '/hammerhead.js') { handler.content = fs.readFileSync( path.join(__dirname, '../client/hammerhead' + (process.env.DEVELOPMENT ? '.js' : '.min.js')) ); } if (route === '/worker-hammerhead.js') { handler.content = fs.readFileSync( path.join(__dirname, '../client/worker-hammerhead' + (process.env.DEVELOPMENT ? '.js' : '.min.js')) ); } if (route === '/transport-worker.js') { handler.content = fs.readFileSync( path.join(__dirname, '../client/transport-worker' + (process.env.DEVELOPMENT ? '.js' : '.min.js')) ); } super.GET(route, handler); } // the following is to fix hamerhead's typescript definitions /** * @param {string} route * @param {StaticContent | (req: http.IncomingMessage, res: http.ServerResponse) => void} handler */ POST(route, handler) { super.POST(route, handler); } } module.exports = RammerheadProxy; const { Session } = require('testcafe-hammerhead'); const UploadStorage = require('testcafe-hammerhead/lib/upload/storage'); const generateId = require('../util/generateId'); const StrShuffler = require('../util/StrShuffler'); // disable UploadStorage, a testcafe testing feature we do not need const emptyFunc = () => {}; UploadStorage.prototype.copy = emptyFunc; UploadStorage.prototype.get = emptyFunc; UploadStorage.prototype.store = emptyFunc; /** * wrapper for initializing Session with saving capabilities */ class RammerheadSession extends Session { data = {}; createdAt = Date.now(); lastUsed = Date.now(); /** * @param {object} options * @param {string} options.id * @param {boolean} options.dontConnectToData - used when we want to connect to data later (or simply don't want to) * @param {boolean} options.disableShuffling * @param {string[]} options.prependScripts */ constructor({ id = generateId(), dontConnectToData = false, disableShuffling = false, prependScripts = [] } = {}) { super(['blah/blah'], { allowMultipleWindows: true, disablePageCaching: false }); // necessary abstract methods for Session this.getIframePayloadScript = async () => ''; this.getPayloadScript = async () => ''; this.getAuthCredentials = () => ({}); this.handleFileDownload = () => void 0; this.handlePageError = () => void 0; this.handleAttachment = () => void 0; // this.handlePageError = (ctx, err) => { // console.error(ctx.req.url); // console.error(err); // }; // intellisense // /** * @type {{ host: string, hostname: string, bypassRules?: string[]; port?: string; proxyAuth?: string, authHeader?: string } | null} */ this.externalProxySettings = null; /** * @type {{ host: string, hostname: string, bypassRules?: string[]; port?: string; proxyAuth?: string, authHeader?: string } | null} */ this.overrideExternalProxySettings = null; // disable http2. error handling from http2 proxy client to non-http2 user is too complicated to handle // (status code 0, for example, will crash rammerhead) // UPDATE: so apparently, some websites *really* want you to make an http2 connection to them before you connect // to their websocket endpoint. // for example, web.whatsapp.com websockets throws a 400 error even though the request is identical, with/without http2. // so now, we undo the change we made that initially was to avoid the whole error mess and a potential source of memory leak. // (also we got the "last resort" error handling in addMoreErrorGuards.js so everything is fine) // this.isHttp2Disabled = () => true; if (global.rhDisableHttp2) { // globally set from RammerheadProxy.js this.disableHttp2(); } this.injectable.scripts.push(...prependScripts); this.injectable.scripts.push('/rammerhead.js'); this.id = id; this.shuffleDict = disableShuffling ? null : StrShuffler.generateDictionary(); if (!dontConnectToData) { this.connectHammerheadToData(); } } /** * @param {boolean} dontCookie - set this to true if the store is using a more reliable approach to * saving the cookies (like in serializeSession) */ connectHammerheadToData(dontCookie = false) { this._connectObjectToHook(this, 'createdAt'); this._connectObjectToHook(this, 'lastUsed'); this._connectObjectToHook(this, 'injectable'); this._connectObjectToHook(this, 'externalProxySettings', 'externalProxySettings', () => { return this.overrideExternalProxySettings; }); this._connectObjectToHook(this, 'shuffleDict'); if (!dontCookie) this._connectObjectToHook(this.cookies._cookieJar.store, 'idx', 'cookies'); } updateLastUsed() { this.lastUsed = Date.now(); } serializeSession() { return JSON.stringify({ data: this.data, serializedCookieJar: this.cookies.serializeJar() }); } // hook system and serializing are for two different store systems static DeserializeSession(id, serializedSession) { const parsed = JSON.parse(serializedSession); if (!parsed.data) throw new Error('expected serializedSession to contain data object'); if (!parsed.serializedCookieJar) throw new Error('expected serializedSession to contain serializedCookieJar object'); const session = new RammerheadSession({ id, dontConnectToData: true }); session.data = parsed.data; session.connectHammerheadToData(true); session.cookies.setJar(parsed.serializedCookieJar); return session; } hasRequestEventListeners() { // force forceProxySrcForImage to be true // see https://github.com/DevExpress/testcafe-hammerhead/blob/47f8b6e370c37f2112fd7f56a3d493fbfcd7ec99/src/session/index.ts#L166 return true; } /** * @private */ _connectObjectToHook(obj, prop, dataProp = prop, getOverride = (_data) => {}) { const originalValue = obj[prop]; Object.defineProperty(obj, prop, { get: () => getOverride(this.data[dataProp]) || this.data[dataProp], set: (value) => { this.data[dataProp] = value; } }); if (!(dataProp in this.data)) { this.data[dataProp] = originalValue; } } } module.exports = RammerheadSession; /* eslint-disable no-unused-vars */ /** * @private * @typedef {import("./RammerheadSession")} RammerheadSession */ /** * this is the minimum in order to have a fully working versatile session store. Though it is an abstract * class and should be treated as such, it includes default functions deemed necessary that are not * particular to different implementations * @abstract */ class RammerheadSessionAbstractStore { constructor() { if (this.constructor === RammerheadSessionAbstractStore) { throw new Error('abstract classes cannot be instantiated'); } } /** * * @param {import('./RammerheadProxy')} proxy - this will overwrite proxy.openSessions with this class instance and * adds a request handler that calls loadSessionToMemory * @param {boolean} removeExistingSessions - whether to remove all sessions before overwriting proxy.openSessions */ attachToProxy(proxy, removeExistingSessions = true) { if (proxy.openSessions === this) throw new TypeError('already attached to proxy'); if (removeExistingSessions) { for (const [, session] of proxy.openSessions.entries()) { proxy.closeSession(session); } } proxy.openSessions = this; } /** * @private */ _mustImplement() { throw new Error('must be implemented'); } /** * @abstract * @returns {string[]} - list of session ids in store */ keys() { this._mustImplement(); } /** * @abstract * @param {string} id * @returns {boolean} */ has(id) { this._mustImplement(); } /** * @abstract * @param {string} id * @param {boolean} updateActiveTimestamp * @returns {RammerheadSession|undefined} */ get(id, updateActiveTimestamp = true) { this._mustImplement(); } /** * the implemented method here will use the dataOperation option in RammerheadSession however they * see fit * @abstract * @param {string} id * @returns {RammerheadSession} */ add(id) { this._mustImplement(); } /** * @abstract * @param {string} id * @returns {boolean} - returns true when a delete operation is performed */ delete(id) { this._mustImplement(); } /** * @abstract * @param {string} id * @param {string} serializedSession */ addSerializedSession(id, serializedSession) { this._mustImplement(); } /** * optional abstract method */ close() {} } module.exports = RammerheadSessionAbstractStore; const fs = require('fs'); const path = require('path'); const RammerheadSessionAbstractStore = require('./RammerheadSessionAbstractStore'); const RammerheadSession = require('./RammerheadSession'); const RammerheadLogging = require('../classes/RammerheadLogging'); // rh = rammerhead. extra f to distinguish between rhsession (folder) and rhfsession (file) const sessionFileExtension = '.rhfsession'; class RammerheadSessionFileCache extends RammerheadSessionAbstractStore { /** * * @param {object} options * @param {string} options.saveDirectory - all cacheTimeouted sessions will be saved in this folder * to avoid storing all the sessions in the memory. * @param {RammerheadLogging|undefined} options.logger * @param {number} options.cacheTimeout - timeout before saving cache to disk and deleting it from the cache * @param {number} options.cacheCheckInterval * @param {boolean} options.deleteUnused - (default: true) if set to true, it deletes unused sessions when saving cache to disk * @param {boolean} options.deleteCorruptedSessions - (default: true) if set to true, auto-deletes session files that * give a parse error (happens when nodejs exits abruptly while serializing session to disk) * @param {object|null} options.staleCleanupOptions - set to null to disable cleaning up stale sessions * @param {number|null} options.staleCleanupOptions.staleTimeout - stale sessions that are inside saveDirectory that go over * this timeout will be deleted. Set to null to disable. * @param {number|null} options.staleCleanupOptions.maxToLive - any created sessions that are older than this will be deleted no matter the usage. * Set to null to disable. * @param {number} options.staleCleanupOptions.staleCheckInterval */ constructor({ saveDirectory = path.join(__dirname, '../../sessions'), logger = new RammerheadLogging({ logLevel: 'disabled' }), cacheTimeout = 1000 * 60 * 20, // 20 minutes cacheCheckInterval = 1000 * 60 * 10, // 10 minutes, deleteUnused = true, deleteCorruptedSessions = true, staleCleanupOptions = { staleTimeout: 1000 * 60 * 60 * 24 * 1, // 1 day maxToLive: 1000 * 60 * 60 * 24 * 4, // four days staleCheckInterval: 1000 * 60 * 60 * 1 // 1 hour } } = {}) { super(); this.saveDirectory = saveDirectory; this.logger = logger; this.deleteUnused = deleteUnused; this.cacheTimeout = cacheTimeout; this.deleteCorruptedSessions = deleteCorruptedSessions; /** * @type {Map.} */ this.cachedSessions = new Map(); setInterval(() => this._saveCacheToDisk(), cacheCheckInterval).unref(); if (staleCleanupOptions) { this._removeStaleSessions(staleCleanupOptions.staleTimeout, staleCleanupOptions.maxToLive); setInterval( () => this._removeStaleSessions(staleCleanupOptions.staleTimeout, staleCleanupOptions.maxToLive), staleCleanupOptions.staleCheckInterval ).unref(); } } keysStore() { return fs .readdirSync(this.saveDirectory) .filter((file) => file.endsWith(sessionFileExtension)) .map((file) => file.slice(0, -sessionFileExtension.length)); } /** * @returns {string[]} - list of session ids in store */ keys() { let arr = this.keysStore(); for (const id of this.cachedSessions.keys()) { if (!arr.includes(id)) arr.push(id); } return arr; } /** * @param {string} id * @returns {boolean} */ has(id) { return this.cachedSessions.has(id) || fs.existsSync(this._getSessionFilePath(id)); } /** * @param {string} id * @param {boolean} updateActiveTimestamp * @returns {RammerheadSession|undefined} */ get(id, updateActiveTimestamp = true, cacheToMemory = true) { if (!this.has(id)) { this.logger.debug(`(FileCache.get) ${id} does not exist`); return; } this.logger.debug(`(FileCache.get) ${id}`); if (this.cachedSessions.has(id)) { this.logger.debug(`(FileCache.get) returning memory cached session ${id}`); return this.cachedSessions.get(id); } let session; try { session = RammerheadSession.DeserializeSession(id, fs.readFileSync(this._getSessionFilePath(id))); } catch (e) { if (e.name === 'SyntaxError' && e.message.includes('JSON')) { this.logger.warn(`(FileCache.get) ${id} bad JSON`); if (this.deleteCorruptedSessions) { this.delete(id); this.logger.warn(`(FileCache.get) ${id} deleted because of bad JSON`); } return; } } if (updateActiveTimestamp) { this.logger.debug(`(FileCache.get) ${id} update active timestamp`); session.updateLastUsed(); } if (cacheToMemory) { this.cachedSessions.set(id, session); this.logger.debug(`(FileCache.get) saved ${id} into cache memory`); } return session; } /** * @param {string} id * @returns {RammerheadSession} */ add(id) { if (this.has(id)) throw new Error(`session ${id} already exists`); fs.writeFileSync(this._getSessionFilePath(id), new RammerheadSession().serializeSession()); this.logger.debug(`FileCache.add ${id}`); return this.get(id); } /** * @param {string} id * @returns {boolean} - returns true when a delete operation is performed */ delete(id) { this.logger.debug(`(FileCache.delete) deleting ${id}`); if (this.has(id)) { fs.unlinkSync(this._getSessionFilePath(id)); this.cachedSessions.delete(id); this.logger.debug(`(FileCache.delete) deleted ${id}`); return true; } this.logger.debug(`(FileCache.delete) ${id} does not exist`); return false; } /** * @param {string} id * @param {string} serializedSession */ addSerializedSession(id, serializedSession) { this.logger.debug(`(FileCache.addSerializedSession) adding serialized session id ${id} to store`); const session = RammerheadSession.DeserializeSession(id, serializedSession); fs.writeFileSync(this._getSessionFilePath(id), session.serializeSession()); this.logger.debug(`(FileCache.addSerializedSession) added ${id} to cache`); } close() { this.logger.debug(`(FileCache.close) calling _saveCacheToDisk`); this._saveCacheToDisk(true); } /** * @private * @param {string} id * @returns {string} - generated file path to session */ _getSessionFilePath(id) { return path.join(this.saveDirectory, id.replace(/\/|\\/g, '') + sessionFileExtension); } /** * @private * @param {number|null} staleTimeout * @param {number|null} maxToLive */ _removeStaleSessions(staleTimeout, maxToLive) { const sessionIds = this.keysStore(); let deleteCount = 0; this.logger.debug(`(FileCache._removeStaleSessions) Need to go through ${sessionIds.length} sessions in store`); const now = Date.now(); for (const id of sessionIds) { const session = this.get(id, false, false); if (!session) { this.logger.debug(`(FileCache._removeStaleSessions) skipping ${id} as .get() returned undefined`); continue; } if ( (staleTimeout && now - session.lastUsed > staleTimeout) || (maxToLive && now - session.createdAt > maxToLive) ) { this.delete(id); deleteCount++; this.logger.debug(`(FileCache._removeStaleSessions) deleted ${id}`); } } this.logger.debug(`(FileCache._removeStaleSessions) Deleted ${deleteCount} sessions from store`); } /** * @private */ _saveCacheToDisk(forceSave) { let deleteCount = 0; this.logger.debug(`(FileCache._saveCacheToDisk) need to go through ${this.cachedSessions.size} sessions`); const now = Date.now(); for (const [sessionId, session] of this.cachedSessions) { if (forceSave || now - session.lastUsed > this.cacheTimeout) { if (session.lastUsed === session.createdAt && this.deleteUnused) { this.cachedSessions.delete(sessionId); deleteCount++; this.logger.debug(`(FileCache._saveCacheToDisk) deleted unused ${sessionId} from memory`); } else { fs.writeFileSync(this._getSessionFilePath(sessionId), session.serializeSession()); this.cachedSessions.delete(sessionId); deleteCount++; this.logger.debug( `(FileCache._saveCacheToDisk) removed ${sessionId} from memory and saved to store` ); } } } this.logger.debug(`(FileCache._saveCacheToDisk) Removed ${deleteCount} sessions from memory`); } } module.exports = RammerheadSessionFileCache; (function () { var hammerhead = window['%hammerhead%']; if (!hammerhead) throw new Error('hammerhead not loaded yet'); if (hammerhead.settings._settings.sessionId) { // task.js already loaded. this will likely never happen though since this file loads before task.js console.warn('unexpected task.js to load before rammerhead.js. url shuffling cannot be used'); main(); } else { // wait for task.js to load hookHammerheadStartOnce(main); // before task.js, we need to add url shuffling addUrlShuffling(); } function main() { fixUrlRewrite(); fixElementGetter(); fixCrossWindowLocalStorage(); delete window.overrideGetProxyUrl; delete window.overrideParseProxyUrl; delete window.overrideIsCrossDomainWindows; // other code if they want to also hook onto hammerhead start // if (window.rammerheadStartListeners) { for (const eachListener of window.rammerheadStartListeners) { try { eachListener(); } catch (e) { console.error(e); } } delete window.rammerheadStartListeners; } // sync localStorage code // // disable if other code wants to implement their own localStorage site wrapper if (window.rammerheadDisableLocalStorageImplementation) { delete window.rammerheadDisableLocalStorageImplementation; return; } // consts var timestampKey = 'rammerhead_synctimestamp'; var updateInterval = 5000; var isSyncing = false; var proxiedLocalStorage = localStorage; var realLocalStorage = proxiedLocalStorage.internal.nativeStorage; var sessionId = hammerhead.settings._settings.sessionId; var origin = window.__get$(window, '__cpLocation').origin; var keyChanges = []; try { syncLocalStorage(); } catch (e) { if (e.message !== 'server wants to disable localStorage syncing') { throw e; } return; } proxiedLocalStorage.addChangeEventListener(function (event) { if (isSyncing) return; if (keyChanges.indexOf(event.key) === -1) keyChanges.push(event.key); }); setInterval(function () { var update = compileUpdate(); if (!update) return; localStorageRequest({ type: 'update', updateData: update }, function (data) { updateTimestamp(data.timestamp); }); keyChanges = []; }, updateInterval); document.addEventListener('visibilitychange', function () { if (document.visibilityState === 'hidden') { var update = compileUpdate(); if (update) { // even though we'll never get the timestamp, it's fine. this way, // the data is safer hammerhead.nativeMethods.sendBeacon.call( window.navigator, getSyncStorageEndpoint(), JSON.stringify({ type: 'update', updateData: update }) ); } } }); function syncLocalStorage() { isSyncing = true; var timestamp = getTimestamp(); var response; if (!timestamp) { // first time syncing response = localStorageRequest({ type: 'sync', fetch: true }); if (response.timestamp) { updateTimestamp(response.timestamp); overwriteLocalStorage(response.data); } } else { // resync response = localStorageRequest({ type: 'sync', timestamp: timestamp, data: proxiedLocalStorage }); if (response.timestamp) { updateTimestamp(response.timestamp); overwriteLocalStorage(response.data); } } isSyncing = false; function overwriteLocalStorage(data) { if (!data || typeof data !== 'object') throw new TypeError('data must be an object'); proxiedLocalStorage.clear(); for (var prop in data) { proxiedLocalStorage[prop] = data[prop]; } } } function updateTimestamp(timestamp) { if (!timestamp) throw new TypeError('timestamp must be defined'); if (isNaN(parseInt(timestamp))) throw new TypeError('timestamp must be a number. received' + timestamp); realLocalStorage[timestampKey] = timestamp; } function getTimestamp() { var rawTimestamp = realLocalStorage[timestampKey]; var timestamp = parseInt(rawTimestamp); if (isNaN(timestamp)) { if (rawTimestamp) { console.warn('invalid timestamp retrieved from storage: ' + rawTimestamp); } return null; } return timestamp; } function getSyncStorageEndpoint() { return ( '/syncLocalStorage?sessionId=' + encodeURIComponent(sessionId) + '&origin=' + encodeURIComponent(origin) ); } function localStorageRequest(data, callback) { if (!data || typeof data !== 'object') throw new TypeError('data must be an object'); var request = hammerhead.createNativeXHR(); // make synchronous if there is no callback request.open('POST', getSyncStorageEndpoint(), !!callback); request.setRequestHeader('content-type', 'application/json'); request.send(JSON.stringify(data)); function check() { if (request.status === 404) { throw new Error('server wants to disable localStorage syncing'); } if (request.status !== 200) throw new Error( 'server sent a non 200 code. got ' + request.status + '. Response: ' + request.responseText ); } if (!callback) { check(); return JSON.parse(request.responseText); } else { request.onload = function () { check(); callback(JSON.parse(request.responseText)); }; } } function compileUpdate() { if (!keyChanges.length) return null; var updates = {}; for (var i = 0; i < keyChanges.length; i++) { updates[keyChanges[i]] = proxiedLocalStorage[keyChanges[i]]; } keyChanges = []; return updates; } } var noShuffling = false; function addUrlShuffling() { const request = new XMLHttpRequest(); const sessionId = (__cpLocation.pathname.slice(1).match(/^[a-z0-9]+/i) || [])[0]; if (!sessionId) { console.warn('cannot get session id from url'); return; } request.open('GET', '/api/shuffleDict?id=' + sessionId, false); request.send(); if (request.status !== 200) { console.warn( `received a non 200 status code while trying to fetch shuffleDict:\nstatus: ${request.status}\nresponse: ${request.responseText}` ); return; } const shuffleDict = JSON.parse(request.responseText); if (!shuffleDict) return; // pasting entire thing here "because lazy" - m28 const mod = (n, m) => ((n % m) + m) % m; const baseDictionary = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~-'; const shuffledIndicator = '_rhs'; const generateDictionary = function () { let str = ''; const split = baseDictionary.split(''); while (split.length > 0) { str += split.splice(Math.floor(Math.random() * split.length), 1)[0]; } return str; }; class StrShuffler { constructor(dictionary = generateDictionary()) { this.dictionary = dictionary; } shuffle(str) { if (str.startsWith(shuffledIndicator)) { return str; } let shuffledStr = ''; for (let i = 0; i < str.length; i++) { const char = str.charAt(i); const idx = baseDictionary.indexOf(char); if (char === '%' && str.length - i >= 3) { shuffledStr += char; shuffledStr += str.charAt(++i); shuffledStr += str.charAt(++i); } else if (idx === -1) { shuffledStr += char; } else { shuffledStr += this.dictionary.charAt(mod(idx + i, baseDictionary.length)); } } return shuffledIndicator + shuffledStr; } unshuffle(str) { if (!str.startsWith(shuffledIndicator)) { return str; } str = str.slice(shuffledIndicator.length); let unshuffledStr = ''; for (let i = 0; i < str.length; i++) { const char = str.charAt(i); const idx = this.dictionary.indexOf(char); if (char === '%' && str.length - i >= 3) { unshuffledStr += char; unshuffledStr += str.charAt(++i); unshuffledStr += str.charAt(++i); } else if (idx === -1) { unshuffledStr += char; } else { unshuffledStr += baseDictionary.charAt(mod(idx - i, baseDictionary.length)); } } return unshuffledStr; } } const replaceUrl = (url, replacer) => { // regex: https://google.com/ sessionid/ url return (url || '').replace(/^((?:[a-z0-9]+:\/\/[^/]+)?(?:\/[^/]+\/))([^]+)/i, function (_, g1, g2) { return g1 + replacer(g2); }); }; const shuffler = new StrShuffler(shuffleDict); // shuffle current url if it isn't already shuffled (unshuffled urls likely come from user input) const oldUrl = __cpLocation.href; const newUrl = replaceUrl(__cpLocation.href, (url) => shuffler.shuffle(url)); if (oldUrl !== newUrl) { history.replaceState(null, null, newUrl); } const getProxyUrl = hammerhead.utils.url.getProxyUrl; const parseProxyUrl = hammerhead.utils.url.parseProxyUrl; hammerhead.utils.url.overrideGetProxyUrl(function (url, opts) { if (noShuffling) { return getProxyUrl(url, opts); } return replaceUrl(getProxyUrl(url, opts), (u) => shuffler.shuffle(u), true); }); hammerhead.utils.url.overrideParseProxyUrl(function (url) { return parseProxyUrl(replaceUrl(url, (u) => shuffler.unshuffle(u), false)); }); // manual hooks // window.overrideGetProxyUrl( (getProxyUrl$1) => function (url, opts) { if (noShuffling) { return getProxyUrl$1(url, opts); } return replaceUrl(getProxyUrl$1(url, opts), (u) => shuffler.shuffle(u), true); } ); window.overrideParseProxyUrl( (parseProxyUrl$1) => function (url) { return parseProxyUrl$1(replaceUrl(url, (u) => shuffler.unshuffle(u), false)); } ); } function fixUrlRewrite() { const port = __cpLocation.port || (__cpLocation.protocol === 'https:' ? '443' : '80'); const getProxyUrl = hammerhead.utils.url.getProxyUrl; hammerhead.utils.url.overrideGetProxyUrl(function (url, opts = {}) { if (!opts.proxyPort) { opts.proxyPort = port; } return getProxyUrl(url, opts); }); window.overrideParseProxyUrl( (parseProxyUrl$1) => function (url) { const parsed = parseProxyUrl$1(url); if (!parsed || !parsed.proxy) return parsed; if (!parsed.proxy.port) { parsed.proxy.port = port; } return parsed; } ); } function fixElementGetter() { const fixList = { HTMLAnchorElement: ['href'], HTMLAreaElement: ['href'], HTMLBaseElement: ['href'], HTMLEmbedElement: ['src'], HTMLFormElement: ['action'], HTMLFrameElement: ['src'], HTMLIFrameElement: ['src'], HTMLImageElement: ['src'], HTMLInputElement: ['src'], HTMLLinkElement: ['href'], HTMLMediaElement: ['src'], HTMLModElement: ['cite'], HTMLObjectElement: ['data'], HTMLQuoteElement: ['cite'], HTMLScriptElement: ['src'], HTMLSourceElement: ['src'], HTMLTrackElement: ['src'] }; const urlRewrite = (url) => (hammerhead.utils.url.parseProxyUrl(url) || {}).destUrl || url; for (const ElementClass in fixList) { for (const attr of fixList[ElementClass]) { if (!window[ElementClass]) { console.warn('unexpected unsupported element class ' + ElementClass); continue; } const desc = Object.getOwnPropertyDescriptor(window[ElementClass].prototype, attr); const originalGet = desc.get; desc.get = function () { return urlRewrite(originalGet.call(this)); }; if (attr === 'action') { const originalSet = desc.set; // don't shuffle form action urls desc.set = function (value) { noShuffling = true; try { var returnVal = originalSet.call(this, value); } catch (e) { noShuffling = false; throw e; } noShuffling = false; return returnVal; }; } Object.defineProperty(window[ElementClass].prototype, attr, desc); } } } function fixCrossWindowLocalStorage() { // completely replace hammerhead's implementation as restore() and save() on every // call is just not viable (mainly memory issues as the garbage collector is sometimes not fast enough) const getLocHost = win => (new URL(hammerhead.utils.url.parseProxyUrl(win.__cpLocation.href).destUrl)).host; const prefix = win => `rammerhead|storage-wrapper|${hammerhead.settings._settings.sessionId}|${ getLocHost(win) }|`; const toRealStorageKey = (key = '', win = window) => prefix(win) + key; const fromRealStorageKey = (key = '', win = window) => { if (!key.startsWith(prefix(win))) return null; return key.slice(prefix.length); }; const replaceStorageInstance = (storageProp, realStorage) => { const reservedProps = ['internal', 'clear', 'key', 'getItem', 'setItem', 'removeItem', 'length']; Object.defineProperty(window, storageProp, { // define a value-based instead of getter-based property, since with this localStorage implementation, // we don't need to rely on sharing a single memory-based storage across frames, unlike hammerhead configurable: true, writable: true, // still use window[storageProp] as basis to allow scripts to access localStorage.internal value: new Proxy(window[storageProp], { get(target, prop, receiver) { if (reservedProps.includes(prop) && prop !== 'length') { return Reflect.get(target, prop, receiver); } else if (prop === 'length') { let len = 0; for (const [key] of Object.entries(realStorage)) { if (fromRealStorageKey(key)) len++; } return len; } else { return realStorage[toRealStorageKey(prop)]; } }, set(_, prop, value) { if (!reservedProps.includes(prop)) { realStorage[toRealStorageKey(prop)] = value; } return true; }, deleteProperty(_, prop) { delete realStorage[toRealStorageKey(prop)]; return true; }, has(target, prop) { return toRealStorageKey(prop) in realStorage || prop in target; }, ownKeys() { const list = []; for (const [key] of Object.entries(realStorage)) { const proxyKey = fromRealStorageKey(key); if (proxyKey && !reservedProps.includes(proxyKey)) list.push(proxyKey); } return list; }, getOwnPropertyDescriptor(_, prop) { return Object.getOwnPropertyDescriptor(realStorage, toRealStorageKey(prop)); }, defineProperty(_, prop, desc) { if (!reservedProps.includes(prop)) { Object.defineProperty(realStorage, toRealStorageKey(prop), desc); } return true; } }) }); }; const rewriteFunction = (prop, newFunc) => { Storage.prototype[prop] = new Proxy(Storage.prototype[prop], { apply(_, thisArg, args) { return newFunc.apply(thisArg, args); } }); }; replaceStorageInstance('localStorage', hammerhead.storages.localStorageProxy.internal.nativeStorage); replaceStorageInstance('sessionStorage', hammerhead.storages.sessionStorageProxy.internal.nativeStorage); rewriteFunction('clear', function () { for (const [key] of Object.entries(this)) { delete this[key]; } }); rewriteFunction('key', function (keyNum) { return (Object.entries(this)[keyNum] || [])[0] || null; }); rewriteFunction('getItem', function (key) { return this.internal.nativeStorage[toRealStorageKey(key, this.internal.ctx)] || null; }); rewriteFunction('setItem', function (key, value) { if (key) { this.internal.nativeStorage[toRealStorageKey(key, this.internal.ctx)] = value; } }); rewriteFunction('removeItem', function (key) { delete this.internal.nativeStorage[toRealStorageKey(key, this.internal.ctx)]; }); } function hookHammerheadStartOnce(callback) { var originalStart = hammerhead.__proto__.start; hammerhead.__proto__.start = function () { originalStart.apply(this, arguments); hammerhead.__proto__.start = originalStart; callback(); }; } })(); const cluster = require('cluster'); if (cluster.isMaster) { require('dotenv-flow').config(); } const exitHook = require('async-exit-hook'); const sticky = require('sticky-session-custom'); const RammerheadProxy = require('../classes/RammerheadProxy'); const addStaticDirToProxy = require('../util/addStaticDirToProxy'); const RammerheadSessionFileCache = require('../classes/RammerheadSessionFileCache'); const config = require('../config'); const setupRoutes = require('./setupRoutes'); const setupPipeline = require('./setupPipeline'); const RammerheadLogging = require('../classes/RammerheadLogging'); const getSessionId = require('../util/getSessionId'); const prefix = config.enableWorkers ? (cluster.isMaster ? '(master) ' : `(${cluster.worker.id}) `) : ''; const logger = new RammerheadLogging({ logLevel: config.logLevel, generatePrefix: (level) => prefix + config.generatePrefix(level) }); const proxyServer = new RammerheadProxy({ logger, loggerGetIP: config.getIP, bindingAddress: config.bindingAddress, port: config.port, crossDomainPort: config.crossDomainPort, dontListen: config.enableWorkers, ssl: config.ssl, getServerInfo: config.getServerInfo, disableLocalStorageSync: config.disableLocalStorageSync, jsCache: config.jsCache, disableHttp2: config.disableHttp2 }); if (config.publicDir) addStaticDirToProxy(proxyServer, config.publicDir); const fileCacheOptions = { logger, ...config.fileCacheSessionConfig }; if (!cluster.isMaster) { fileCacheOptions.staleCleanupOptions = null; } const sessionStore = new RammerheadSessionFileCache(fileCacheOptions); sessionStore.attachToProxy(proxyServer); setupPipeline(proxyServer, sessionStore); setupRoutes(proxyServer, sessionStore, logger); // nicely close proxy server and save sessions to store before we exit exitHook(() => { logger.info(`(server) Received exit signal, closing proxy server`); proxyServer.close(); logger.info('(server) Closed proxy server'); }); if (!config.enableWorkers) { const formatUrl = (secure, hostname, port) => `${secure ? 'https' : 'http'}://${hostname}:${port}`; logger.info( `(server) Rammerhead proxy is listening on ${formatUrl(config.ssl, config.bindingAddress, config.port)}` ); } // spawn workers if multithreading is enabled // if (config.enableWorkers) { /** * @type {import('sticky-session-custom/lib/sticky/master').MasterOptions} */ const stickyOptions = { workers: config.workers, generatePrehashArray(req) { let sessionId = getSessionId(req.url); // /sessionid/url if (!sessionId) { // /editsession?id=sessionid const parsed = new URL(req.url, 'https://a.com'); sessionId = parsed.searchParams.get('id') || parsed.searchParams.get('sessionId'); if (!sessionId) { // sessionId is in referer header for (let i = 0; i < req.headers.length; i += 2) { if (req.headers[i].toLowerCase() === 'referer') { sessionId = getSessionId(req.headers[i + 1]); break; } } if (!sessionId) { // if there is still none, it's likely a static asset, in which case, // just delegate it to a worker sessionId = ' '; } } } return sessionId.split('').map((e) => e.charCodeAt()); } }; logger.info(JSON.stringify({ port: config.port, crossPort: config.crossDomainPort, master: cluster.isMaster })); const closeMasters = [sticky.listen(proxyServer.server1, config.port, config.bindingAddress, stickyOptions)]; if (config.crossDomainPort) { closeMasters.push( sticky.listen(proxyServer.server2, config.crossDomainPort, config.bindingAddress, stickyOptions) ); } if (closeMasters[0]) { // master process // const formatUrl = (secure, hostname, port) => `${secure ? 'https' : 'http'}://${hostname}:${port}`; logger.info( `Rammerhead proxy load balancer is listening on ${formatUrl( config.ssl, config.bindingAddress, config.port )}` ); // nicely close proxy server and save sessions to store before we exit exitHook(async (done) => { logger.info('Master received exit signal. Shutting down workers'); for (const closeMaster of closeMasters) { await new Promise((resolve) => closeMaster(resolve)); } logger.info('Closed all workers'); done(); }); } else { logger.info(`Worker ${cluster.worker.id} is running`); } } // if you want to just extend the functionality of this proxy server, you can // easily do so using this. mainly used for debugging module.exports = proxyServer; const config = require('../config'); const getSessionId = require('../util/getSessionId'); /** * @param {import('../classes/RammerheadProxy')} proxyServer * @param {import('../classes/RammerheadSessionAbstractStore')} sessionStore */ module.exports = function setupPipeline(proxyServer, sessionStore) { // remove headers defined in config.js proxyServer.addToOnRequestPipeline((req, res, _serverInfo, isRoute) => { if (isRoute) return; // only strip those that are going to the proxy destination website // restrict session to IP if enabled if (config.restrictSessionToIP) { const sessionId = getSessionId(req.url); const session = sessionId && sessionStore.get(sessionId); if (session && session.data.restrictIP && session.data.restrictIP !== config.getIP(req)) { res.writeHead(403); res.end('Sessions must come from the same IP'); return true; } } for (const eachHeader of config.stripClientHeaders) { delete req.headers[eachHeader]; } }); Object.assign(proxyServer.rewriteServerHeaders, config.rewriteServerHeaders); }; const generateId = require('../util/generateId'); const URLPath = require('../util/URLPath'); const httpResponse = require('../util/httpResponse'); const config = require('../config'); const StrShuffler = require('../util/StrShuffler'); const RammerheadSession = require('../classes/RammerheadSession'); /** * * @param {import('../classes/RammerheadProxy')} proxyServer * @param {import('../classes/RammerheadSessionAbstractStore')} sessionStore * @param {import('../classes/RammerheadLogging')} logger */ module.exports = function setupRoutes(proxyServer, sessionStore, logger) { const isNotAuthorized = (req, res) => { if (!config.password) return; const { pwd } = new URLPath(req.url).getParams(); if (config.password !== pwd) { httpResponse.accessForbidden(logger, req, res, config.getIP(req), 'bad password'); return true; } return false; }; if (process.env.DEVELOPMENT) { proxyServer.GET('/garbageCollect', (req, res) => { global.gc(); res.end('Ok'); }); } proxyServer.GET('/needpassword', (req, res) => { res.end(config.password ? 'true' : 'false'); }); proxyServer.GET('/newsession', (req, res) => { if (isNotAuthorized(req, res)) return; const id = generateId(); const session = new RammerheadSession(); session.data.restrictIP = config.getIP(req); // workaround for saving the modified session to disk sessionStore.addSerializedSession(id, session.serializeSession()); res.end(id); }); proxyServer.GET('/editsession', (req, res) => { if (isNotAuthorized(req, res)) return; let { id, httpProxy, enableShuffling } = new URLPath(req.url).getParams(); if (!id || !sessionStore.has(id)) { return httpResponse.badRequest(logger, req, res, config.getIP(req), 'not found'); } const session = sessionStore.get(id); if (httpProxy) { if (httpProxy.startsWith('http://')) { httpProxy = httpProxy.slice(7); } session.setExternalProxySettings(httpProxy); } else { session.externalProxySettings = null; } if (enableShuffling === '1' && !session.shuffleDict) { session.shuffleDict = StrShuffler.generateDictionary(); } if (enableShuffling === '0') { session.shuffleDict = null; } res.end('Success'); }); proxyServer.GET('/deletesession', (req, res) => { if (isNotAuthorized(req, res)) return; const { id } = new URLPath(req.url).getParams(); if (!id || !sessionStore.has(id)) { res.end('not found'); return; } sessionStore.delete(id); res.end('Success'); }); proxyServer.GET('/sessionexists', (req, res) => { const id = new URLPath(req.url).get('id'); if (!id) { httpResponse.badRequest(logger, req, res, config.getIP(req), 'Must specify id parameter'); } else { res.end(sessionStore.has(id) ? 'exists' : 'not found'); } }); proxyServer.GET('/mainport', (req, res) => { const serverInfo = config.getServerInfo(req); res.end((serverInfo.port || '').toString()); }); }; /* baseDictionary originally generated with (certain characters was removed to avoid breaking pages): let str = ''; for (let i = 32; i <= 126; i++) { let c = String.fromCharCode(i); if (c !== '/' && c !== '_' && encodeURI(c).length === 1) str += c; } */ const mod = (n, m) => ((n % m) + m) % m; const baseDictionary = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~-'; const shuffledIndicator = '_rhs'; const generateDictionary = function () { let str = ''; const split = baseDictionary.split(''); while (split.length > 0) { str += split.splice(Math.floor(Math.random() * split.length), 1)[0]; } return str; }; class StrShuffler { constructor(dictionary = generateDictionary()) { this.dictionary = dictionary; } shuffle(str) { if (str.startsWith(shuffledIndicator)) { return str; } let shuffledStr = ''; for (let i = 0; i < str.length; i++) { const char = str.charAt(i); const idx = baseDictionary.indexOf(char); if (char === '%' && str.length - i >= 3) { shuffledStr += char; shuffledStr += str.charAt(++i); shuffledStr += str.charAt(++i); } else if (idx === -1) { shuffledStr += char; } else { shuffledStr += this.dictionary.charAt(mod(idx + i, baseDictionary.length)); } } return shuffledIndicator + shuffledStr; } unshuffle(str) { if (!str.startsWith(shuffledIndicator)) { return str; } str = str.slice(shuffledIndicator.length); let unshuffledStr = ''; for (let i = 0; i < str.length; i++) { const char = str.charAt(i); const idx = this.dictionary.indexOf(char); if (char === '%' && str.length - i >= 3) { unshuffledStr += char; unshuffledStr += str.charAt(++i); unshuffledStr += str.charAt(++i); } else if (idx === -1) { unshuffledStr += char; } else { unshuffledStr += baseDictionary.charAt(mod(idx - i, baseDictionary.length)); } } return unshuffledStr; } } StrShuffler.baseDictionary = baseDictionary; StrShuffler.shuffledIndicator = shuffledIndicator; StrShuffler.generateDictionary = generateDictionary; module.exports = StrShuffler; /** * for lazy people who don't want to type out `new URL('http://blah' + req.url).searchParams.get(ugh)` all the time */ module.exports = class URLPath extends URL { /** * @param {string} path - /site/path */ constructor(path) { super(path, 'http://foobar'); } /** * @param {string} param - ?param=value * @returns {string|null} */ get(param) { return this.searchParams.get(param); } /** * @returns {{[param: string]: string}} */ getParams() { return Object.fromEntries(this.searchParams); } }; const crypto = require('crypto'); let cacheGet = async (_key) => { throw new TypeError('cannot cache get: must initialize cache settings first'); }; let cacheSet = async (_key, _value) => { throw new TypeError('cannot cache set: must initialize cache settings first'); }; /** * * @param {import('../classes/RammerheadJSAbstractCache.js')} jsCache */ module.exports = async function (jsCache) { const md5 = (data) => crypto.createHash('md5').update(data).digest('hex'); cacheGet = async (key) => await jsCache.get(md5(key)); cacheSet = async (key, value) => { if (!value) return; await jsCache.set(md5(key), value); } }; // patch ScriptResourceProcessor // https://github.com/DevExpress/testcafe-hammerhead/blob/47f8b6e370c37f2112fd7f56a3d493fbfcd7ec99/src/processing/resources/script.ts#L21 const scriptProcessor = require('testcafe-hammerhead/lib/processing/resources/script'); const { processScript } = require('testcafe-hammerhead/lib/processing/script'); const { updateScriptImportUrls } = require('testcafe-hammerhead/lib/utils/url'); const BUILTIN_HEADERS = require('testcafe-hammerhead/lib/request-pipeline/builtin-header-names'); scriptProcessor.__proto__.processResource = async function processResource(script, ctx, _charset, urlReplacer) { if (!script) return script; let processedScript = await cacheGet(script); if (!processedScript) { processedScript = processScript( script, true, false, urlReplacer, ctx.destRes.headers[BUILTIN_HEADERS.serviceWorkerAllowed], ctx.nativeAutomation ); await cacheSet(script, processedScript); } else processedScript = updateScriptImportUrls(processedScript, ctx.serverInfo, ctx.session.id, ctx.windowId); return processedScript; }; // handle the additional errors: ERR_INVALID_PROTOCOL and ETIMEDOUT // hammerhead handled errors: ECONNRESET, EPIPE (or ECONNABORTED for windows) const hGuard = require('testcafe-hammerhead/lib/request-pipeline/connection-reset-guard'); const isConnectionResetError = hGuard.isConnectionResetError; hGuard.isConnectionResetError = function (err) { // for some reason, ECONNRESET isn't handled correctly if ( isConnectionResetError(err) || err.code === 'ERR_INVALID_PROTOCOL' || err.code === 'ETIMEDOUT' || err.code === 'ECONNRESET' || err.code === 'EPIPE' ) { return true; } console.error('Unknown crash-inducing error:', err); // never return false as to avoid crashing the server return true; }; process.on('uncaughtException', (err) => { // for some reason, the above never catches all of the errors. this is a last resort failsafe if ( err.message.includes('ECONN') || err.message.includes('EPIPE') || err.message.includes('ETIMEDOUT') || err.message.includes('ERR_INVALID_') ) { // crash avoided! console.error('Avoided crash:' + err.message); } else { // probably a TypeError or something important console.error('About to throw: ' + err.message); throw err; } }); const mime = require('mime'); const fs = require('fs'); const path = require('path'); // these routes are reserved by hammerhead and rammerhead const forbiddenRoutes = [ '/rammerhead.js', '/hammerhead.js', '/task.js', '/iframe-task.js', '/messaging', '/transport-worker.js', '/worker-hammerhead.js' ]; const isDirectory = (dir) => fs.lstatSync(dir).isDirectory(); /** * * @param {import('testcafe-hammerhead').Proxy} proxy * @param {string} staticDir - all of the files and folders in the specified directory will be served * publicly. /index.html will automatically link to / * @param {string} rootPath - all the files that will be served under rootPath */ function addStaticFilesToProxy(proxy, staticDir, rootPath = '/', shouldIgnoreFile = (_file, _dir) => false) { if (!isDirectory(staticDir)) { throw new TypeError('specified folder path is not a directory'); } if (!rootPath.endsWith('/')) rootPath = rootPath + '/'; if (!rootPath.startsWith('/')) rootPath = '/' + rootPath; const files = fs.readdirSync(staticDir); files.map((file) => { if (isDirectory(path.join(staticDir, file))) { addStaticFilesToProxy(proxy, path.join(staticDir, file), rootPath + file + '/', shouldIgnoreFile); return; } if (shouldIgnoreFile(file, staticDir)) { return; } const pathToFile = path.join(staticDir, file); const staticContent = { content: fs.readFileSync(pathToFile), contentType: mime.getType(file) }; const route = rootPath + file; if (forbiddenRoutes.includes(route)) { throw new TypeError( `route clashes with hammerhead. problematic route: ${route}. problematic static file: ${pathToFile}` ); } proxy.GET(rootPath + file, staticContent); if (file === 'index.html') { proxy.GET(rootPath, staticContent); } }); } module.exports = addStaticFilesToProxy; const RequestPipelineContext = require('testcafe-hammerhead/lib/request-pipeline/context'); const StrShuffler = require('./StrShuffler'); const getSessionId = require('./getSessionId'); const replaceUrl = (url, replacer) => { // regex: https://google.com/ sessionid/ url return (url || '').replace(/^((?:[a-z0-9]+:\/\/[^/]+)?(?:\/[^/]+\/))([^]+)/i, function (_, g1, g2) { return g1 + replacer(g2); }); }; // unshuffle incoming url // const BUILTIN_HEADERS = require('testcafe-hammerhead/lib/request-pipeline/builtin-header-names'); const _dispatch = RequestPipelineContext.prototype.dispatch; RequestPipelineContext.prototype.dispatch = function (openSessions) { let sessionId = getSessionId(this.req.url); let session = sessionId && openSessions.get(sessionId); if (!session) { sessionId = getSessionId(this.req.headers[BUILTIN_HEADERS.referer]); session = sessionId && openSessions.get(sessionId); } if (session && session.shuffleDict) { const shuffler = new StrShuffler(session.shuffleDict); this.req.url = replaceUrl(this.req.url, (url) => shuffler.unshuffle(url)); if (getSessionId(this.req.headers[BUILTIN_HEADERS.referer]) === sessionId) { this.req.headers[BUILTIN_HEADERS.referer] = replaceUrl(this.req.headers[BUILTIN_HEADERS.referer], (url) => shuffler.unshuffle(url) ); } } return _dispatch.call(this, openSessions); }; // shuffle rewritten proxy urls // let disableShuffling = false; // for later use const _toProxyUrl = RequestPipelineContext.prototype.toProxyUrl; RequestPipelineContext.prototype.toProxyUrl = function (...args) { const proxyUrl = _toProxyUrl.apply(this, args); if (!this.session.shuffleDict || disableShuffling) return proxyUrl; const shuffler = new StrShuffler(this.session.shuffleDict); return replaceUrl(proxyUrl, (url) => shuffler.shuffle(url)); }; // unshuffle task.js referer header const Proxy = require('testcafe-hammerhead/lib/proxy/index'); const __onTaskScriptRequest = Proxy.prototype._onTaskScriptRequest; Proxy.prototype._onTaskScriptRequest = async function _onTaskScriptRequest(req, ...args) { const referer = req.headers[BUILTIN_HEADERS.referer]; const sessionId = getSessionId(referer); const session = sessionId && this.openSessions.get(sessionId); if (session && session.shuffleDict) { const shuffler = new StrShuffler(session.shuffleDict); req.headers[BUILTIN_HEADERS.referer] = replaceUrl(req.headers[BUILTIN_HEADERS.referer], (url) => shuffler.unshuffle(url) ); } return __onTaskScriptRequest.call(this, req, ...args); }; // don't shuffle action urls (because we don't get to control the rewriting when the user submits the form) const DomProcessor = require('testcafe-hammerhead/lib/processing/dom/index'); const __processUrlAttrs = DomProcessor.prototype._processUrlAttrs; DomProcessor.prototype._processUrlAttrs = function _processUrlAttrs(el, urlReplacer, pattern) { try { disableShuffling = pattern.urlAttr?.toLowerCase() === 'action'; __processUrlAttrs.call(this, el, urlReplacer, pattern); disableShuffling = false; } catch (e) { disableShuffling = false; throw e; } }; const urlUtils = require('testcafe-hammerhead/lib/utils/url'); const RequestPipelineContext = require('testcafe-hammerhead/lib/request-pipeline/context'); /** * if a non-crossdomain origin makes a request to a crossdomain port, the ports are flipped. this is to fix that issue. * there is also another issue with https://domain and https://domain:443 not matching. port 443/80 are automatically * removed if https and 443, and http and 80. * original: https://github.com/DevExpress/testcafe-hammerhead/blob/47f8b6e370c37f2112fd7f56a3d493fbfcd7ec99/src/request-pipeline/context/index.ts#L452 */ RequestPipelineContext.prototype.getProxyOrigin = function getProxyOrigin(isCrossDomain = false) { // if we receive a request that has a proxy origin header, (ctx.getProxyOrigin(!!ctx.dest.reqOrigin), // https://github.com/DevExpress/testcafe-hammerhead/blob/47f8b6e370c37f2112fd7f56a3d493fbfcd7ec99/src/request-pipeline/header-transforms/transforms.ts#L144), // then we must return the other port over. however, the issue with this is we don't know if the incoming request is actually a // crossdomain port (a simple check for reqOrigin cannot suffice, as a request from a non-crossdomain origin to a crossdomain port and // vice versa can happen), // so this will fix the issue from non-crossdomain port to crossdomain-port but will NOT fix crosdomain-port to non-crossdomain port. // However, the latter case will never happen because hammerhead made all client rewriting cross-domain requests to always use the // cross-domain ports, even if the origin is from a cross-domain port. const port = isCrossDomain ? this.serverInfo.port : this.serverInfo.crossDomainPort; // don't add a port if port is 443 and protocol is https:, and don't add a port if port is 80 and protocol is http:. // note that this isn't supported by the client rewriting, so client hammerhead's port.toString() will throw an error const hostPort = (this.serverInfo.protocol == 'https:' && port == 443) || (this.serverInfo.protocol == 'http:' && port == 80) ? null : port; return urlUtils.getDomain({ protocol: this.serverInfo.protocol, // use host instead of hostname so we can manually add in the port host: this.serverInfo.hostname + (hostPort ? ':' + hostPort : '') }); }; const transforms = require('testcafe-hammerhead/lib/request-pipeline/header-transforms/transforms'); const BUILTIN_HEADERS = require('testcafe-hammerhead/lib/request-pipeline/builtin-header-names'); /** * if we create this server with port === crossDomainPort, origin header doesn't get properly sent * so we manually add it if reqOrigin !== url */ transforms.forcedRequestTransforms[BUILTIN_HEADERS.origin] = (_src, ctx) => { if (ctx.serverInfo.port != ctx.serverInfo.crossDomainPort) return void 0; return ctx.dest.reqOrigin || ctx.dest.domain; }; const stages = require('testcafe-hammerhead/lib/request-pipeline/stages'); const { Duplex } = require('stream'); stages.unshift(function fixWebsocket(ctx) { ctx.isWebSocket = ctx.res instanceof Duplex; }); // fixes EPIPE error when trying to write head to a closed socket const hammerheadWS = require('testcafe-hammerhead/lib/request-pipeline/websocket'); const respondOnWebSocket = hammerheadWS.respondOnWebSocket; hammerheadWS.respondOnWebSocket = function (ctx) { ctx.res.on('error', (err) => { if (err.code !== 'EPIPE') { // console.error('Unknown crash-inducing error:', err); } // cleanup end will automatically be handled by the 'end' listener }); // crashes happen when client wants websocket but destination server says no. // reproduced by setting disableHttp2 to true and going to web.whatsapp.com if (!ctx.destRes.upgrade) { ctx.res.end(`HTTP/${ctx.destRes.httpVersion} ${ctx.destRes.statusCode} ${ctx.destRes.statusMessage}\r\r\n`); ctx.destRes.destroy(); return; } respondOnWebSocket(ctx); }; const uuid = require('uuid').v4; module.exports = () => uuid().replace(/-/g, ''); module.exports = (reqPath) => ((reqPath || '').match(/^(?:[a-z0-9]+:\/\/[^/]+)?\/([a-z0-9]{32})/i) || [])[1]; /** * @typedef {'badRequest'|'accessForbidden'} httpResponseTypes */ /** * @type {{[key in httpResponseTypes]: (logger: import('../classes/RammerheadLogging'), req: import('http').IncomingMessage, res: import('http').ServerResponse, ip: string, msg: string) => void}} */ module.exports = { badRequest: (logger, req, res, ip, msg) => { logger.error(`(httpResponse.badRequest) ${ip} ${req.url} ${msg}`); res.writeHead(400); res.end(msg); }, accessForbidden: (logger, req, res, ip, msg) => { logger.error(`(httpResponse.badRequest) ${ip} ${req.url} ${msg}`); res.writeHead(403); res.end(msg); } }; // https://github.com/DevExpress/testcafe-hammerhead/blob/47f8b6e370c37f2112fd7f56a3d493fbfcd7ec99/src/processing/resources/index.ts const url = require('url'); const pageProcessor = require('testcafe-hammerhead/lib/processing/resources/page'); const manifestProcessor = require('testcafe-hammerhead/lib/processing/resources/manifest'); const scriptProcessor = require('testcafe-hammerhead/lib/processing/resources/script'); const stylesheetProcessor = require('testcafe-hammerhead/lib/processing/resources/stylesheet'); const urlUtil = require('testcafe-hammerhead/lib/utils/url'); const { encodeContent, decodeContent } = require('testcafe-hammerhead/lib/processing/encoding'); const { platform } = require('os'); const IS_WIN = platform() === 'win32'; const DISK_RE = /^[A-Za-z]:/; const RESOURCE_PROCESSORS = [pageProcessor, manifestProcessor, scriptProcessor, stylesheetProcessor]; function getResourceUrlReplacer(ctx) { return function urlReplacer(resourceUrl, resourceType, charsetAttrValue, baseUrl, isCrossDomain = false, isUrlsSet = false) { if (isUrlsSet) return urlUtil.handleUrlsSet(urlReplacer, resourceUrl, resourceType, charsetAttrValue, baseUrl, isCrossDomain); if (!urlUtil.isSupportedProtocol(resourceUrl) && !urlUtil.isSpecialPage(resourceUrl)) return resourceUrl; if (IS_WIN && ctx.dest.protocol === 'file:' && DISK_RE.test(resourceUrl)) resourceUrl = '/' + resourceUrl; // NOTE: Resolves base URLs without a protocol ('//google.com/path' for example). baseUrl = baseUrl ? url.resolve(ctx.dest.url, baseUrl) : ''; resourceUrl = urlUtil.processSpecialChars(resourceUrl); let resolvedUrl = url.resolve(baseUrl || ctx.dest.url, resourceUrl); if (!urlUtil.isValidUrl(resolvedUrl)) return resourceUrl; // NOTE: Script or const isScriptLike = urlUtil.parseResourceType(resourceType).isScript; const charsetStr = charsetAttrValue || (isScriptLike && ctx.contentInfo.charset.get()); resolvedUrl = urlUtil.ensureTrailingSlash(resourceUrl, resolvedUrl); if (!urlUtil.isValidUrl(resolvedUrl)) return resolvedUrl; return ctx.toProxyUrl(resolvedUrl, isCrossDomain, resourceType, charsetStr); }; } require('testcafe-hammerhead/lib/processing/resources/index').process = async function process(ctx) { const { destResBody, contentInfo } = ctx; const { encoding, charset } = contentInfo; for (const processor of RESOURCE_PROCESSORS) { if (!processor.shouldProcessResource(ctx)) continue; const urlReplacer = getResourceUrlReplacer(ctx); if (pageProcessor === processor) await ctx.prepareInjectableUserScripts(ctx.eventFactory, ctx.session.injectable.userScripts); const decoded = await decodeContent(destResBody, encoding, charset); // @ts-ignore: Cannot invoke an expression whose type lacks a call signature const processed = await processor.processResource(decoded, ctx, charset, urlReplacer); // <-- add async support if (processed === pageProcessor.RESTART_PROCESSING) return await process(ctx); return await encodeContent(processed, encoding, charset); } return destResBody; } module.exports = function streamToString(stream) { const chunks = []; return new Promise((resolve, reject) => { stream.on('data', (chunk) => chunks.push(Buffer.from(chunk))); stream.on('error', (err) => reject(err)); stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); }); }; require('dotenv-flow').config(); const path = require('path'); const fs = require('fs'); const UglifyJS = require('uglify-js'); // modify unmodifable items that cannot be hooked in rammerhead.js fs.writeFileSync( path.join(__dirname, './client/hammerhead.js'), fs .readFileSync(path.join(__dirname, '../node_modules/testcafe-hammerhead/lib/client/hammerhead.js'), 'utf8') // part of fix for iframing issue .replace('(function initHammerheadClient () {', '(function initHammerheadClient () {' + 'if (window["%is-hammerhead%"]) throw new TypeError("already ran"); window["%is-hammerhead%"] = true;' + 'window.rammerheadTop = (function() {var w = window; while (w !== w.top && w.parent["%hammerhead"]) w = w.parent; return w;})();' + 'window.rammerheadParent = window.rammerheadTop === window ? window : window.parent;' + 'window.distanceRammerheadTopToTop = (function() { var i=0,w=window; while (w !== window.top) {i++;w=w.parent} return i; })();' + 'window.rammerheadAncestorOrigins = Array.from(__cpLocation.ancestorOrigins).slice(0, -window.distanceRammerheadTopToTop);\n') // fix iframing proxy issue. // we replace window.top comparisons with the most upper window that's still a proxied page .replace( /(window|win|wnd|instance|opener|activeWindow)\.top/g, '$1.rammerheadTop' ) .replace( /window\.parent/g, 'window.rammerheadParent' ) .replace( /window\.__cpLocation\.ancestorOrigins/g, 'window.rammerheadAncestorOrigins' ) .replace( 'isCrossDomainParent = parentLocationWrapper === parentWindow.__cpLocation', 'isCrossDomainParent = parentLocationWrapper === parentWindow.__cpLocation || !parentWindow["%hammerhead%"]' ) .replace( '!sameOriginCheck(window1Location, window2Location)', '!(sameOriginCheck(window1Location, window2Location) && (!!window1["%is-hammerhead%"] === !!window2["%is-hammerhead%"]))' ) // return false when unable to convert properties on other windows to booleans (!) .replace( /!(parent|parentWindow|window1|window2|window\.top)\[("%(?:is-)?hammerhead%")]/g, '!(() => { try{ return $1[$2]; }catch(error){ return true } })()' ) // disable saving to localStorage as we are using a completely different implementation .replace('saveToNativeStorage = function () {', 'saveToNativeStorage = function () {return;') // prevent calls to elements on a closed iframe .replace('dispatchEvent: function () {', '$& if (!window) return null;') .replace('click: function () {', '$& if (!window) return null;') .replace('setSelectionRange: function () {', '$& if (!window) return null;') .replace('select: function () {', '$& if (!window) return null;') .replace('focus: function () {', '$& if (!window) return null;') .replace('blur: function () {', '$& if (!window) return null;') .replace('preventDefault: function () {', '$& if (!window) return null;') // expose hooks for rammerhead.js .replace( 'function parseProxyUrl$1', 'window.overrideParseProxyUrl = function(rewrite) {parseProxyUrl$$1 = rewrite(parseProxyUrl$$1)}; $&' ) .replace( 'function getProxyUrl$1', 'window.overrideGetProxyUrl = function(rewrite) {getProxyUrl$$1 = rewrite(getProxyUrl$$1)}; $&' ) .replace('return window.__cpLocation.search;', 'return (new URL(get$$2())).search;') .replace('return window.__cpLocation.hash;', 'return (new URL(get$$2())).hash;') .replace( 'setter: function (search) {', '$& var url = new URL(get$$2()); url.search = search; window.__cpLocation = convertToProxyUrl(url.href); return search;' ) .replace( 'setter: function (hash) {', '$& var url = new URL(get$$2()); url.hash = hash; window.__cpLocation.hash = (new URL(convertToProxyUrl(url.href))).hash; return hash;' ) // sometimes, postMessage doesn't work as expected when // postMessage gets run/received in same window without hammerhead wrappings. // this is to double check hammerhead wrapped it // (cloudflare's turnsile threw this error after it tried to postMessage a fail code) .replace( 'data.type !== MessageType.Service && isWindow(target)', '$& && data.type?.startsWith("hammerhead|")' ) ); // fix the // worker-hammerhead.js:2434 Uncaught TypeError: Cannot read properties of undefined (reading 'toString') // at worker-hammerhead.js:2434:35 fs.writeFileSync( path.join(__dirname, './client/worker-hammerhead.js'), fs .readFileSync(path.join(__dirname, '../node_modules/testcafe-hammerhead/lib/client/worker-hammerhead.js'), 'utf8') .replace('proxyLocation.port.toString()', 'proxyLocation.port?.toString() || (proxyLocation.protocol === "https:" ? 443 : 80)') ); // fix the // transport-worker.js:1022 Uncaught TypeError: Cannot read properties of undefined (reading 'toString') // at transport-worker.js:1022:38 fs.writeFileSync( path.join(__dirname, './client/transport-worker.js'), fs .readFileSync(path.join(__dirname, '../node_modules/testcafe-hammerhead/lib/client/transport-worker.js'), 'utf8') .replace('proxyLocation.port.toString()', 'proxyLocation.port?.toString() || (proxyLocation.protocol === "https:" ? 443 : 80)') ); const minify = (fileName, newFileName) => { const minified = UglifyJS.minify(fs.readFileSync(path.join(__dirname, './client', fileName), 'utf8')); if (minified.error) { throw minified.error; } fs.writeFileSync(path.join(__dirname, './client', newFileName), minified.code, 'utf8'); }; minify('rammerhead.js', 'rammerhead.min.js'); minify('hammerhead.js', 'hammerhead.min.js'); minify('worker-hammerhead.js', 'worker-hammerhead.min.js'); minify('transport-worker.js', 'transport-worker.min.js'); const path = require('path'); const fs = require('fs'); const os = require('os'); const RammerheadJSMemCache = require('./classes/RammerheadJSMemCache.js'); const RammerheadJSFileCache = require('./classes/RammerheadJSFileCache.js'); const enableWorkers = os.cpus().length !== 1; module.exports = { //// HOSTING CONFIGURATION //// bindingAddress: '127.0.0.1', port: 8080, crossDomainPort: 8081, publicDir: path.join(__dirname, '../public'), // set to null to disable // enable or disable multithreading enableWorkers, workers: os.cpus().length, // ssl object is either null or { key: fs.readFileSync('path/to/key'), cert: fs.readFileSync('path/to/cert') } // for more info, see https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener ssl: null, // this function's return object will determine how the client url rewriting will work. // set them differently from bindingAddress and port if rammerhead is being served // from a reverse proxy. getServerInfo: () => ({ hostname: 'localhost', port: 8080, crossDomainPort: 8081, protocol: 'http:' }), // example of non-hard-coding the hostname header // getServerInfo: (req) => { // return { hostname: new URL('http://' + req.headers.host).hostname, port: 443, crossDomainPort: 8443, protocol: 'https: }; // }, // enforce a password for creating new sessions. set to null to disable password: 'sharkie4life', // disable or enable localStorage sync (turn off if clients send over huge localStorage data, resulting in huge memory usages) disableLocalStorageSync: false, // restrict sessions to be only used per IP restrictSessionToIP: true, // caching options for js rewrites. (disk caching not recommended for slow HDD disks) // recommended: 50mb for memory, 5gb for disk // jsCache: new RammerheadJSMemCache(5 * 1024 * 1024), jsCache: new RammerheadJSFileCache(path.join(__dirname, '../cache-js'), 5 * 1024 * 1024 * 1024, 50000, enableWorkers), // whether to disable http2 support or not (from proxy to destination site). // disabling may reduce number of errors/memory, but also risk // removing support for picky sites like web.whatsapp.com that want // the client to connect to http2 before connecting to their websocket disableHttp2: false, //// REWRITE HEADER CONFIGURATION //// // removes reverse proxy headers // cloudflare example: // stripClientHeaders: ['cf-ipcountry', 'cf-ray', 'x-forwarded-proto', 'cf-visitor', 'cf-connecting-ip', 'cdn-loop', 'x-forwarded-for'], stripClientHeaders: [], // if you want to modify response headers, like removing the x-frame-options header, do it like so: // rewriteServerHeaders: { // // you can also specify a function to modify/add the header using the original value (undefined if adding the header) // // 'x-frame-options': (originalHeaderValue) => '', // 'x-frame-options': null, // set to null to tell rammerhead that you want to delete it // }, rewriteServerHeaders: {}, //// SESSION STORE CONFIG //// // see src/classes/RammerheadSessionFileCache.js for more details and options fileCacheSessionConfig: { saveDirectory: path.join(__dirname, '../sessions'), cacheTimeout: 1000 * 60 * 20, // 20 minutes cacheCheckInterval: 1000 * 60 * 10, // 10 minutes deleteUnused: true, staleCleanupOptions: { staleTimeout: 1000 * 60 * 60 * 24 * 3, // 3 days maxToLive: null, staleCheckInterval: 1000 * 60 * 60 * 6 // 6 hours }, // corrupted session files happens when nodejs exits abruptly while serializing the JSON sessions to disk deleteCorruptedSessions: true, }, //// LOGGING CONFIGURATION //// // valid values: 'disabled', 'debug', 'traffic', 'info', 'warn', 'error' logLevel: process.env.DEVELOPMENT ? 'debug' : 'info', generatePrefix: (level) => `[${new Date().toISOString()}] [${level.toUpperCase()}] `, // logger depends on this value getIP: (req) => req.socket.remoteAddress // use the example below if rammerhead is sitting behind a reverse proxy like nginx // getIP: req => (req.headers['x-forwarded-for'] || req.connection.remoteAddress || '').split(',')[0].trim() }; if (fs.existsSync(path.join(__dirname, '../config.js'))) Object.assign(module.exports, require('../config')); const RammerheadProxy = require('./classes/RammerheadProxy'); const RammerheadLogging = require('./classes/RammerheadLogging'); const RammerheadSession = require('./classes/RammerheadSession'); const RammerheadSessionAbstractStore = require('./classes/RammerheadSessionAbstractStore'); const RammerheadSessionFileCache = require('./classes/RammerheadSessionFileCache'); const generateId = require('./util/generateId'); const addStaticFilesToProxy = require('./util/addStaticDirToProxy'); const RammerheadSessionMemoryStore = require('./classes/RammerheadMemoryStore'); const StrShuffler = require('./util/StrShuffler'); const URLPath = require('./util/URLPath'); const RammerheadJSAbstractCache = require('./classes/RammerheadJSAbstractCache.js'); const RammerheadJSFileCache = require('./classes/RammerheadJSFileCache.js'); const RammerheadJSMemCache = require('./classes/RammerheadJSMemCache.js'); module.exports = { RammerheadProxy, RammerheadLogging, RammerheadSession, RammerheadSessionAbstractStore, RammerheadSessionMemoryStore, RammerheadSessionFileCache, RammerheadJSAbstractCache, RammerheadJSFileCache, RammerheadJSMemCache, StrShuffler, generateId, addStaticFilesToProxy, URLPath }; require('./server/index.js'); src/client/*.min.* src/client/hammerhead.js { "parser": "babel-eslint", "env": { "commonjs": true, "es2021": true, "node": true, "browser": true }, "extends": "eslint:recommended", "parserOptions": { "ecmaVersion": 12 }, "rules": { "no-unused-vars": ["warn", { "args": "after-used", "argsIgnorePattern": "^_" }] } } node_modules/ # don't commit generated builds or env files src/client/*.min.* src/client/hammerhead.js src/client/worker-hammerhead.js src/client/transport-worker.js .env # don't commit session and cache data sessions/* cache-js/* # but commit the empty folders !sessions/.gitkeep !cache-js/.gitkeep # ignore any custom configurations /config.js src/client/*.min.* src/client/hammerhead.js { "printWidth": 120, "trailingComma": "none", "singleQuote": true, "tabWidth": 4 } { "name": "rammerhead", "version": "1.2.64", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "rammerhead", "version": "1.2.64", "hasInstallScript": true, "license": "MIT", "dependencies": { "async-exit-hook": "^2.0.1", "lru-cache": "^7.14.1", "mime": "^2.5.2", "sticky-session-custom": "^1.2.0", "testcafe-hammerhead": "31.6.2", "uglify-js": "^3.15.3", "uuid": "^8.3.2", "ws": "^8.2.0" }, "devDependencies": { "babel-eslint": "^10.1.0", "dotenv-flow": "^3.2.0", "eslint": "^7.32.0", "npm-force-resolutions": "0.0.10", "prettier": "^2.3.2" } }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "dependencies": { "sprintf-js": "~1.0.2" } }, "node_modules/@babel/helper-get-function-arity": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", "dev": true, "dependencies": { "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, "dependencies": { "ansi-colors": "^4.1.1" }, "engines": { "node": ">=8.6" } }, "node_modules/eslint/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "engines": { "node": ">=4" } }, "node_modules/eslint-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, "dependencies": { "eslint-visitor-keys": "^1.1.0" }, "engines": { "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/mysticatea" } }, "../sticky-session": { "name": "sticky-session-custom", "version": "1.2.0", "extraneous": true, "license": "MIT", "dependencies": { "debug": "^2.2.0", "http-parser-js": "^0.5.6", "ip": "^1.0.0" }, "devDependencies": { "jscs": "^2.1.1", "jshint": "^2.8.0" }, "engines": { "node": ">= 0.12.0" } }, "node_modules/testcafe-hammerhead/node_modules/mime": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", "bin": { "mime": "cli.js" } }, "node_modules/eslint/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { "color-convert": "^2.0.1" }, "engines": { "node": ">=8" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "node_modules/dotenv": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", "dev": true, "engines": { "node": ">=10" } }, "node_modules/eslint": { "version": "7.32.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", "dev": true, "dependencies": { "ignore": "^4.0.6", "table": "^6.0.9", "eslint-scope": "^5.1.1", "js-yaml": "^3.13.1", "semver": "^7.2.1", "natural-compare": "^1.4.0", "doctrine": "^3.0.0", "file-entry-cache": "^6.0.1", "is-glob": "^4.0.0", "progress": "^2.0.0", "lodash.merge": "^4.6.2", "functional-red-black-tree": "^1.0.1", "eslint-visitor-keys": "^2.0.0", "strip-ansi": "^6.0.0", "text-table": "^0.2.0", "import-fresh": "^3.0.0", "espree": "^7.3.1", "imurmurhash": "^0.1.4", "cross-spawn": "^7.0.2", "@eslint/eslintrc": "^0.4.3", "regexpp": "^3.1.0", "eslint-utils": "^2.1.0", "chalk": "^4.0.0", "strip-json-comments": "^3.1.0", "debug": "^4.0.1", "ajv": "^6.10.0", "@humanwhocodes/config-array": "^0.5.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "esutils": "^2.0.2", "globals": "^13.6.0", "minimatch": "^3.0.4", "glob-parent": "^5.1.2", "fast-deep-equal": "^3.1.3", "esquery": "^1.4.0", "v8-compile-cache": "^2.0.3", "enquirer": "^2.3.5", "optionator": "^0.9.1", "escape-string-regexp": "^4.0.0", "@babel/code-frame": "7.12.11" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { "node": "^10.12.0 || >=12.0.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/espree": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "dev": true, "dependencies": { "acorn": "^7.4.0", "acorn-jsx": "^5.3.1", "eslint-visitor-keys": "^1.3.0" }, "engines": { "node": "^10.12.0 || >=12.0.0" } }, "node_modules/@babel/helper-validator-identifier": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "engines": { "node": ">=6" } }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "engines": { "node": ">=0.12" }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/prettier": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", "dev": true, "bin": { "prettier": "bin-prettier.js" }, "engines": { "node": ">=10.13.0" } }, "node_modules/shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" }, "engines": { "node": "*" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/espree/node_modules/acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true, "bin": { "acorn": "bin/acorn" }, "engines": { "node": ">=0.4.0" } }, "node_modules/eslint-visitor-keys": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true, "engines": { "node": ">=4" } }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "engines": { "node": ">=0.10.0" } }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" }, "engines": { "node": ">= 8" } }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "node_modules/eslint/node_modules/table/node_modules/ajv": { "version": "8.9.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.2.2" }, "funding": { "type": "github", "url": "https://github.com/sponsors/epoberezkin" } }, "node_modules/@babel/types": { "version": "7.17.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.16.7", "to-fast-properties": "^2.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", "dev": true, "dependencies": { "@babel/code-frame": "^7.16.7", "@babel/parser": "^7.16.7", "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/httpntlm": { "version": "1.8.13", "resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.8.13.tgz", "integrity": "sha512-2F2FDPiWT4rewPzNMg3uPhNkP3NExENlUGADRUDPQvuftuUTGW98nLZtGemCIW3G40VhWZYgkIDcQFAwZ3mf2Q==", "funding": [ { "type": "paypal", "url": "https://www.paypal.com/donate/?hosted_button_id=2CKNJLZJBW8ZC" }, { "type": "buymeacoffee", "url": "https://www.buymeacoffee.com/samdecrock" } ], "dependencies": { "des.js": "^1.0.1", "httpreq": ">=0.4.22", "js-md4": "^0.3.2", "underscore": "~1.12.1" }, "engines": { "node": ">=10.4.0" } }, "node_modules/eslint/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { "has-flag": "^4.0.0" }, "engines": { "node": ">=8" } }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/nanoid": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", "bin": { "nanoid": "bin/nanoid.cjs" }, "engines": { "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true, "engines": { "node": ">=10" } }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, "node_modules/@babel/parser": { "version": "7.17.7", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.7.tgz", "integrity": "sha512-bm3AQf45vR4gKggRfvJdYJ0gFLoCbsPxiFLSH6hTVYABptNHY6l9NrhnucVjQ/X+SPtLANT9lc0fFhikj+VBRA==", "dev": true, "bin": { "parser": "bin/babel-parser.js" }, "engines": { "node": ">=6.0.0" } }, "node_modules/ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" }, "node_modules/mustache": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/mustache/-/mustache-2.3.2.tgz", "integrity": "sha512-KpMNwdQsYz3O/SBS1qJ/o3sqUJ5wSb8gb0pul8CO0S56b9Y2ALm8zCfsjPXsqGFfoNBkDwZuZIAjhsZI03gYVQ==", "bin": { "mustache": "bin/mustache" }, "engines": { "npm": ">=1.4.0" } }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dependencies": { "safe-buffer": "~5.1.0" } }, "node_modules/estraverse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, "engines": { "node": ">=4.0" } }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/@humanwhocodes/config-array": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.0", "debug": "^4.1.1", "minimatch": "^3.0.4" }, "engines": { "node": ">=10.10.0" } }, "node_modules/babel-eslint": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", "deprecated": "babel-eslint is now @babel/eslint-parser. This package will no longer receive updates.", "dev": true, "dependencies": { "@babel/code-frame": "^7.0.0", "@babel/parser": "^7.7.0", "@babel/traverse": "^7.7.0", "@babel/types": "^7.7.0", "eslint-visitor-keys": "^1.0.0", "resolve": "^1.12.0" }, "engines": { "node": ">=6" }, "peerDependencies": { "eslint": ">= 4.12.1" } }, "node_modules/http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" }, "node_modules/debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "dependencies": { "ms": "2.1.2" }, "engines": { "node": ">=6.0" }, "peerDependenciesMeta": { "supports-color": { "optional": true } } }, "node_modules/@babel/code-frame": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", "dev": true, "dependencies": { "@babel/highlight": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz", "integrity": "sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@types/estree": { "version": "0.0.46", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz", "integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==" }, "node_modules/commander": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", "engines": { "node": ">= 6" } }, "node_modules/eslint/node_modules/slice-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, "node_modules/testcafe-hammerhead/node_modules/ws": { "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", "engines": { "node": ">=8.3.0" }, "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "peerDependenciesMeta": { "bufferutil": { "optional": true }, "utf-8-validate": { "optional": true } } }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dependencies": { "wrappy": "1" } }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "dependencies": { "callsites": "^3.0.0" }, "engines": { "node": ">=6" } }, "node_modules/merge-stream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", "dependencies": { "readable-stream": "^2.0.1" } }, "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" } }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" }, "engines": { "node": ">=4" } }, "node_modules/testcafe-hammerhead/node_modules/iconv-lite": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.1.tgz", "integrity": "sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q==", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, "engines": { "node": ">=0.10.0" } }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/httpreq": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/httpreq/-/httpreq-1.1.1.tgz", "integrity": "sha512-uhSZLPPD2VXXOSN8Cni3kIsoFHaU2pT/nySEU/fHr/ePbqHYr0jeiQRmUKLEirC09SFPsdMoA7LU7UXMd/w0Kw==", "engines": { "node": ">= 6.15.1" } }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "bin": { "uuid": "dist/bin/uuid" } }, "node_modules/@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, "node_modules/esquery": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "dependencies": { "estraverse": "^5.1.0" }, "engines": { "node": ">=0.10" } }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true, "engines": { "node": ">=4" } }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" }, "funding": { "type": "github", "url": "https://github.com/sponsors/epoberezkin" } }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" }, "engines": { "node": ">=8.0.0" } }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true, "engines": { "node": ">=6" } }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" }, "engines": { "node": ">= 0.8.0" } }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "dependencies": { "is-glob": "^4.0.1" }, "engines": { "node": ">= 6" } }, "node_modules/ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true, "engines": { "node": ">= 4" } }, "node_modules/esotope-hammerhead": { "version": "0.6.5", "resolved": "https://registry.npmjs.org/esotope-hammerhead/-/esotope-hammerhead-0.6.5.tgz", "integrity": "sha512-vjncN4nG+RvsUNFC0idHNw1Xzse8GUWn15tr8In0Q4EBscnELTdpQVfsF/cRsMlvSbJdd0gUrW6gK5U+LTmVCg==", "dependencies": { "@types/estree": "0.0.46" } }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, "node_modules/async-exit-hook": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", "engines": { "node": ">=0.12.0" } }, "node_modules/uglify-js": { "version": "3.15.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.3.tgz", "integrity": "sha512-6iCVm2omGJbsu3JWac+p6kUiOpg3wFO2f8lIXjfEb8RrmLjzog1wTPMmwKB7swfzzqxj9YM+sGUM++u1qN4qJg==", "bin": { "uglifyjs": "bin/uglifyjs" }, "engines": { "node": ">=0.8.0" } }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "engines": { "node": ">=6" } }, "node_modules/match-url-wildcard": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/match-url-wildcard/-/match-url-wildcard-0.0.4.tgz", "integrity": "sha512-R1XhQaamUZPWLOPtp4ig5j+3jctN+skhgRmEQTUamMzmNtRG69QEirQs0NZKLtHMR7tzWpmtnS4Eqv65DcgXUA==", "dependencies": { "escape-string-regexp": "^1.0.5" } }, "node_modules/resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", "dev": true, "dependencies": { "is-core-module": "^2.8.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/tough-cookie": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", "universalify": "^0.2.0", "url-parse": "^1.5.3" }, "engines": { "node": ">=6" } }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" } }, "node_modules/@babel/generator": { "version": "7.17.7", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz", "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==", "dev": true, "dependencies": { "@babel/types": "^7.17.0", "jsesc": "^2.5.1", "source-map": "^0.5.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", "dev": true }, "node_modules/flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, "dependencies": { "flatted": "^3.1.0", "rimraf": "^3.0.2" }, "engines": { "node": "^10.12.0 || >=12.0.0" } }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "dependencies": { "function-bind": "^1.1.1" }, "engines": { "node": ">= 0.4.0" } }, "node_modules/read-file-relative": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/read-file-relative/-/read-file-relative-1.2.0.tgz", "integrity": "sha1-mPfZbqoh0rTHov69Y9L8jPNen5s=", "dependencies": { "callsite": "^1.0.0" } }, "node_modules/json-format": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-format/-/json-format-1.0.1.tgz", "integrity": "sha1-FD9n5irxKda//tKIpGJl6iPQ3ww=", "dev": true }, "node_modules/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dependencies": { "brace-expansion": "^1.1.7" }, "engines": { "node": "*" } }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "node_modules/eslint/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { "color-name": "~1.1.4" }, "engines": { "node": ">=7.0.0" } }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "dependencies": { "color-name": "1.1.3" } }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/mime": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "bin": { "mime": "cli.js" }, "engines": { "node": ">=4.0.0" } }, "node_modules/@electron/asar": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.7.tgz", "integrity": "sha512-8FaSCAIiZGYFWyjeevPQt+0e9xCK9YmJ2Rjg5SXgdsXon6cRnU0Yxnbe6CvJbQn26baifur2Y2G5EBayRIsjyg==", "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" }, "engines": { "node": ">=10.12.0" } }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true, "engines": { "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/mysticatea" } }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "dependencies": { "flat-cache": "^3.0.4" }, "engines": { "node": "^10.12.0 || >=12.0.0" } }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, "node_modules/eslint/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "13.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", "dev": true, "dependencies": { "type-fest": "^0.20.2" }, "engines": { "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/esquery/node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "engines": { "node": ">=4.0" } }, "node_modules/@babel/helper-environment-visitor": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", "dev": true, "dependencies": { "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { "version": "7.17.3", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", "dev": true, "dependencies": { "@babel/code-frame": "^7.16.7", "@babel/generator": "^7.17.3", "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-function-name": "^7.16.7", "@babel/helper-hoist-variables": "^7.16.7", "@babel/helper-split-export-declaration": "^7.16.7", "@babel/parser": "^7.17.3", "@babel/types": "^7.17.0", "debug": "^4.1.0", "globals": "^11.1.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/sticky-session-custom/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dependencies": { "ms": "2.0.0" } }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" }, "node_modules/xmlhttprequest": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=", "dev": true, "engines": { "node": ">=0.4.0" } }, "node_modules/lru-cache": { "version": "7.14.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", "engines": { "node": ">=12" } }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "dependencies": { "punycode": "^2.1.0" } }, "node_modules/url-parse": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, "engines": { "node": ">=4" } }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/source-map-support/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/acorn": { "version": "8.7.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", "dev": true, "peer": true, "bin": { "acorn": "bin/acorn" }, "engines": { "node": ">=0.4.0" } }, "node_modules/pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", "engines": { "node": ">=0.10.0" } }, "node_modules/callsite": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", "engines": { "node": "*" } }, "node_modules/semver": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", "dependencies": { "lru-cache": "^6.0.0" }, "bin": { "semver": "bin/semver.js" }, "engines": { "node": ">=10" } }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "dependencies": { "esutils": "^2.0.2" }, "engines": { "node": ">=6.0.0" } }, "node_modules/semver/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dependencies": { "yallist": "^4.0.0" }, "engines": { "node": ">=10" } }, "node_modules/js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "node_modules/eslint/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "node_modules/bowser": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/bowser/-/bowser-1.6.0.tgz", "integrity": "sha1-N/w4e2Fstq7zcNq01r1AK3TFxU0=" }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true, "bin": { "jsesc": "bin/jsesc" }, "engines": { "node": ">=4" } }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, "node_modules/@babel/helper-hoist-variables": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", "dev": true, "dependencies": { "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/http-parser-js": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.6.tgz", "integrity": "sha512-vDlkRPDJn93swjcjqMSaGSPABbIarsr1TLAui/gLDXzV5VsJNdXNzMYDyNBLQkjWQCJ1uizu8T2oDMhmGt0PRA==" }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" }, "engines": { "node": ">=4" } }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "dependencies": { "estraverse": "^5.2.0" }, "engines": { "node": ">=4.0" } }, "node_modules/readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "engines": { "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/flatted": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", "dev": true }, "node_modules/js-md4": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==" }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true, "engines": { "node": ">=0.4.0" } }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "dependencies": { "color-convert": "^1.9.0" }, "engines": { "node": ">=4" } }, "node_modules/eslint/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/testcafe-hammerhead/node_modules/debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dependencies": { "ms": "2.1.2" }, "engines": { "node": ">=6.0" }, "peerDependenciesMeta": { "supports-color": { "optional": true } } }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, "node_modules/universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "engines": { "node": ">= 4.0.0" } }, "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, "node_modules/dotenv-flow": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/dotenv-flow/-/dotenv-flow-3.2.0.tgz", "integrity": "sha512-GEB6RrR4AbqDJvNSFrYHqZ33IKKbzkvLYiD5eo4+9aFXr4Y4G+QaFrB/fNp0y6McWBmvaPn3ZNjIufnj8irCtg==", "dev": true, "dependencies": { "dotenv": "^8.0.0" }, "engines": { "node": ">= 8.0.0" } }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "dependencies": { "safe-buffer": "^5.0.1" }, "engines": { "node": "*" } }, "node_modules/des.js": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", "dependencies": { "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" } }, "node_modules/@eslint/eslintrc": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.1.1", "espree": "^7.3.0", "globals": "^13.9.0", "ignore": "^4.0.6", "import-fresh": "^3.2.1", "js-yaml": "^3.13.1", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" }, "engines": { "node": "^10.12.0 || >=12.0.0" } }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "node_modules/os-family": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/os-family/-/os-family-1.1.0.tgz", "integrity": "sha512-E3Orl5pvDJXnVmpaAA2TeNNpNhTMl4o5HghuWhOivBjEiTnJSrMYSa5uZMek1lBEvu8kKEsa2YgVcGFVDqX/9w==" }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, "node_modules/esrecurse/node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "engines": { "node": ">=4.0" } }, "node_modules/testcafe-hammerhead": { "version": "31.6.2", "resolved": "https://registry.npmjs.org/testcafe-hammerhead/-/testcafe-hammerhead-31.6.2.tgz", "integrity": "sha512-0whraISoT70LN9K6y1zgti30/M0seYVWlODZCWhUlDPZHSQDZaSj2G6QSamj2hfaki6DAB1qnE5x2FA8TvJcYA==", "dependencies": { "pinkie": "2.0.4", "semver": "7.5.3", "read-file-relative": "^1.2.0", "@electron/asar": "^3.2.3", "os-family": "^1.0.0", "parse5": "^7.1.2", "lodash": "^4.17.20", "esotope-hammerhead": "0.6.5", "bowser": "1.6.0", "lru-cache": "2.6.3", "@adobe/css-tools": "^4.3.0-rc.1", "nanoid": "^3.1.12", "tunnel-agent": "0.6.0", "match-url-wildcard": "0.0.4", "http-cache-semantics": "^4.1.0", "merge-stream": "^1.0.1", "mime": "~1.4.1", "debug": "4.3.1", "httpntlm": "^1.8.10", "acorn-hammerhead": "0.6.2", "tough-cookie": "4.1.3", "crypto-md5": "^1.0.0", "ws": "^7.4.6", "iconv-lite": "0.5.1", "mustache": "^2.1.1" }, "engines": { "node": ">=14.0.0" } }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, "engines": { "node": ">=8" } }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "node_modules/acorn-hammerhead": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/acorn-hammerhead/-/acorn-hammerhead-0.6.2.tgz", "integrity": "sha512-JZklfs1VVyjA1hf1y5qSzKSmK3K1UUUI7fQTuM/Zhv3rz4kFhdx4QwVnmU6tBEC8g/Ov6B+opfNFPeSZrlQfqA==", "dependencies": { "@types/estree": "0.0.46" } }, "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, "node_modules/npm-force-resolutions": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/npm-force-resolutions/-/npm-force-resolutions-0.0.10.tgz", "integrity": "sha512-Jscex+xIU6tw3VsyrwxM1TeT+dd9Fd3UOMAjy6J1TMpuYeEqg4LQZnATQO5vjPrsARm3und6zc6Dii/GUyRE5A==", "dev": true, "dependencies": { "json-format": "^1.0.1", "source-map-support": "^0.5.5", "xmlhttprequest": "^1.8.0" }, "bin": { "npm-force-resolutions": "index.js" } }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "dependencies": { "has-flag": "^3.0.0" }, "engines": { "node": ">=4" } }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "node_modules/eslint/node_modules/table": { "version": "6.8.0", "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", "dev": true, "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1" }, "engines": { "node": ">=10.0.0" } }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", "dev": true, "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.3" }, "engines": { "node": ">= 0.8.0" } }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" }, "engines": { "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint/node_modules/globals": { "version": "13.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", "dev": true, "dependencies": { "type-fest": "^0.20.2" }, "engines": { "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "engines": { "node": ">=0.8.0" } }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "dependencies": { "prelude-ls": "^1.2.1" }, "engines": { "node": ">= 0.8.0" } }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/is-core-module": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", "dev": true, "dependencies": { "has": "^1.0.3" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/@adobe/css-tools": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==" }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, "node_modules/@babel/helper-split-export-declaration": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", "dev": true, "dependencies": { "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/eslint/node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/crypto-md5": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/crypto-md5/-/crypto-md5-1.0.0.tgz", "integrity": "sha1-zMjadQx1PH7curxUKWdHKjhOhrs=", "engines": { "iojs": ">=1.0.0", "node": ">=0.5.2" } }, "node_modules/eslint/node_modules/@babel/code-frame": { "version": "7.12.11", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", "dev": true, "dependencies": { "@babel/highlight": "^7.10.4" } }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, "engines": { "node": ">=0.10.0" } }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true, "engines": { "node": ">=0.8.19" } }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" }, "engines": { "node": ">= 8" } }, "node_modules/testcafe-hammerhead/node_modules/lru-cache": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.3.tgz", "integrity": "sha1-UczQtPwMhDWH16VwnOTTt2Kb7cU=" }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "node_modules/underscore": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==" }, "node_modules/sticky-session-custom": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/sticky-session-custom/-/sticky-session-custom-1.2.0.tgz", "integrity": "sha512-tATDe2d4EITMyr+xdfpeekygNUvxCsSFQAVFcpXuFCy2Hk8CYNbZcGwgkIN9wqsgMGFx02XfUJBxJCynWR7F4A==", "dependencies": { "debug": "^2.2.0", "http-parser-js": "^0.5.6", "ip": "^1.0.0" }, "engines": { "node": ">= 0.12.0" } }, "node_modules/parse5": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", "dependencies": { "entities": "^4.4.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" } }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true, "engines": { "node": ">=4" } }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "node_modules/@babel/helper-function-name": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", "dev": true, "dependencies": { "@babel/helper-get-function-arity": "^7.16.7", "@babel/template": "^7.16.7", "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "engines": { "node": ">= 0.8.0" } }, "node_modules/ws": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/ws/-/ws-8.4.2.tgz", "integrity": "sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "peerDependenciesMeta": { "bufferutil": { "optional": true }, "utf-8-validate": { "optional": true } } }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true } }, "dependencies": { "@adobe/css-tools": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==" }, "@babel/code-frame": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", "dev": true, "requires": { "@babel/highlight": "^7.16.7" } }, "@babel/generator": { "version": "7.17.7", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz", "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==", "dev": true, "requires": { "@babel/types": "^7.17.0", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-environment-visitor": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", "dev": true, "requires": { "@babel/types": "^7.16.7" } }, "@babel/helper-function-name": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.16.7", "@babel/template": "^7.16.7", "@babel/types": "^7.16.7" } }, "@babel/helper-get-function-arity": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", "dev": true, "requires": { "@babel/types": "^7.16.7" } }, "@babel/helper-hoist-variables": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", "dev": true, "requires": { "@babel/types": "^7.16.7" } }, "@babel/helper-split-export-declaration": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", "dev": true, "requires": { "@babel/types": "^7.16.7" } }, "@babel/helper-validator-identifier": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", "dev": true }, "@babel/highlight": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz", "integrity": "sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { "version": "7.17.7", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.7.tgz", "integrity": "sha512-bm3AQf45vR4gKggRfvJdYJ0gFLoCbsPxiFLSH6hTVYABptNHY6l9NrhnucVjQ/X+SPtLANT9lc0fFhikj+VBRA==", "dev": true }, "@babel/template": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", "dev": true, "requires": { "@babel/code-frame": "^7.16.7", "@babel/parser": "^7.16.7", "@babel/types": "^7.16.7" } }, "@babel/traverse": { "version": "7.17.3", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", "dev": true, "requires": { "@babel/code-frame": "^7.16.7", "@babel/generator": "^7.17.3", "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-function-name": "^7.16.7", "@babel/helper-hoist-variables": "^7.16.7", "@babel/helper-split-export-declaration": "^7.16.7", "@babel/parser": "^7.17.3", "@babel/types": "^7.17.0", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { "version": "7.17.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.16.7", "to-fast-properties": "^2.0.0" } }, "@electron/asar": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.7.tgz", "integrity": "sha512-8FaSCAIiZGYFWyjeevPQt+0e9xCK9YmJ2Rjg5SXgdsXon6cRnU0Yxnbe6CvJbQn26baifur2Y2G5EBayRIsjyg==", "requires": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" } }, "@eslint/eslintrc": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.1.1", "espree": "^7.3.0", "globals": "^13.9.0", "ignore": "^4.0.6", "import-fresh": "^3.2.1", "js-yaml": "^3.13.1", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" }, "dependencies": { "globals": { "version": "13.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", "dev": true, "requires": { "type-fest": "^0.20.2" } } } }, "@humanwhocodes/config-array": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", "dev": true, "requires": { "@humanwhocodes/object-schema": "^1.2.0", "debug": "^4.1.1", "minimatch": "^3.0.4" } }, "@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, "@types/estree": { "version": "0.0.46", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz", "integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==" }, "acorn": { "version": "8.7.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", "dev": true, "peer": true }, "acorn-hammerhead": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/acorn-hammerhead/-/acorn-hammerhead-0.6.2.tgz", "integrity": "sha512-JZklfs1VVyjA1hf1y5qSzKSmK3K1UUUI7fQTuM/Zhv3rz4kFhdx4QwVnmU6tBEC8g/Ov6B+opfNFPeSZrlQfqA==", "requires": { "@types/estree": "0.0.46" } }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" } }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { "sprintf-js": "~1.0.2" } }, "async-exit-hook": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==" }, "babel-eslint": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "@babel/parser": "^7.7.0", "@babel/traverse": "^7.7.0", "@babel/types": "^7.7.0", "eslint-visitor-keys": "^1.0.0", "resolve": "^1.12.0" } }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "bowser": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/bowser/-/bowser-1.6.0.tgz", "integrity": "sha1-N/w4e2Fstq7zcNq01r1AK3TFxU0=" }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, "callsite": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "requires": { "color-name": "1.1.3" } }, "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, "commander": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==" }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "crypto-md5": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/crypto-md5/-/crypto-md5-1.0.0.tgz", "integrity": "sha1-zMjadQx1PH7curxUKWdHKjhOhrs=" }, "debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "requires": { "ms": "2.1.2" } }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, "des.js": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", "requires": { "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" } }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "requires": { "esutils": "^2.0.2" } }, "dotenv": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", "dev": true }, "dotenv-flow": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/dotenv-flow/-/dotenv-flow-3.2.0.tgz", "integrity": "sha512-GEB6RrR4AbqDJvNSFrYHqZ33IKKbzkvLYiD5eo4+9aFXr4Y4G+QaFrB/fNp0y6McWBmvaPn3ZNjIufnj8irCtg==", "dev": true, "requires": { "dotenv": "^8.0.0" } }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, "enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, "requires": { "ansi-colors": "^4.1.1" } }, "entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { "version": "7.32.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", "dev": true, "requires": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.3", "@humanwhocodes/config-array": "^0.5.0", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.0.1", "doctrine": "^3.0.0", "enquirer": "^2.3.5", "escape-string-regexp": "^4.0.0", "eslint-scope": "^5.1.1", "eslint-utils": "^2.1.0", "eslint-visitor-keys": "^2.0.0", "espree": "^7.3.1", "esquery": "^1.4.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", "glob-parent": "^5.1.2", "globals": "^13.6.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", "progress": "^2.0.0", "regexpp": "^3.1.0", "semver": "^7.2.1", "strip-ansi": "^6.0.0", "strip-json-comments": "^3.1.0", "table": "^6.0.9", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, "dependencies": { "@babel/code-frame": { "version": "7.12.11", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", "dev": true, "requires": { "@babel/highlight": "^7.10.4" } }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { "color-convert": "^2.0.1" } }, "astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { "color-name": "~1.1.4" } }, "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, "eslint-visitor-keys": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true }, "globals": { "version": "13.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", "dev": true, "requires": { "type-fest": "^0.20.2" } }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, "slice-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "requires": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" } }, "table": { "version": "6.8.0", "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", "dev": true, "requires": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1" }, "dependencies": { "ajv": { "version": "8.9.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } } } } } }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "eslint-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, "requires": { "eslint-visitor-keys": "^1.1.0" } }, "eslint-visitor-keys": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true }, "esotope-hammerhead": { "version": "0.6.5", "resolved": "https://registry.npmjs.org/esotope-hammerhead/-/esotope-hammerhead-0.6.5.tgz", "integrity": "sha512-vjncN4nG+RvsUNFC0idHNw1Xzse8GUWn15tr8In0Q4EBscnELTdpQVfsF/cRsMlvSbJdd0gUrW6gK5U+LTmVCg==", "requires": { "@types/estree": "0.0.46" } }, "espree": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "dev": true, "requires": { "acorn": "^7.4.0", "acorn-jsx": "^5.3.1", "eslint-visitor-keys": "^1.3.0" }, "dependencies": { "acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true } } }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "esquery": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "requires": { "estraverse": "^5.1.0" }, "dependencies": { "estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true } } }, "esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { "estraverse": "^5.2.0" }, "dependencies": { "estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true } } }, "estraverse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "requires": { "flat-cache": "^3.0.4" } }, "flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, "requires": { "flatted": "^3.1.0", "rimraf": "^3.0.2" } }, "flatted": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", "dev": true }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, "glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { "is-glob": "^4.0.1" } }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { "function-bind": "^1.1.1" } }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" }, "http-parser-js": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.6.tgz", "integrity": "sha512-vDlkRPDJn93swjcjqMSaGSPABbIarsr1TLAui/gLDXzV5VsJNdXNzMYDyNBLQkjWQCJ1uizu8T2oDMhmGt0PRA==" }, "httpntlm": { "version": "1.8.13", "resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.8.13.tgz", "integrity": "sha512-2F2FDPiWT4rewPzNMg3uPhNkP3NExENlUGADRUDPQvuftuUTGW98nLZtGemCIW3G40VhWZYgkIDcQFAwZ3mf2Q==", "requires": { "des.js": "^1.0.1", "httpreq": ">=0.4.22", "js-md4": "^0.3.2", "underscore": "~1.12.1" } }, "httpreq": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/httpreq/-/httpreq-1.1.1.tgz", "integrity": "sha512-uhSZLPPD2VXXOSN8Cni3kIsoFHaU2pT/nySEU/fHr/ePbqHYr0jeiQRmUKLEirC09SFPsdMoA7LU7UXMd/w0Kw==" }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "requires": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { "once": "^1.3.0", "wrappy": "1" } }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" }, "is-core-module": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", "dev": true, "requires": { "has": "^1.0.3" } }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "requires": { "is-extglob": "^2.1.1" } }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, "js-md4": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==" }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, "js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" } }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, "json-format": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-format/-/json-format-1.0.1.tgz", "integrity": "sha1-FD9n5irxKda//tKIpGJl6iPQ3ww=", "dev": true }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "requires": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, "lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", "dev": true }, "lru-cache": { "version": "7.14.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==" }, "match-url-wildcard": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/match-url-wildcard/-/match-url-wildcard-0.0.4.tgz", "integrity": "sha512-R1XhQaamUZPWLOPtp4ig5j+3jctN+skhgRmEQTUamMzmNtRG69QEirQs0NZKLtHMR7tzWpmtnS4Eqv65DcgXUA==", "requires": { "escape-string-regexp": "^1.0.5" } }, "merge-stream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", "requires": { "readable-stream": "^2.0.1" } }, "mime": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==" }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "^1.1.7" } }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "mustache": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/mustache/-/mustache-2.3.2.tgz", "integrity": "sha512-KpMNwdQsYz3O/SBS1qJ/o3sqUJ5wSb8gb0pul8CO0S56b9Y2ALm8zCfsjPXsqGFfoNBkDwZuZIAjhsZI03gYVQ==" }, "nanoid": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==" }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, "npm-force-resolutions": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/npm-force-resolutions/-/npm-force-resolutions-0.0.10.tgz", "integrity": "sha512-Jscex+xIU6tw3VsyrwxM1TeT+dd9Fd3UOMAjy6J1TMpuYeEqg4LQZnATQO5vjPrsARm3und6zc6Dii/GUyRE5A==", "dev": true, "requires": { "json-format": "^1.0.1", "source-map-support": "^0.5.5", "xmlhttprequest": "^1.8.0" } }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1" } }, "optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", "dev": true, "requires": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.3" } }, "os-family": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/os-family/-/os-family-1.1.0.tgz", "integrity": "sha512-E3Orl5pvDJXnVmpaAA2TeNNpNhTMl4o5HghuWhOivBjEiTnJSrMYSa5uZMek1lBEvu8kKEsa2YgVcGFVDqX/9w==" }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "requires": { "callsites": "^3.0.0" } }, "parse5": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", "requires": { "entities": "^4.4.0" } }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, "prettier": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", "dev": true }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, "psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" }, "read-file-relative": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/read-file-relative/-/read-file-relative-1.2.0.tgz", "integrity": "sha1-mPfZbqoh0rTHov69Y9L8jPNen5s=", "requires": { "callsite": "^1.0.0" } }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, "require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, "resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", "dev": true, "requires": { "is-core-module": "^2.8.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" } }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { "glob": "^7.1.3" } }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "semver": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", "requires": { "lru-cache": "^6.0.0" }, "dependencies": { "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "requires": { "yallist": "^4.0.0" } } } }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { "shebang-regex": "^3.0.0" } }, "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, "source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" }, "dependencies": { "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, "sticky-session-custom": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/sticky-session-custom/-/sticky-session-custom-1.2.0.tgz", "integrity": "sha512-tATDe2d4EITMyr+xdfpeekygNUvxCsSFQAVFcpXuFCy2Hk8CYNbZcGwgkIN9wqsgMGFx02XfUJBxJCynWR7F4A==", "requires": { "debug": "^2.2.0", "http-parser-js": "^0.5.6", "ip": "^1.0.0" }, "dependencies": { "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "requires": { "ms": "2.0.0" } } } }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" } }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { "ansi-regex": "^5.0.1" } }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" } }, "supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, "testcafe-hammerhead": { "version": "31.6.2", "resolved": "https://registry.npmjs.org/testcafe-hammerhead/-/testcafe-hammerhead-31.6.2.tgz", "integrity": "sha512-0whraISoT70LN9K6y1zgti30/M0seYVWlODZCWhUlDPZHSQDZaSj2G6QSamj2hfaki6DAB1qnE5x2FA8TvJcYA==", "requires": { "@adobe/css-tools": "^4.3.0-rc.1", "@electron/asar": "^3.2.3", "acorn-hammerhead": "0.6.2", "bowser": "1.6.0", "crypto-md5": "^1.0.0", "debug": "4.3.1", "esotope-hammerhead": "0.6.5", "http-cache-semantics": "^4.1.0", "httpntlm": "^1.8.10", "iconv-lite": "0.5.1", "lodash": "^4.17.20", "lru-cache": "2.6.3", "match-url-wildcard": "0.0.4", "merge-stream": "^1.0.1", "mime": "~1.4.1", "mustache": "^2.1.1", "nanoid": "^3.1.12", "os-family": "^1.0.0", "parse5": "^7.1.2", "pinkie": "2.0.4", "read-file-relative": "^1.2.0", "semver": "7.5.3", "tough-cookie": "4.1.3", "tunnel-agent": "0.6.0", "ws": "^7.4.6" }, "dependencies": { "debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "requires": { "ms": "2.1.2" } }, "iconv-lite": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.1.tgz", "integrity": "sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q==", "requires": { "safer-buffer": ">= 2.1.2 < 3" } }, "lru-cache": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.3.tgz", "integrity": "sha1-UczQtPwMhDWH16VwnOTTt2Kb7cU=" }, "mime": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" }, "ws": { "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==" } } }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, "tough-cookie": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "requires": { "psl": "^1.1.33", "punycode": "^2.1.1", "universalify": "^0.2.0", "url-parse": "^1.5.3" } }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { "safe-buffer": "^5.0.1" } }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "requires": { "prelude-ls": "^1.2.1" } }, "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true }, "uglify-js": { "version": "3.15.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.3.tgz", "integrity": "sha512-6iCVm2omGJbsu3JWac+p6kUiOpg3wFO2f8lIXjfEb8RrmLjzog1wTPMmwKB7swfzzqxj9YM+sGUM++u1qN4qJg==" }, "underscore": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==" }, "universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==" }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "requires": { "punycode": "^2.1.0" } }, "url-parse": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "requires": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, "v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { "isexe": "^2.0.0" } }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/ws/-/ws-8.4.2.tgz", "integrity": "sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==" }, "xmlhttprequest": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=", "dev": true }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } } { "name": "rammerhead", "version": "1.2.64", "description": "User friendly web proxy powered by testcafe-hammerhead", "main": "src/index.js", "scripts": { "preinstall": "npm install --package-lock-only --ignore-scripts && npx npm-force-resolutions", "start": "node src/server.js", "build": "node src/build.js", "bstart": "npm run build && npm run start", "test": "npm run format && npm run lint && npm run build", "lint": "eslint -c .eslintrc.json --ext .js src", "format": "prettier --write 'src/**/*.js'", "clientes5": "es-check es5 src/client/*.js public/**/*.js" }, "repository": { "type": "git", "url": "git+https://github.com/binary-person/rammerhead.git" }, "author": "Simon Cheng (https://github.com/binary-person)", "license": "MIT", "bugs": { "url": "https://github.com/binary-person/rammerhead/issues" }, "homepage": "https://github.com/binary-person/rammerhead#readme", "dependencies": { "async-exit-hook": "^2.0.1", "lru-cache": "^7.14.1", "mime": "^2.5.2", "sticky-session-custom": "^1.2.0", "testcafe-hammerhead": "31.6.2", "uglify-js": "^3.15.3", "uuid": "^8.3.2", "ws": "^8.2.0" }, "devDependencies": { "babel-eslint": "^10.1.0", "dotenv-flow": "^3.2.0", "eslint": "^7.32.0", "npm-force-resolutions": "0.0.10", "prettier": "^2.3.2" }, "resolutions": { "tmp": "0.2.1" }, "files": [ "src/*", "CHANGELOG.md", "package.json", "README.md" ] }