update dependencies and devDependencies, related updates and minor fixes
[squeep-indie-auther] / src / common.js
1 'use strict';
2
3 const { common } = require('@squeep/api-dingus');
4
5 const { randomBytes } = require('crypto');
6 const { promisify } = require('util');
7 const randomBytesAsync = promisify(randomBytes);
8
9 /**
10 * Limit length of string to keep logs sane
11 * @param {String} str
12 * @param {Number} len
13 * @returns {String}
14 */
15 const logTruncate = (str, len) => {
16 if (typeof str !== 'string' || str.toString().length <= len) {
17 return str;
18 }
19 return str.toString().slice(0, len) + `... (${str.toString().length} bytes)`;
20 };
21
22 /**
23 * Turn a snake into a camel.
24 * @param {String} snakeCase
25 * @param {String|RegExp} delimiter
26 * @returns {String}
27 */
28 const camelfy = (snakeCase, delimiter = '_') => {
29 if (!snakeCase || typeof snakeCase.split !== 'function') {
30 return undefined;
31 }
32 const words = snakeCase.split(delimiter);
33 return [
34 words.shift(),
35 ...words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)),
36 ].join('');
37 };
38
39 /**
40 * Return an array containing x if x is not an array.
41 * @param {*} x
42 */
43 const ensureArray = (x) => {
44 if (x === undefined) {
45 return [];
46 }
47 if (!Array.isArray(x)) {
48 return Array(x);
49 }
50 return x;
51 };
52
53 /**
54 * Recursively freeze an object.
55 * @param {Object} o
56 * @returns {Object}
57 */
58 const freezeDeep = (o) => {
59 Object.freeze(o);
60 Object.getOwnPropertyNames(o).forEach((prop) => {
61 if (Object.hasOwnProperty.call(o, prop)
62 && ['object', 'function'].includes(typeof o[prop]) // eslint-disable-line security/detect-object-injection
63 && !Object.isFrozen(o[prop])) { // eslint-disable-line security/detect-object-injection
64 return freezeDeep(o[prop]); // eslint-disable-line security/detect-object-injection
65 }
66 });
67 return o;
68 };
69
70
71 /** Oauth2.1 §3.2.3.1
72 * %x20-21 / %x23-5B / %x5D-7E
73 * @param {String} char
74 */
75 const validErrorChar = (char) => {
76 const value = char.charCodeAt(0);
77 return value === 0x20 || value === 0x21
78 || (value >= 0x23 && value <= 0x5b)
79 || (value >= 0x5d && value <= 0x7e);
80 };
81
82
83 /**
84 * Determine if an OAuth error message is valid.
85 * @param {String} error
86 * @returns {Boolean}
87 */
88 const validError = (error) => {
89 return error && error.split('').filter((c) => !validErrorChar(c)).length === 0 || false;
90 };
91
92
93 /**
94 * OAuth2.1 §3.2.2.1
95 * scope-token = 1*( %x21 / %x23-5B / %x5D-7E )
96 * @param {String} char
97 */
98 const validScopeChar = (char) => {
99 const value = char.charCodeAt(0);
100 return value === 0x21
101 || (value >= 0x23 && value <= 0x5b)
102 || (value >= 0x5d && value <= 0x7e);
103 };
104
105
106 /**
107 * Determine if a scope has a valid name.
108 * @param {String} scope
109 * @returns {Boolean}
110 */
111 const validScope = (scope) => {
112 return scope && scope.split('').filter((c) => !validScopeChar(c)).length === 0 || false;
113 };
114
115
116 /**
117 *
118 * @param {Number} bytes
119 */
120 const newSecret = async (bytes = 64) => {
121 return (await randomBytesAsync(bytes * 3 / 4)).toString('base64');
122 };
123
124
125 /**
126 * Convert a Date object to epoch seconds.
127 * @param {Date=} date
128 * @returns {Number}
129 */
130 const dateToEpoch = (date) => {
131 const dateMs = date ? date.getTime() : Date.now();
132 return Math.ceil(dateMs / 1000);
133 };
134
135
136 const omit = (o, props) => {
137 return Object.fromEntries(Object.entries(o).filter(([k]) => !props.includes(k)))
138 };
139
140
141 /**
142 * Log Mystery Box statistics events.
143 * @param {Console} logger
144 * @param {String} scope
145 */
146 const mysteryBoxLogger = (logger, scope) => {
147 return (s) => {
148 logger.debug(scope, `${s.packageName}@${s.packageVersion}:${s.method}`, omit(s, [
149 'packageName',
150 'packageVersion',
151 'method',
152 ]));
153 };
154 };
155
156
157 const nop = () => { /**/ };
158
159 module.exports = {
160 ...common,
161 camelfy,
162 dateToEpoch,
163 ensureArray,
164 freezeDeep,
165 logTruncate,
166 mysteryBoxLogger,
167 newSecret,
168 omit,
169 randomBytesAsync,
170 validScope,
171 validError,
172 nop,
173 };
174