0855720ee2a1766e46e92dad2a853edce22288a9
3 const { common
} = require('@squeep/api-dingus');
5 const { randomBytes
} = require('node:crypto');
6 const { promisify
} = require('node:util');
7 const randomBytesAsync
= promisify(randomBytes
);
10 * Limit length of string to keep logs sane
11 * @param {string} str str
12 * @param {number} len len
13 * @returns {string} str
15 const logTruncate
= (str
, len
) => {
16 if (typeof str
!== 'string' || str
.toString().length
<= len
) {
19 return str
.toString().slice(0, len
) + `... (${str.toString().length} bytes)`;
23 * Turn a snake into a camel.
24 * @param {string} snakeCase snake case
25 * @param {string | RegExp} delimiter delimiter
26 * @returns {string} camel case
28 const camelfy
= (snakeCase
, delimiter
= '_') => {
29 if (!snakeCase
|| typeof snakeCase
.split
!== 'function') {
32 const words
= snakeCase
.split(delimiter
);
35 ...words
.map((word
) => word
.charAt(0).toUpperCase() + word
.slice(1)),
40 * Return an array containing x if x is not an array.
42 * @returns {any[]} x[]
44 const ensureArray
= (x
) => {
45 if (x
=== undefined) {
48 if (!Array
.isArray(x
)) {
55 * Recursively freeze an object.
56 * @param {object} o obj
57 * @returns {object} frozen obj
59 const freezeDeep
= (o
) => {
61 Object
.getOwnPropertyNames(o
).forEach((prop
) => {
62 if (Object
.hasOwn(o
, prop
)
63 && ['object', 'function'].includes(typeof o
[prop
]) // eslint-disable-line security/detect-object-injection
64 && !Object
.isFrozen(o
[prop
])) { // eslint-disable-line security/detect-object-injection
65 return freezeDeep(o
[prop
]); // eslint-disable-line security/detect-object-injection
74 * %x20-21 / %x23-5B / %x5D-7E
75 * ' '-'!' / '#'-'[' / ']'-'~'
76 * not allowed: control characters, '"', '\'
77 * @param {string} char character
78 * @returns {boolean} is valid
80 const validErrorChar
= (char) => {
81 const value
= char.charCodeAt(0);
82 return value
=== 0x20 || value
=== 0x21
83 || (value
>= 0x23 && value
<= 0x5b)
84 || (value
>= 0x5d && value
<= 0x7e);
89 * Determine if an OAuth error message is valid.
90 * @param {string} error error
91 * @returns {boolean} is valid
93 const validError
= (error
) => {
94 return error
&& error
.split('').filter((c
) => !validErrorChar(c
)).length
=== 0 || false;
100 * scope-token = 1*( %x21 / %x23-5B / %x5D-7E )
101 * @param {string} char char
102 * @returns {boolean} is valid
104 const validScopeChar
= (char) => {
105 const value
= char.charCodeAt(0);
106 return value
=== 0x21
107 || (value
>= 0x23 && value
<= 0x5b)
108 || (value
>= 0x5d && value
<= 0x7e);
113 * Determine if a scope has a valid name.
114 * @param {string} scope scope
115 * @returns {boolean} is valid
117 const validScope
= (scope
) => {
118 return scope
&& scope
.split('').filter((c
) => !validScopeChar(c
)).length
=== 0 || false;
124 * @param {number} bytes bytes
125 * @returns {string} base64 random string
127 const newSecret
= async (bytes
= 64) => {
128 return (await
randomBytesAsync(bytes
* 3 / 4)).toString('base64');
133 * Convert a Date object to epoch seconds.
134 * @param {Date=} date date
135 * @returns {number} epoch
137 const dateToEpoch
= (date
) => {
138 const dateMs
= date
? date
.getTime() : Date
.now();
139 return Math
.ceil(dateMs
/ 1000);
143 const omit
= (o
, props
) => {
144 return Object
.fromEntries(Object
.entries(o
).filter(([k
]) => !props
.includes(k
)));
149 * @typedef {object} ConsoleLike
150 * @property {Function} debug log debug
154 * Log Mystery Box statistics events.
155 * @param {ConsoleLike} logger logger instance
156 * @param {string} scope scope
157 * @returns {Function} stat logger
159 const mysteryBoxLogger
= (logger
, scope
) => {
161 logger
.debug(scope
, `${s.packageName}@${s.packageVersion}:${s.method}`, omit(s
, [
170 const nop
= () => { /**/ };