mentie 0.3.15 → 0.3.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/modules/cache.js +5 -0
- package/modules/crypto.js +1 -1
- package/modules/environment.js +7 -3
- package/modules/logging.js +6 -3
- package/modules/network.js +17 -12
- package/modules/promises.js +18 -9
- package/modules/text.js +5 -6
- package/modules/validations.js +45 -0
- package/package.json +1 -1
package/modules/cache.js
CHANGED
|
@@ -29,6 +29,11 @@ export function cache( key, value, expires_in_ms=Infinity ) {
|
|
|
29
29
|
log.warn( `The cache key is ${ key }, this may indicate a bug in your cache logic` )
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
if( typeof expires_in_ms !== 'number' ) {
|
|
33
|
+
log.warn( `cache() expires_in_ms must be a number, got ${ typeof expires_in_ms }` )
|
|
34
|
+
expires_in_ms = Infinity
|
|
35
|
+
}
|
|
36
|
+
|
|
32
37
|
// If value is provided, save value and expiration
|
|
33
38
|
if( value !== undefined ) _cache[key] = { value, expires: Date.now() + expires_in_ms }
|
|
34
39
|
|
package/modules/crypto.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @param {string} data - The data to be hashed.
|
|
5
5
|
* @param {string} [algo='sha256'] - The algorithm to be used for hashing. Defaults to 'sha256'.
|
|
6
|
-
* @param {string} [_digest='hex'] - The encoding of the output hash. Defaults to '
|
|
6
|
+
* @param {string} [_digest='hex'] - The encoding of the output hash. Defaults to 'hex'.
|
|
7
7
|
* @returns {Promise<string>} The hashed value of the data.
|
|
8
8
|
*/
|
|
9
9
|
export async function hash( data, algo='sha256', _digest='hex' ) {
|
package/modules/environment.js
CHANGED
|
@@ -117,10 +117,14 @@ env.is_android = () => env.is_web() && navigator.userAgent?.toUpperCase().includ
|
|
|
117
117
|
* Checks if the code is running in a web browser and the platform is iOS.
|
|
118
118
|
* @returns {boolean} True if the code is running in a web browser and the platform is made by Apple
|
|
119
119
|
*/
|
|
120
|
-
env.is_apple = () =>
|
|
120
|
+
env.is_apple = () => {
|
|
121
|
+
if( !env.is_web() ) return false
|
|
122
|
+
const userAgent = navigator.userAgent?.toUpperCase()
|
|
123
|
+
return userAgent?.includes( 'MAC OS X' ) || userAgent?.includes( 'IPHONE' ) || userAgent?.includes( 'IPAD' )
|
|
124
|
+
}
|
|
121
125
|
|
|
122
126
|
/**
|
|
123
|
-
* Checks if the code is running in a web browser and the platform is
|
|
127
|
+
* Checks if the code is running in a web browser and the platform is Linux.
|
|
124
128
|
* @returns {boolean} True if the code is running in a web browser and the platform is Linux, otherwise false.
|
|
125
129
|
*/
|
|
126
130
|
env.is_linux = () => env.is_web() && navigator.userAgent?.toUpperCase().includes( 'LINUX' )
|
|
@@ -165,7 +169,7 @@ export const is_emulator = env.is_emulator()
|
|
|
165
169
|
* Checks if the code is running in a development environment.
|
|
166
170
|
* @returns {boolean} Returns true if the code is running in a development environment, otherwise returns false.
|
|
167
171
|
*/
|
|
168
|
-
export const is_github_actions = typeof process !== 'undefined' && process.env?.GITHUB_ACTIONS
|
|
172
|
+
export const is_github_actions = typeof process !== 'undefined' && process.env?.GITHUB_ACTIONS === 'true'
|
|
169
173
|
|
|
170
174
|
/**
|
|
171
175
|
* @returns {boolean} Returns true if the code is running on an Apple device, otherwise returns false.
|
package/modules/logging.js
CHANGED
|
@@ -109,7 +109,7 @@ export function log( ...messages ) {
|
|
|
109
109
|
if( dev || should_log( levels ) ) {
|
|
110
110
|
|
|
111
111
|
// Annotate the provided messages
|
|
112
|
-
annotate_messages( messages )
|
|
112
|
+
messages = annotate_messages( messages )
|
|
113
113
|
|
|
114
114
|
// Log the messages
|
|
115
115
|
console.log( ...messages )
|
|
@@ -134,7 +134,7 @@ log.info = function( ...messages ) {
|
|
|
134
134
|
if( env.is_emulator() || should_log( levels ) ) {
|
|
135
135
|
|
|
136
136
|
// Annotate the provided messages
|
|
137
|
-
annotate_messages( messages )
|
|
137
|
+
messages = annotate_messages( messages )
|
|
138
138
|
|
|
139
139
|
// Log the messages
|
|
140
140
|
console.info( ...messages )
|
|
@@ -159,7 +159,7 @@ log.warn = function( ...messages ) {
|
|
|
159
159
|
if( dev || should_log( levels ) ) {
|
|
160
160
|
|
|
161
161
|
// Annotate the provided messages
|
|
162
|
-
annotate_messages( messages )
|
|
162
|
+
messages = annotate_messages( messages )
|
|
163
163
|
|
|
164
164
|
// Log the messages
|
|
165
165
|
console.warn( '⚠️ ', ...messages )
|
|
@@ -181,6 +181,9 @@ log.error = function( ...messages ) {
|
|
|
181
181
|
const levels = [ 'error', 'warn', 'info' ]
|
|
182
182
|
if( !should_log( levels ) ) return
|
|
183
183
|
|
|
184
|
+
// Annotate the provided messages
|
|
185
|
+
messages = annotate_messages( messages )
|
|
186
|
+
|
|
184
187
|
// Log the messages if the loglevel matches
|
|
185
188
|
console.error( '🚨 ', ...messages )
|
|
186
189
|
console.trace()
|
package/modules/network.js
CHANGED
|
@@ -6,32 +6,37 @@
|
|
|
6
6
|
* @returns {Object} signal_data - Object containing fetch options and abort signal
|
|
7
7
|
* @returns {Object} signal_data.fetch_options - Options for usage with fetch requests
|
|
8
8
|
* @returns {AbortController} signal_data.controller - Raw abort controller
|
|
9
|
-
* @returns {
|
|
10
|
-
* @
|
|
11
|
-
*
|
|
9
|
+
* @returns {number|null} signal_data.timeout_id - Timeout ID, or null if no timeout was set
|
|
10
|
+
* @example
|
|
11
|
+
* const { fetch_options, controller } = abort_controller( { timeout_ms: 5000 } )
|
|
12
|
+
* fetch( 'https://api.example.com/data', fetch_options )
|
|
13
|
+
* controller.abort() // Abort the request
|
|
12
14
|
*/
|
|
13
15
|
export const abort_controller = ( { timeout_ms }={} ) => {
|
|
14
16
|
|
|
17
|
+
// Input validation
|
|
18
|
+
if( timeout_ms !== undefined && ( typeof timeout_ms !== 'number' || timeout_ms < 0 ) ) {
|
|
19
|
+
throw new Error( 'timeout_ms must be a non-negative number' )
|
|
20
|
+
}
|
|
21
|
+
|
|
15
22
|
// Request with timeout
|
|
16
23
|
const controller = new AbortController()
|
|
17
|
-
const timeout_id = timeout_ms
|
|
24
|
+
const timeout_id = timeout_ms !== undefined ? setTimeout( () => {
|
|
18
25
|
controller.abort()
|
|
19
|
-
}, timeout_ms )
|
|
26
|
+
}, timeout_ms ) : null
|
|
27
|
+
|
|
28
|
+
// Clear timeout when controller is aborted
|
|
29
|
+
controller.signal.addEventListener( 'abort', () => {
|
|
30
|
+
if( timeout_id !== null ) clearTimeout( timeout_id )
|
|
31
|
+
} )
|
|
20
32
|
|
|
21
33
|
const fetch_options = {
|
|
22
34
|
signal: controller.signal
|
|
23
35
|
}
|
|
24
36
|
|
|
25
|
-
const abort = () => {
|
|
26
|
-
if( timeout_ms ) clearTimeout( timeout_id )
|
|
27
|
-
controller.abort()
|
|
28
|
-
}
|
|
29
|
-
|
|
30
37
|
return {
|
|
31
38
|
fetch_options,
|
|
32
39
|
controller,
|
|
33
|
-
abort_signal: abort,
|
|
34
|
-
abort,
|
|
35
40
|
timeout_id
|
|
36
41
|
}
|
|
37
42
|
|
package/modules/promises.js
CHANGED
|
@@ -60,7 +60,7 @@ export async function make_retryable( async_function, { retry_times = 5, cooldow
|
|
|
60
60
|
*
|
|
61
61
|
* @param {Array<Function>} async_function_array - Array of async functions to be throttled and retried.
|
|
62
62
|
* @param {Object} options - Options for throttling and retrying.
|
|
63
|
-
* @param {number} [options.
|
|
63
|
+
* @param {number} [options.max_parallel=2] - Maximum number of functions to run in parallel.
|
|
64
64
|
* @param {number} [options.retry_times] - Number of times to retry each function.
|
|
65
65
|
* @param {number} [options.cooldown_in_s] - Cooldown time in seconds between retries.
|
|
66
66
|
* @param {number} [options.cooldown_entropy] - Random factor to add to the cooldown time.
|
|
@@ -112,17 +112,26 @@ export async function throttle_and_retry( async_function_array = [], { max_paral
|
|
|
112
112
|
*/
|
|
113
113
|
export async function promise_timeout( promise, timeout_in_ms=60_000, throw_on_timeout=true ) {
|
|
114
114
|
|
|
115
|
-
// Timeout
|
|
116
|
-
|
|
115
|
+
// Timeout id placeholder
|
|
116
|
+
let timeout_id
|
|
117
|
+
|
|
118
|
+
// Timeout function that stores the timeout ID so we can clear it
|
|
119
|
+
const timeout = () => new Promise( ( res, rej ) => {
|
|
120
|
+
timeout_id = setTimeout( throw_on_timeout ? rej : () => res( 'timed out' ), timeout_in_ms )
|
|
121
|
+
} )
|
|
117
122
|
|
|
118
123
|
// Race the promise against the timeout
|
|
119
|
-
|
|
124
|
+
try {
|
|
125
|
+
const result = await Promise.race( [
|
|
126
|
+
promise,
|
|
127
|
+
timeout()
|
|
128
|
+
] )
|
|
120
129
|
|
|
121
|
-
|
|
122
|
-
promise,
|
|
130
|
+
return result
|
|
123
131
|
|
|
124
|
-
|
|
125
|
-
timeout
|
|
126
|
-
|
|
132
|
+
} finally {
|
|
133
|
+
// Clear the timeout
|
|
134
|
+
clearTimeout( timeout_id )
|
|
135
|
+
}
|
|
127
136
|
|
|
128
137
|
}
|
package/modules/text.js
CHANGED
|
@@ -22,12 +22,11 @@ export const truncate = ( text, length=100, suffix='...' ) => {
|
|
|
22
22
|
* @param {function} [options.alerter] - The custom function to display the success message.
|
|
23
23
|
* @returns {Promise<void>} - A promise that resolves when the text is successfully copied to the clipboard.
|
|
24
24
|
*/
|
|
25
|
-
export const copy_to_clipboard = async ( text, { success_message='Copied to clipboard', alerter } ) => {
|
|
26
|
-
if( !navigator?.clipboard?.writeText ) return
|
|
25
|
+
export const copy_to_clipboard = async ( text, { success_message='Copied to clipboard', alerter=alert } ) => {
|
|
26
|
+
if( !navigator?.clipboard?.writeText ) return alerter( 'Your browser does not support copying to clipboard' )
|
|
27
27
|
await navigator.clipboard.writeText( text )
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
else alert( success_message )
|
|
29
|
+
alerter( success_message )
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
/**
|
|
@@ -58,12 +57,12 @@ export const random_letter = ( allowed_chars, capitals=true ) => {
|
|
|
58
57
|
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
export const random_string_of_length = ( length, allowed_chars, numbers=true ) => {
|
|
60
|
+
export const random_string_of_length = ( length, allowed_chars, numbers=true, capitals=true ) => {
|
|
62
61
|
|
|
63
62
|
return Array.from( { length }, () => {
|
|
64
63
|
// If numbers are allowed, add a 50% chance of generating a number
|
|
65
64
|
if( numbers && Math.random() > 0.5 ) return `${ random_number_between( 9, 0 ) }`
|
|
66
|
-
return random_letter( allowed_chars )
|
|
65
|
+
return random_letter( allowed_chars, capitals )
|
|
67
66
|
} ).join( '' )
|
|
68
67
|
|
|
69
68
|
}
|
package/modules/validations.js
CHANGED
|
@@ -15,6 +15,51 @@ export const email_regex = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/
|
|
|
15
15
|
*/
|
|
16
16
|
export const sanetise_string = string => `${ string }`.trim().toLowerCase()
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Validates whether a given string is a valid IPv4 address.
|
|
20
|
+
* @param {string} ip - The IP address string to validate.
|
|
21
|
+
* @returns {boolean} True if the IP address is valid, otherwise false.
|
|
22
|
+
*/
|
|
23
|
+
export const is_ipv4 = ip => {
|
|
24
|
+
|
|
25
|
+
// Split the IP address into its components and make then numbers
|
|
26
|
+
const octets = ip.split( '.' ).map( octet => parseInt( octet, 10 ) )
|
|
27
|
+
|
|
28
|
+
// Check if the IP address has 4 octets and each octet is between 0 and 255
|
|
29
|
+
return octets.length === 4 && octets.every( octet => !isNaN( octet ) && octet >= 0 && octet <= 255 )
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Sanitizes and optionally validates an IPv4 address.
|
|
35
|
+
* @param {Object} options - The options object.
|
|
36
|
+
* @param {string} options.ip - The IP address to sanitize.
|
|
37
|
+
* @param {boolean} [options.validate=false] - Whether to validate the IP address.
|
|
38
|
+
* @param {boolean} [options.error_on_invalid=false] - Whether to throw an error on an invalid IP.
|
|
39
|
+
* @returns {string|null} The sanitized IPv4 address, or null if validation fails and error_on_invalid is false.
|
|
40
|
+
* @throws {Error} If the IP address is not provided or invalid when error_on_invalid is true.
|
|
41
|
+
*/
|
|
42
|
+
export const sanetise_ipv4 = ( { ip, validate=false, error_on_invalid=false } ) => {
|
|
43
|
+
|
|
44
|
+
// Ensure ip was provided
|
|
45
|
+
if( !ip ) throw new Error( 'IP address is required' )
|
|
46
|
+
|
|
47
|
+
// Sanetise ip address as string
|
|
48
|
+
ip = sanetise_string( ip )
|
|
49
|
+
|
|
50
|
+
// Remove ipv6 prefix if present
|
|
51
|
+
ip = ip.replace( '::ffff:', '' )
|
|
52
|
+
|
|
53
|
+
// Check if the IP address is valid
|
|
54
|
+
if( validate && !is_ipv4( ip ) ) {
|
|
55
|
+
if( error_on_invalid ) throw new Error( `Invalid IPv4 address: ${ ip }` )
|
|
56
|
+
return null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Return the sanitized IP address
|
|
60
|
+
return ip
|
|
61
|
+
|
|
62
|
+
}
|
|
18
63
|
|
|
19
64
|
/**
|
|
20
65
|
* Checks if an object contains all the required properties.
|