X-Git-Url: https://git.squeep.com/?a=blobdiff_plain;f=src%2Fcommon.js;h=0d6500c44eaaf7b201e96ecef698931761423005;hb=3ca7fccb306d0b23626befc3791ffa360b3db1e7;hp=b82b13e35e9b2fbf8224b8ee6407b5e3076ac9a9;hpb=9696c012e6b9a6c58904baa397ca0ebf78112316;p=websub-hub diff --git a/src/common.js b/src/common.js index b82b13e..0d6500c 100644 --- a/src/common.js +++ b/src/common.js @@ -19,46 +19,74 @@ const randomBytesAsync = promisify(randomBytes); /** * The HMAC hashes we are willing to support. - * @param {String} algorithm - * @returns {Boolean} + * @param {string} algorithm potential sha* algorithm + * @returns {boolean} is supported */ const validHash = (algorithm) => getHashes() - .filter((h) => h.match(/^sha[0-9]+$/)) + .filter((h) => h.match(/^sha\d+$/)) .includes(algorithm); + +/** + * Return an array containing x if x is not an array. + * @param {*} x possibly an array + * @returns {Array} x or [x] + */ +const ensureArray = (x) => { + if (x === undefined) { + return []; + } + if (!Array.isArray(x)) { + return Array(x); + } + return x; +}; + + /** * Recursively freeze an object. - * @param {Object} o - * @returns {Object} + * @param {object} o object + * @returns {object} frozen object */ const freezeDeep = (o) => { Object.freeze(o); Object.getOwnPropertyNames(o).forEach((prop) => { - if (Object.hasOwnProperty.call(o, prop) + if (Object.hasOwn(o, prop) && ['object', 'function'].includes(typeof o[prop]) && !Object.isFrozen(o[prop])) { return freezeDeep(o[prop]); } }); return o; -} +}; /** - * Pick out useful axios response fields. - * @param {*} res - * @returns + * Pick out useful got response fields. + * @param {*} res response + * @returns {object} winnowed response */ -const axiosResponseLogData = (res) => { +const gotResponseLogData = (res) => { const data = common.pick(res, [ - 'status', - 'statusText', + 'statusCode', + 'statusMessage', 'headers', - 'elapsedTimeMs', - 'data', + 'body', + 'error', ]); - if (data.data) { - data.data = logTruncate(data.data, 100); + if (typeof res.body === 'string') { + data.body = logTruncate(data.body, 100); + } else if (res.body instanceof Buffer) { + data.body = ``; + } + if (res?.timings?.phases?.total) { + data.elapsedTimeMs = res.timings.phases.total; + } + if (res?.redirectUrls?.length) { + data.redirectUrls = res.redirectUrls; + } + if (res?.retryCount) { + data.retryCount = res.retryCount; } return data; }; @@ -66,7 +94,7 @@ const axiosResponseLogData = (res) => { /** * Fallback values, if not configured. - * @returns {Object} + * @returns {object} object */ const topicLeaseDefaults = () => { return Object.freeze({ @@ -79,54 +107,55 @@ const topicLeaseDefaults = () => { /** * Pick from a range, constrained, with some fuzziness. - * @param {Number} attempt - * @param {Number[]} delays - * @param {Number} jitter - * @returns {Number} + * @param {number} attempt attempt number + * @param {number[]=} retryBackoffSeconds array of indexed delays + * @param {number=} jitter vary backoff by up to this fraction additional + * @returns {number} seconds to delay retry */ - const attemptRetrySeconds = (attempt, retryBackoffSeconds = [60, 120, 360, 1440, 7200, 43200, 86400], jitter = 0.618) => { +const attemptRetrySeconds = (attempt, retryBackoffSeconds = [60, 120, 360, 1440, 7200, 43200, 86400], jitter = 0.618) => { const maxAttempt = retryBackoffSeconds.length - 1; if (typeof attempt !== 'number' || attempt < 0) { attempt = 0; } else if (attempt > maxAttempt) { attempt = maxAttempt; } - // eslint-disable-next-line security/detect-object-injection + let seconds = retryBackoffSeconds[attempt]; seconds += Math.floor(Math.random() * seconds * jitter); return seconds; -} +}; /** * Return array items split as an array of arrays of no more than per items each. - * @param {Array} array - * @param {Number} per + * @param {Array} array items + * @param {number} per chunk size + * @returns {Array[]} array of chunks */ - const arrayChunk = (array, per = 1) => { +const arrayChunk = (array, per = 1) => { const nChunks = Math.ceil(array.length / per); return Array.from(Array(nChunks), (_, i) => array.slice(i * per, (i + 1) * per)); -} +}; /** * Be paranoid about blowing the stack when pushing to an array. - * @param {Array} dst - * @param {Array} src + * @param {Array} dst destination array + * @param {Array} src source array */ - const stackSafePush = (dst, src) => { +const stackSafePush = (dst, src) => { const jsEngineMaxArguments = 2**16; // Current as of Node 12 arrayChunk(src, jsEngineMaxArguments).forEach((items) => { Array.prototype.push.apply(dst, items); }); -} +}; /** * Limit length of string to keep logs sane - * @param {String} str - * @param {Number} len - * @returns {String} + * @param {string} str string + * @param {number} len max length + * @returns {string} truncated string */ const logTruncate = (str, len) => { if (typeof str !== 'string' || str.toString().length <= len) { @@ -135,13 +164,17 @@ const logTruncate = (str, len) => { return str.toString().slice(0, len) + `... (${str.toString().length} bytes)`; }; +const nop = () => undefined; + module.exports = { ...common, arrayChunk, attemptRetrySeconds, - axiosResponseLogData, + gotResponseLogData, + ensureArray, freezeDeep, logTruncate, + nop, randomBytesAsync, stackSafePush, topicLeaseDefaults,