@appium/base-driver
Advanced tools
Comparing version 9.3.16 to 9.3.17
@@ -214,4 +214,3 @@ "use strict"; | ||
`the security ramifications, please do so by following ` + | ||
`the documented instructions at https://github.com/appium` + | ||
`/appium/blob/master/docs/en/writing-running-appium/security.md`); | ||
`the documented instructions at http://appium.io/docs/en/2.0/guides/security/`); | ||
} | ||
@@ -218,0 +217,0 @@ } |
@@ -72,3 +72,3 @@ "use strict"; | ||
async function calculateFolderIntegrity(folderPath) { | ||
return (await support_1.fs.glob('**/*', { cwd: folderPath, strict: false, nosort: true })).length; | ||
return (await support_1.fs.glob('**/*', { cwd: folderPath })).length; | ||
} | ||
@@ -439,3 +439,2 @@ async function calculateFileIntegrity(filePath) { | ||
cwd: tmpRoot, | ||
strict: false, | ||
// Get the top level match | ||
@@ -442,0 +441,0 @@ })).sort((a, b) => a.split(path_1.default.sep).length - b.split(path_1.default.sep).length); |
@@ -1,2 +0,6 @@ | ||
export function handleIdempotency(req: any, res: any, next: any): Promise<any>; | ||
/** | ||
* @param {import('express').Request} req | ||
* @param {import('express').Response} res | ||
*/ | ||
export function handleIdempotency(req: import('express').Request, res: import('express').Response, next: any): Promise<any>; | ||
//# sourceMappingURL=idempotency.d.ts.map |
@@ -9,29 +9,25 @@ "use strict"; | ||
const lru_cache_1 = __importDefault(require("lru-cache")); | ||
const support_1 = require("@appium/support"); | ||
const os_1 = __importDefault(require("os")); | ||
const path_1 = __importDefault(require("path")); | ||
const lodash_1 = __importDefault(require("lodash")); | ||
const events_1 = require("events"); | ||
const CACHE_SIZE = 1024; | ||
const IDEMPOTENT_RESPONSES = new lru_cache_1.default({ | ||
max: CACHE_SIZE, | ||
max: 64, | ||
ttl: 30 * 60 * 1000, | ||
updateAgeOnGet: true, | ||
dispose(key, { response }) { | ||
if (response) { | ||
support_1.fs.rimrafSync(response); | ||
} | ||
}, | ||
updateAgeOnHas: true, | ||
dispose: (key, { responseStateListener }) => { | ||
responseStateListener.removeAllListeners(); | ||
} | ||
}); | ||
const MONITORED_METHODS = ['POST', 'PATCH']; | ||
const IDEMPOTENCY_KEY_HEADER = 'x-idempotency-key'; | ||
process.on('exit', () => { | ||
const resPaths = [...IDEMPOTENT_RESPONSES.values()].map(({ response }) => response).filter(Boolean); | ||
for (const resPath of resPaths) { | ||
try { | ||
// Asynchronous calls are not supported in onExit handler | ||
support_1.fs.rimrafSync(resPath); | ||
} | ||
catch (ign) { } | ||
/** | ||
* | ||
* @param {string} key | ||
* @param {import('express').Request} req | ||
* @param {import('express').Response} res | ||
*/ | ||
function cacheResponse(key, req, res) { | ||
if (!res.socket) { | ||
return; | ||
} | ||
}); | ||
function cacheResponse(key, req, res) { | ||
const responseStateListener = new events_1.EventEmitter(); | ||
@@ -44,29 +40,20 @@ IDEMPOTENT_RESPONSES.set(key, { | ||
}); | ||
const tmpFile = path_1.default.resolve(os_1.default.tmpdir(), `${support_1.util.uuidV4()}.response`); | ||
const responseListener = support_1.fs.createWriteStream(tmpFile, { | ||
emitClose: true, | ||
}); | ||
const originalSocketWriter = res.socket.write.bind(res.socket); | ||
let response = ''; | ||
const patchedWriter = (chunk, encoding, next) => { | ||
if (responseListener.writable) { | ||
responseListener.write(chunk); | ||
if (lodash_1.default.isString(chunk)) { | ||
response += chunk; | ||
} | ||
else if (lodash_1.default.isFunction(chunk.toString)) { | ||
response += chunk.toString(lodash_1.default.isString(encoding) ? encoding : undefined); | ||
} | ||
return originalSocketWriter(chunk, encoding, next); | ||
}; | ||
res.socket.write = patchedWriter; | ||
let writeError = null; | ||
let httpErrorMessage = null; | ||
let isResponseFullySent = false; | ||
responseListener.once('error', (e) => { | ||
writeError = e; | ||
}); | ||
res.once('finish', () => { | ||
isResponseFullySent = true; | ||
responseListener.end(); | ||
}); | ||
res.once('error', (e) => { httpErrorMessage = e.message; }); | ||
req.once('error', (e) => { httpErrorMessage = e.message; }); | ||
res.once('finish', () => { isResponseFullySent = true; }); | ||
res.once('close', () => { | ||
if (!isResponseFullySent) { | ||
responseListener.end(); | ||
} | ||
}); | ||
responseListener.once('close', () => { | ||
if (res.socket?.write === patchedWriter) { | ||
@@ -78,10 +65,8 @@ res.socket.write = originalSocketWriter; | ||
`Cache consistency has been damaged`); | ||
return responseStateListener.emit('ready', null); | ||
} | ||
if (writeError) { | ||
logger_1.default.info(`Could not cache the response identified by '${key}': ${writeError.message}`); | ||
else if (httpErrorMessage) { | ||
logger_1.default.info(`Could not cache the response identified by '${key}': ${httpErrorMessage}`); | ||
IDEMPOTENT_RESPONSES.delete(key); | ||
return responseStateListener.emit('ready', null); | ||
} | ||
if (!isResponseFullySent) { | ||
else if (!isResponseFullySent) { | ||
logger_1.default.info(`Could not cache the response identified by '${key}', ` + | ||
@@ -91,13 +76,23 @@ `because it has not been completed`); | ||
IDEMPOTENT_RESPONSES.delete(key); | ||
return responseStateListener.emit('ready', null); | ||
} | ||
IDEMPOTENT_RESPONSES.get(key).response = tmpFile; | ||
responseStateListener.emit('ready', tmpFile); | ||
const value = IDEMPOTENT_RESPONSES.get(key); | ||
if (value) { | ||
value.response = Buffer.from(response, 'utf8'); | ||
responseStateListener.emit('ready', value.response); | ||
} | ||
else { | ||
responseStateListener.emit('ready', null); | ||
} | ||
}); | ||
} | ||
/** | ||
* @param {import('express').Request} req | ||
* @param {import('express').Response} res | ||
*/ | ||
async function handleIdempotency(req, res, next) { | ||
const key = req.headers[IDEMPOTENCY_KEY_HEADER]; | ||
if (!key) { | ||
const keyOrArr = req.headers[IDEMPOTENCY_KEY_HEADER]; | ||
if (!keyOrArr) { | ||
return next(); | ||
} | ||
const key = lodash_1.default.isArray(keyOrArr) ? keyOrArr[0] : keyOrArr; | ||
if (!MONITORED_METHODS.includes(req.method)) { | ||
@@ -113,4 +108,4 @@ // GET, DELETE, etc. requests are idempotent by default | ||
} | ||
const { method: storedMethod, path: storedPath, response, responseStateListener, } = IDEMPOTENT_RESPONSES.get(key); | ||
if (req.method !== storedMethod || req.path !== storedPath) { | ||
const { method, path, response, responseStateListener, } = IDEMPOTENT_RESPONSES.get(key); | ||
if (req.method !== method || req.path !== path) { | ||
logger_1.default.warn(`Got two different requests with the same idempotency key '${key}'`); | ||
@@ -120,15 +115,9 @@ logger_1.default.warn('Is the client generating idempotency keys properly?'); | ||
} | ||
const rerouteCachedResponse = async (cachedResPath) => { | ||
if (!(await support_1.fs.exists(cachedResPath))) { | ||
IDEMPOTENT_RESPONSES.delete(key); | ||
logger_1.default.warn(`Could not read the cached response identified by key '${key}'`); | ||
logger_1.default.warn('The temporary storage is not accessible anymore'); | ||
return next(); | ||
} | ||
support_1.fs.createReadStream(cachedResPath).pipe(res.socket); | ||
}; | ||
if (response) { | ||
logger_1.default.info(`The same request with the idempotency key '${key}' has been already processed`); | ||
logger_1.default.info(`Rerouting its response to the current request`); | ||
await rerouteCachedResponse(response); | ||
if (!res.socket?.writable) { | ||
return next(); | ||
} | ||
res.socket.write(response.toString('utf8')); | ||
} | ||
@@ -138,7 +127,7 @@ else { | ||
logger_1.default.info(`Waiting for the response to be rerouted to the current request`); | ||
responseStateListener.once('ready', async (cachedResponsePath) => { | ||
if (!cachedResponsePath) { | ||
responseStateListener.once('ready', async (/** @type {Buffer?} */ cachedResponse) => { | ||
if (!cachedResponse || !res.socket?.writable) { | ||
return next(); | ||
} | ||
await rerouteCachedResponse(cachedResponsePath); | ||
res.socket.write(cachedResponse.toString('utf8')); | ||
}); | ||
@@ -145,0 +134,0 @@ } |
@@ -90,2 +90,3 @@ "use strict"; | ||
} | ||
// @ts-ignore | ||
app.use(middleware_1.handleIdempotency); | ||
@@ -92,0 +93,0 @@ app.use((0, middleware_1.fixPythonContentType)(basePath)); |
@@ -296,4 +296,3 @@ /* eslint-disable no-unused-vars */ | ||
`the security ramifications, please do so by following ` + | ||
`the documented instructions at https://github.com/appium` + | ||
`/appium/blob/master/docs/en/writing-running-appium/security.md` | ||
`the documented instructions at http://appium.io/docs/en/2.0/guides/security/` | ||
); | ||
@@ -300,0 +299,0 @@ } |
@@ -75,3 +75,3 @@ import _ from 'lodash'; | ||
async function calculateFolderIntegrity(folderPath) { | ||
return (await fs.glob('**/*', {cwd: folderPath, strict: false, nosort: true})).length; | ||
return (await fs.glob('**/*', {cwd: folderPath})).length; | ||
} | ||
@@ -479,3 +479,2 @@ | ||
cwd: tmpRoot, | ||
strict: false, | ||
// Get the top level match | ||
@@ -482,0 +481,0 @@ }) |
import log from './logger'; | ||
import LRU from 'lru-cache'; | ||
import {fs, util} from '@appium/support'; | ||
import os from 'os'; | ||
import path from 'path'; | ||
import _ from 'lodash'; | ||
import {EventEmitter} from 'events'; | ||
const CACHE_SIZE = 1024; | ||
const IDEMPOTENT_RESPONSES = new LRU({ | ||
max: CACHE_SIZE, | ||
max: 64, | ||
ttl: 30 * 60 * 1000, | ||
updateAgeOnGet: true, | ||
dispose(key, {response}) { | ||
if (response) { | ||
fs.rimrafSync(response); | ||
} | ||
}, | ||
updateAgeOnHas: true, | ||
dispose: (key, {responseStateListener}) => { | ||
responseStateListener.removeAllListeners(); | ||
} | ||
}); | ||
@@ -21,13 +18,13 @@ const MONITORED_METHODS = ['POST', 'PATCH']; | ||
process.on('exit', () => { | ||
const resPaths = [...IDEMPOTENT_RESPONSES.values()].map(({response}) => response).filter(Boolean); | ||
for (const resPath of resPaths) { | ||
try { | ||
// Asynchronous calls are not supported in onExit handler | ||
fs.rimrafSync(resPath); | ||
} catch (ign) {} | ||
/** | ||
* | ||
* @param {string} key | ||
* @param {import('express').Request} req | ||
* @param {import('express').Response} res | ||
*/ | ||
function cacheResponse(key, req, res) { | ||
if (!res.socket) { | ||
return; | ||
} | ||
}); | ||
function cacheResponse(key, req, res) { | ||
const responseStateListener = new EventEmitter(); | ||
@@ -40,10 +37,9 @@ IDEMPOTENT_RESPONSES.set(key, { | ||
}); | ||
const tmpFile = path.resolve(os.tmpdir(), `${util.uuidV4()}.response`); | ||
const responseListener = fs.createWriteStream(tmpFile, { | ||
emitClose: true, | ||
}); | ||
const originalSocketWriter = res.socket.write.bind(res.socket); | ||
let response = ''; | ||
const patchedWriter = (chunk, encoding, next) => { | ||
if (responseListener.writable) { | ||
responseListener.write(chunk); | ||
if (_.isString(chunk)) { | ||
response += chunk; | ||
} else if (_.isFunction(chunk.toString)) { | ||
response += chunk.toString(_.isString(encoding) ? encoding : undefined); | ||
} | ||
@@ -53,17 +49,8 @@ return originalSocketWriter(chunk, encoding, next); | ||
res.socket.write = patchedWriter; | ||
let writeError = null; | ||
let httpErrorMessage = null; | ||
let isResponseFullySent = false; | ||
responseListener.once('error', (e) => { | ||
writeError = e; | ||
}); | ||
res.once('finish', () => { | ||
isResponseFullySent = true; | ||
responseListener.end(); | ||
}); | ||
res.once('error', (e) => { httpErrorMessage = e.message; }); | ||
req.once('error', (e) => { httpErrorMessage = e.message; }); | ||
res.once('finish', () => { isResponseFullySent = true; }); | ||
res.once('close', () => { | ||
if (!isResponseFullySent) { | ||
responseListener.end(); | ||
} | ||
}); | ||
responseListener.once('close', () => { | ||
if (res.socket?.write === patchedWriter) { | ||
@@ -76,31 +63,37 @@ res.socket.write = originalSocketWriter; | ||
`Could not cache the response identified by '${key}'. ` + | ||
`Cache consistency has been damaged` | ||
`Cache consistency has been damaged` | ||
); | ||
return responseStateListener.emit('ready', null); | ||
} | ||
if (writeError) { | ||
log.info(`Could not cache the response identified by '${key}': ${writeError.message}`); | ||
} else if (httpErrorMessage) { | ||
log.info(`Could not cache the response identified by '${key}': ${httpErrorMessage}`); | ||
IDEMPOTENT_RESPONSES.delete(key); | ||
return responseStateListener.emit('ready', null); | ||
} | ||
if (!isResponseFullySent) { | ||
} else if (!isResponseFullySent) { | ||
log.info( | ||
`Could not cache the response identified by '${key}', ` + | ||
`because it has not been completed` | ||
`because it has not been completed` | ||
); | ||
log.info('Does the client terminate connections too early?'); | ||
IDEMPOTENT_RESPONSES.delete(key); | ||
return responseStateListener.emit('ready', null); | ||
} | ||
IDEMPOTENT_RESPONSES.get(key).response = tmpFile; | ||
responseStateListener.emit('ready', tmpFile); | ||
const value = IDEMPOTENT_RESPONSES.get(key); | ||
if (value) { | ||
value.response = Buffer.from(response, 'utf8'); | ||
responseStateListener.emit('ready', value.response); | ||
} else { | ||
responseStateListener.emit('ready', null); | ||
} | ||
}); | ||
} | ||
/** | ||
* @param {import('express').Request} req | ||
* @param {import('express').Response} res | ||
*/ | ||
async function handleIdempotency(req, res, next) { | ||
const key = req.headers[IDEMPOTENCY_KEY_HEADER]; | ||
if (!key) { | ||
const keyOrArr = req.headers[IDEMPOTENCY_KEY_HEADER]; | ||
if (!keyOrArr) { | ||
return next(); | ||
} | ||
const key = _.isArray(keyOrArr) ? keyOrArr[0] : keyOrArr; | ||
if (!MONITORED_METHODS.includes(req.method)) { | ||
@@ -119,8 +112,8 @@ // GET, DELETE, etc. requests are idempotent by default | ||
const { | ||
method: storedMethod, | ||
path: storedPath, | ||
method, | ||
path, | ||
response, | ||
responseStateListener, | ||
} = IDEMPOTENT_RESPONSES.get(key); | ||
if (req.method !== storedMethod || req.path !== storedPath) { | ||
if (req.method !== method || req.path !== path) { | ||
log.warn(`Got two different requests with the same idempotency key '${key}'`); | ||
@@ -131,24 +124,17 @@ log.warn('Is the client generating idempotency keys properly?'); | ||
const rerouteCachedResponse = async (cachedResPath) => { | ||
if (!(await fs.exists(cachedResPath))) { | ||
IDEMPOTENT_RESPONSES.delete(key); | ||
log.warn(`Could not read the cached response identified by key '${key}'`); | ||
log.warn('The temporary storage is not accessible anymore'); | ||
return next(); | ||
} | ||
fs.createReadStream(cachedResPath).pipe(res.socket); | ||
}; | ||
if (response) { | ||
log.info(`The same request with the idempotency key '${key}' has been already processed`); | ||
log.info(`Rerouting its response to the current request`); | ||
await rerouteCachedResponse(response); | ||
if (!res.socket?.writable) { | ||
return next(); | ||
} | ||
res.socket.write(response.toString('utf8')); | ||
} else { | ||
log.info(`The same request with the idempotency key '${key}' is being processed`); | ||
log.info(`Waiting for the response to be rerouted to the current request`); | ||
responseStateListener.once('ready', async (cachedResponsePath) => { | ||
if (!cachedResponsePath) { | ||
responseStateListener.once('ready', async (/** @type {Buffer?} */ cachedResponse) => { | ||
if (!cachedResponse || !res.socket?.writable) { | ||
return next(); | ||
} | ||
await rerouteCachedResponse(cachedResponsePath); | ||
res.socket.write(cachedResponse.toString('utf8')); | ||
}); | ||
@@ -155,0 +141,0 @@ } |
@@ -121,2 +121,3 @@ import _ from 'lodash'; | ||
} | ||
// @ts-ignore | ||
app.use(handleIdempotency); | ||
@@ -123,0 +124,0 @@ app.use(fixPythonContentType(basePath)); |
{ | ||
"name": "@appium/base-driver", | ||
"version": "9.3.16", | ||
"version": "9.3.17", | ||
"description": "Base driver class for Appium drivers", | ||
@@ -47,9 +47,9 @@ "keywords": [ | ||
"dependencies": { | ||
"@appium/support": "^4.1.3", | ||
"@appium/types": "^0.13.2", | ||
"@colors/colors": "1.5.0", | ||
"@appium/support": "^4.1.4", | ||
"@appium/types": "^0.13.3", | ||
"@colors/colors": "1.6.0", | ||
"@types/async-lock": "1.4.0", | ||
"@types/bluebird": "3.5.38", | ||
"@types/express": "4.17.17", | ||
"@types/lodash": "4.14.195", | ||
"@types/lodash": "4.14.196", | ||
"@types/method-override": "0.0.32", | ||
@@ -71,3 +71,3 @@ "@types/serve-favicon": "2.5.4", | ||
"source-map-support": "0.5.21", | ||
"type-fest": "3.11.1", | ||
"type-fest": "3.13.1", | ||
"validate.js": "0.13.1" | ||
@@ -82,3 +82,3 @@ }, | ||
}, | ||
"gitHead": "ec57ff4c2a1ae3ecbab11bd02d4249ca67626d83", | ||
"gitHead": "d6204b6902074210943d7bbbf72d139b9b170a20", | ||
"typedoc": { | ||
@@ -85,0 +85,0 @@ "entryPoint": "./lib/index.js" |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
909922
15967
+ Added@types/lodash@4.14.196(transitive)
- Removed@colors/colors@1.5.0(transitive)
- Removed@types/lodash@4.14.195(transitive)
- Removedtype-fest@3.11.1(transitive)
Updated@appium/support@^4.1.4
Updated@appium/types@^0.13.3
Updated@colors/colors@1.6.0
Updated@types/lodash@4.14.196
Updatedtype-fest@3.13.1