1 /* eslint-disable security/detect-object-injection */
5 * Assorted utility functions.
8 const { common
} = require('@squeep/api-dingus');
10 const { randomBytes
, getHashes
} = require('crypto');
11 const { promisify
} = require('util');
15 * Wrap this in a promise, as crypto.randomBytes is capable of blocking.
17 const randomBytesAsync
= promisify(randomBytes
);
21 * The HMAC hashes we are willing to support.
22 * @param {String} algorithm
25 const validHash
= (algorithm
) => getHashes()
26 .filter((h
) => h
.match(/^sha[0-9]+$/))
30 * Recursively freeze an object.
34 const freezeDeep
= (o
) => {
36 Object
.getOwnPropertyNames(o
).forEach((prop
) => {
37 if (Object
.hasOwnProperty
.call(o
, prop
)
38 && ['object', 'function'].includes(typeof o
[prop
])
39 && !Object
.isFrozen(o
[prop
])) {
40 return freezeDeep(o
[prop
]);
48 * Pick out useful axios response fields.
52 const axiosResponseLogData
= (res
) => {
53 const data
= common
.pick(res
, [
61 data
.data
= logTruncate(data
.data
, 100);
68 * Fallback values, if not configured.
71 const topicLeaseDefaults
= () => {
72 return Object
.freeze({
73 leaseSecondsPreferred: 86400 * 10,
74 leaseSecondsMin: 86400 * 1,
75 leaseSecondsMax: 86400 * 365,
81 * Pick from a range, constrained, with some fuzziness.
82 * @param {Number} attempt
83 * @param {Number[]} delays
84 * @param {Number} jitter
87 const attemptRetrySeconds
= (attempt
, retryBackoffSeconds
= [60, 120, 360, 1440, 7200, 43200, 86400], jitter
= 0.618) => {
88 const maxAttempt
= retryBackoffSeconds
.length
- 1;
89 if (typeof attempt
!== 'number' || attempt
< 0) {
91 } else if (attempt
> maxAttempt
) {
94 // eslint-disable-next-line security/detect-object-injection
95 let seconds
= retryBackoffSeconds
[attempt
];
96 seconds
+= Math
.floor(Math
.random() * seconds
* jitter
);
102 * Return array items split as an array of arrays of no more than per items each.
103 * @param {Array} array
104 * @param {Number} per
106 const arrayChunk
= (array
, per
= 1) => {
107 const nChunks
= Math
.ceil(array
.length
/ per
);
108 return Array
.from(Array(nChunks
), (_
, i
) => array
.slice(i
* per
, (i
+ 1) * per
));
113 * Be paranoid about blowing the stack when pushing to an array.
117 const stackSafePush
= (dst
, src
) => {
118 const jsEngineMaxArguments
= 2**16; // Current as of Node 12
119 arrayChunk(src
, jsEngineMaxArguments
).forEach((items
) => {
120 Array
.prototype.push
.apply(dst
, items
);
126 * Limit length of string to keep logs sane
127 * @param {String} str
128 * @param {Number} len
131 const logTruncate
= (str
, len
) => {
132 if (typeof str
!== 'string' || str
.toString().length
<= len
) {
135 return str
.toString().slice(0, len
) + `... (${str.toString().length} bytes)`;
142 axiosResponseLogData
,