@mcmhomes/panorama-viewer
Version:
Provides React components to render panoramas.
1,150 lines (1,063 loc) • 27 kB
JSX
/**
* Returns true if the value is set (not undefined and not null).
*
* @param {*} value
* @returns {boolean}
*/
export const ISSET = (value) => (typeof value !== 'undefined') && (value !== null);
/**
* Returns true if the value is an array.
*
* @param {*} value
* @returns {boolean}
*/
export const IS_ARRAY = (value) => Array.isArray(value);
/**
* Ensures the given value is an array (returns the value wrapped in an array if it's not).
*
* @param {*} value
* @returns {*[]}
*/
export const ARRAY = (value) => IS_ARRAY(value) ? value : ((typeof value !== 'undefined') ? [value] : []);
/**
* Returns true if the value is an object.
*
* @param {*} value
* @returns {boolean}
*/
export const IS_OBJECT = (value) => (typeof value === 'object') && (value !== null) && !Array.isArray(value);
/**
* Ensures the given value is an object (returns an empty object if it's not).
*
* @param value
* @returns {object}
*/
export const OBJECT = (value) => IS_OBJECT(value) ? value : {};
/**
* Ensures the given value is a string (casts it to a string if it's not, null and undefined will return an empty string).
*
* @param {*} value
* @returns {string}
*/
export const STRING = (value) => ISSET(value) ? ('' + value) : '';
/**
* Returns the first non-null non-undefined value as a string.
*
* @param {*} values
* @returns {string}
*/
export const STRING_ANY = (...values) =>
{
for(let value of values)
{
if(ISSET(value))
{
return '' + value;
}
}
return '';
};
/**
* Ensures the given value is an integer (attempts to cast it to an integer if it's not, null and undefined will return 0).
*
* @param {*} value
* @returns {number}
*/
export const INT = (value) => Math.round(FLOAT(value));
/**
* Returns the first non-null non-undefined int-castable value as an integer.
*
* @param {*} values
* @returns {number}
*/
export const INT_ANY = (...values) => Math.round(FLOAT_ANY(...values));
/**
* Ensures the given value is a float (attempts to cast it to a float if it's not, null and undefined will return 0).
*
* @param {*} value
* @returns {number}
*/
export const FLOAT = (value) =>
{
const v = +value;
if(!isNaN(v))
{
return v;
}
return 0;
};
/**
* Returns the first non-null non-undefined float-castable value as a float.
*
* @param {*} values
* @returns {number}
*/
export const FLOAT_ANY = (...values) =>
{
for(let value of values)
{
if(value !== null)
{
const v = +value;
if(!isNaN(v))
{
return v;
}
}
}
return 0;
};
/**
* Ensures the given value is an integer (attempts to cast it to an integer if it's not, null and undefined will return 0).
* This version is less strict than INT, as it relies on parseFloat instead of on +value, meaning that it will accept strings that contain a number followed by other characters, which +value doesn't.
*
* @param {*} value
* @returns {number}
*/
export const INT_LAX = (value) => Math.round(FLOAT_LAX(value));
/**
* Returns the first non-null non-undefined int-castable value as an integer.
* This version is less strict than INT_ANY, as it relies on parseFloat instead of on +value, meaning that it will accept strings that contain a number followed by other characters, which +value doesn't.
*
* @param {*} values
* @returns {number}
*/
export const INT_LAX_ANY = (...values) => Math.round(FLOAT_LAX_ANY(...values));
const REGEX_ALL_NON_FLOAT_CHARACTERS = /[^0-9.\-]/g;
/**
* Ensures the given value is a float (attempts to cast it to a float if it's not, null and undefined will return 0).
* This version is less strict than FLOAT, as it relies on parseFloat instead of on +value, meaning that it will accept strings that contain a number followed by other characters, which +value doesn't.
*
* @param {*} value
* @returns {number}
*/
export const FLOAT_LAX = (value) =>
{
const v = (typeof value === 'number') ? value : parseFloat((value + '').replace(REGEX_ALL_NON_FLOAT_CHARACTERS, ''));
if(!isNaN(v))
{
return v;
}
return 0;
};
/**
* Returns the first non-null non-undefined float-castable value as a float.
* This version is less strict than FLOAT_ANY, as it relies on parseFloat instead of on +value, meaning that it will accept strings that contain a number followed by other characters, which +value doesn't.
*
* @param {*} values
* @returns {number}
*/
export const FLOAT_LAX_ANY = (...values) =>
{
for(let value of values)
{
if(value !== null)
{
const v = (typeof value === 'number') ? value : parseFloat((value + '').replace(REGEX_ALL_NON_FLOAT_CHARACTERS, ''));
if(!isNaN(v))
{
return v;
}
}
}
return 0;
};
/**
* Loops through each element in the given array or object, and calls the callback for each element.
*
* @param {*[]|object|Function} elements
* @param {Function} callback
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
* @returns {*[]|object|Function}
*/
export const each = (elements, callback, optionalSkipHasOwnPropertyCheck = false) =>
{
if((elements !== null) && (typeof elements !== 'undefined'))
{
if(Array.isArray(elements))
{
for(let index = 0; index < elements.length; index++)
{
if(callback.call(elements[index], elements[index], index) === false)
{
break;
}
}
}
else if((typeof elements === 'object') || (typeof elements === 'function'))
{
for(let index in elements)
{
if((optionalSkipHasOwnPropertyCheck === true) || Object.prototype.hasOwnProperty.call(elements, index))
{
if(callback.call(elements[index], elements[index], index) === false)
{
break;
}
}
}
}
else
{
console.warn('Executed each() on an invalid type: [' + (typeof elements) + ']', elements);
}
}
return elements;
};
/**
* Returns true if the array or object contains the given value.
*
* Values are compared by casting both of them to a string.
*
* @param {array|object|Function} array
* @param {*} value
* @returns {boolean}
*/
export const contains = (array, value) =>
{
if(!array)
{
return false;
}
let result = false;
value = STRING(value);
each(array, (val) =>
{
if(STRING(val) === value)
{
result = true;
return false;
}
});
return result;
};
/**
* @callback __filterCallback
* @param {*} value
* @param {*} index
* @returns {boolean|undefined}
*/
/**
* Loops through the given elements, and returns a new array or object, with only the elements that returned true (or a value equals to true) from the callback.
* If no callback is given, it will return all elements that are of a true value (for example, values that are: not null, not undefined, not false, not 0, not an empty string, not an empty array, not an empty object).
*
* @param {*[]|object|Function} elements
* @param {__filterCallback} [callback]
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
* @returns {*[]|object|Function}
*/
export const filter = (elements, callback, optionalSkipHasOwnPropertyCheck = false) =>
{
if((elements !== null) && (typeof elements !== 'undefined'))
{
if(Array.isArray(elements))
{
let result = [];
for(let index = 0; index < elements.length; index++)
{
if((!callback && elements[index]) || (callback && callback.call(elements[index], elements[index], index)))
{
result.push(elements[index]);
}
}
return result;
}
else if((typeof elements === 'object') || (typeof elements === 'function'))
{
let result = {};
for(let index in elements)
{
if((optionalSkipHasOwnPropertyCheck === true) || Object.prototype.hasOwnProperty.call(elements, index))
{
if((!callback && elements[index]) || (callback && callback.call(elements[index], elements[index], index)))
{
result[index] = elements[index];
}
}
}
return result;
}
else
{
console.warn('Executed filter() on an invalid type: [' + (typeof elements) + ']', elements);
}
}
return elements;
};
/**
* Generates a base64 string (with +/ replaced by -_) that is guaranteed to be unique.
*
* @returns {string}
*/
export const uniqueId = (() =>
{
let previousUniqueIdsTime = null;
let previousUniqueIds = {};
const safeAtob = (base64string) =>
{
if(typeof atob === 'function')
{
return atob(base64string);
}
return window.Buffer.from(base64string, 'base64').toString();
};
const safeBtoa = (string) =>
{
if(typeof btoa === 'function')
{
return btoa(string);
}
return window.Buffer.from(string).toString('base64');
};
const numberToBytes = (number) =>
{
const size = (number === 0) ? 0 : Math.ceil((Math.floor(Math.log2(number)) + 1) / 8);
const bytes = new Uint8ClampedArray(size);
let x = number;
for(let i = (size - 1); i >= 0; i--)
{
const rightByte = x & 0xff;
bytes[i] = rightByte;
x = Math.floor(x / 0x100);
}
return bytes;
};
const base64ToBytes = (base64string) =>
{
const binary = safeAtob(base64string.trim());
const len = binary.length;
let data = new Uint8Array(len);
for(let i = 0; i < len; i++)
{
data[i] = binary.charCodeAt(i);
}
return data;
};
const hexToBase64 = (hexstring) =>
{
return safeBtoa(hexstring.replace(/[^0-9A-F]/gi, '').match(/\w{2}/g).map((a) => String.fromCharCode(parseInt(a, 16))).join(''));
};
const bytesToBase64 = (arraybuffer) =>
{
const bytes = new Uint8Array(arraybuffer);
const len = bytes.byteLength;
let binary = '';
for(let i = 0; i < len; i++)
{
binary += String.fromCharCode(bytes[i]);
}
return safeBtoa(binary);
};
const generateUniqueId = () =>
{
let now;
try
{
if(typeof window === 'undefined')
{
throw new Error();
}
// noinspection JSDeprecatedSymbols
now = (performance.timeOrigin || performance.timing.navigationStart) + performance.now();
if(typeof now !== 'number')
{
throw new Error();
}
}
catch(e)
{
now = (Date.now ? Date.now() : (new Date()).getTime());
}
now = Math.round(now);
if(typeof window === 'undefined')
{
let uuid = null;
try
{
uuid = crypto?.randomUUID();
}
catch(e)
{
uuid = '';
uuid += (Math.random() + ' ').substring(2, 12).padEnd(10, '0');
uuid += (Math.random() + ' ').substring(2, 12).padEnd(10, '0');
uuid += (Math.random() + ' ').substring(2, 12).padEnd(10, '0');
uuid += (Math.random() + ' ').substring(2, 12).padEnd(10, '0');
}
return {
time:now,
id: ('' + now + uuid).replaceAll('=', '').replaceAll('+', '-').replaceAll('/', '_'),
};
}
const nowBytes = numberToBytes(now);
let uuid = null;
try
{
uuid = crypto?.randomUUID();
}
catch(e)
{
}
if(uuid)
{
uuid = base64ToBytes(hexToBase64(uuid));
}
else
{
const bytesChunkA = numberToBytes((Math.random() + ' ').substring(2, 12).padEnd(10, '0'));
const bytesChunkB = numberToBytes((Math.random() + ' ').substring(2, 12).padEnd(10, '0'));
const bytesChunkC = numberToBytes((Math.random() + ' ').substring(2, 12).padEnd(10, '0'));
const bytesChunkD = numberToBytes((Math.random() + ' ').substring(2, 12).padEnd(10, '0'));
uuid = new Uint8Array(bytesChunkA.length + bytesChunkB.length + bytesChunkC.length + bytesChunkD.length);
uuid.set(bytesChunkA, 0);
uuid.set(bytesChunkB, bytesChunkA.length);
uuid.set(bytesChunkC, bytesChunkA.length + bytesChunkB.length);
uuid.set(bytesChunkD, bytesChunkA.length + bytesChunkB.length + bytesChunkC.length);
}
const bytes = new Uint8Array(nowBytes.length + uuid.length);
bytes.set(nowBytes, 0);
bytes.set(uuid, nowBytes.length);
uuid = bytesToBase64(bytes).replaceAll('=', '').replaceAll('+', '-').replaceAll('/', '_');
return {
time:now,
id: uuid,
};
};
return () =>
{
while(true)
{
const result = generateUniqueId();
if(previousUniqueIdsTime !== result.time)
{
previousUniqueIdsTime = result.time;
previousUniqueIds = {[result.id]:true};
return result.id;
}
else if(previousUniqueIds[result.id] !== true)
{
previousUniqueIds[result.id] = true;
return result.id;
}
}
};
})();
/**
* @callback __setTimeoutCallback
* @param {number} deltaTime
*/
/**
* Executes the callback after the given number of milliseconds. Passes the elapsed time in seconds to the callback.
*
* To cancel the timeout, call remove() on the result of this function (example: "const timeoutHandler = setTimeout((deltaTime)=>{}, 1000); timeoutHandler.remove();")
*
* @param {__setTimeoutCallback} callback ([number] deltaTime)
* @param {number} ms
* @returns {{remove:Function}}
*/
export const setTimeoutRemovable = (callback, ms) =>
{
if(typeof window === 'undefined')
{
return {
remove:() =>
{
},
};
}
ms = FLOAT_LAX(ms);
let lastTime = performance.now();
/** @type {number|null} */
let handler = window.setTimeout(() =>
{
const currentTime = performance.now();
try
{
callback((currentTime - lastTime) / 1000);
}
catch(e)
{
console.error(e);
}
lastTime = currentTime;
}, ms);
return {
remove:
() =>
{
if(handler !== null)
{
window.clearTimeout(handler);
handler = null;
}
},
};
};
/**
* @callback __setIntervalCallback
* @param {number} deltaTime
*/
/**
* Executes the callback every given number of milliseconds. Passes the time difference in seconds between the last frame and now to it.
*
* To remove the interval, call remove() on the result of this function (example: "const intervalHandler = setInterval((deltaTime)=>{}, 1000); intervalHandler.remove();")
*
* @param {__setIntervalCallback} callback ([number] deltaTime)
* @param {number} [intervalMs]
* @param {boolean} [fireImmediately]
* @returns {{remove:Function}}
*/
export const setIntervalRemovable = (callback, intervalMs = 1000, fireImmediately = false) =>
{
intervalMs = FLOAT_LAX_ANY(intervalMs, 1000);
if(fireImmediately)
{
try
{
callback(0);
}
catch(e)
{
console.error(e);
}
}
if(typeof window === 'undefined')
{
return {
remove:() =>
{
},
};
}
let lastTime = performance.now();
/** @type {number|null} */
let handler = window.setInterval(() =>
{
const currentTime = performance.now();
try
{
callback((currentTime - lastTime) / 1000);
}
catch(e)
{
console.error(e);
}
lastTime = currentTime;
}, intervalMs);
return {
remove:
() =>
{
if(handler !== null)
{
window.clearInterval(handler);
handler = null;
}
},
};
};
/**
* @callback __setAnimationFrameTimeoutCallback
* @param {number} deltaTime
*/
/**
* Executes the callback after the given number of frames. Passes the elapsed time in seconds to the callback.
*
* To cancel the timeout, call remove() on the result of this function (example: "const timeoutHandler = setAnimationFrameTimeout((deltaTime){}, 5); timeoutHandler.remove();")
*
* @param {__setAnimationFrameTimeoutCallback} callback ([number] deltaTime)
* @param {number} [frames]
* @returns {{remove:Function}}
*/
export const setAnimationFrameTimeoutRemovable = (callback, frames = 1) =>
{
if(typeof window === 'undefined')
{
return {
remove:() =>
{
},
};
}
frames = INT_LAX_ANY(frames, 1);
let run = true;
let requestAnimationFrameId = null;
let lastTime = performance.now();
const tick = () =>
{
if(run)
{
if(frames <= 0)
{
run = false;
requestAnimationFrameId = null;
const currentTime = performance.now();
try
{
callback((currentTime - lastTime) / 1000);
}
catch(e)
{
console.error(e);
}
lastTime = currentTime;
return;
}
frames--;
requestAnimationFrameId = window.requestAnimationFrame(tick);
}
};
tick();
return {
remove:
() =>
{
run = false;
if(requestAnimationFrameId !== null)
{
window.cancelAnimationFrame(requestAnimationFrameId);
requestAnimationFrameId = null;
}
},
};
};
/**
* @callback __setAnimationFrameIntervalCallback
* @param {number} deltaTime
*/
/**
* Executes the callback every given number of frames. Passes the time difference in seconds between the last frame and now to it.
*
* To remove the interval, call remove() on the result of this function (example: "const intervalHandler = setAnimationFrameInterval((deltaTime)=>{}, 5); intervalHandler.remove();")
*
* @param {__setAnimationFrameIntervalCallback} callback ([number] deltaTime)
* @param {number} [intervalFrames]
* @param {boolean} [fireImmediately]
* @returns {{remove:Function}}
*/
export const setAnimationFrameIntervalRemovable = (callback, intervalFrames = 1, fireImmediately = false) =>
{
intervalFrames = INT_LAX_ANY(intervalFrames, 1);
if(fireImmediately)
{
try
{
callback(0);
}
catch(e)
{
console.error(e);
}
}
if(typeof window === 'undefined')
{
return {
remove:() =>
{
},
};
}
let run = true;
let requestAnimationFrameId = null;
let lastTime = performance.now();
let frames = intervalFrames;
const tick = () =>
{
if(run)
{
if(frames <= 0)
{
let currentTime = performance.now();
try
{
callback((currentTime - lastTime) / 1000);
}
catch(e)
{
console.error(e);
}
lastTime = currentTime;
frames = intervalFrames;
}
frames--;
if(run)
{
requestAnimationFrameId = window.requestAnimationFrame(tick);
}
}
};
window.requestAnimationFrame(tick);
return {
remove:
() =>
{
run = false;
if(requestAnimationFrameId !== null)
{
window.cancelAnimationFrame(requestAnimationFrameId);
requestAnimationFrameId = null;
}
},
};
};
/**
* Returns a promise, which will be resolved after the given number of milliseconds.
*
* @param {number} ms
* @returns {Promise}
*/
export const promiseTimeout = (ms) =>
{
ms = FLOAT_LAX(ms);
if(ms <= 0)
{
return new Promise(resolve => resolve(undefined));
}
return new Promise(resolve => setTimeoutRemovable(resolve, ms));
};
/**
* Returns a promise, which will be resolved after the given number of frames.
*
* @param {number} frames
* @returns {Promise}
*/
export const promiseAnimationFrameTimeout = (frames) =>
{
frames = INT_LAX(frames);
if(frames <= 0)
{
return new Promise(resolve => resolve(undefined));
}
return new Promise(resolve => setAnimationFrameTimeoutRemovable(resolve, frames));
};
/**
* Allows you to do a fetch, with built-in retry and abort functionality.
*
* @param {string} url
* @param {Object} [options]
* @returns {{then:Function, catch:Function, finally:Function, remove:Function, isRemoved:Function}|Promise<Response|any>}
*/
export const retryingFetch = (url, options) =>
{
let currentRetries = 0;
const retries = INT_LAX(options?.retries);
let controllerAborted = false;
let controller = null;
if((typeof window !== 'undefined') && (typeof window.AbortController !== 'undefined'))
{
controller = new AbortController();
}
let promise = (async () =>
{
const attemptFetch = async () =>
{
if(controllerAborted || controller?.signal?.aborted)
{
throw new Error('Aborted');
}
try
{
const response = await fetch(url, {
signal:controller?.signal,
...(options ?? {}),
retries:undefined,
delay: undefined,
});
if(!response.ok)
{
throw new Error('Network request failed: ' + response.status + ' ' + response.statusText);
}
return response;
}
catch(error)
{
if(controllerAborted || controller?.signal?.aborted)
{
throw new Error('Aborted');
}
if(currentRetries >= retries)
{
throw error;
}
currentRetries++;
await promiseTimeout((typeof options?.delay === 'function') ? INT_LAX_ANY(options?.delay(currentRetries), 500) : (INT_LAX_ANY(options?.delay, 500)));
return await attemptFetch();
}
};
return await attemptFetch();
})();
let result = {};
result.then = (...args) =>
{
promise = promise.then(...args);
return result;
};
result.catch = (...args) =>
{
promise = promise.catch(...args);
return result;
};
result.finally = (...args) =>
{
promise = promise.finally(...args);
return result;
};
result.remove = (...args) =>
{
controllerAborted = true;
if(controller)
{
controller.abort(...args);
}
return result;
};
result.isRemoved = () => (controllerAborted || !!controller?.signal?.aborted);
return result;
};
/**
* @callback __mapToArrayCallback
* @param {*} value
* @param {*} index
* @returns {*}
*/
/**
* Loops through the given elements, and returns a new array, with the elements that were returned from the callback. Always returns an array.
*
* @param {*[]|object|Function} elements
* @param {__mapToArrayCallback} [callback]
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
* @returns {*[]}
*/
export const mapToArray = (elements, callback, optionalSkipHasOwnPropertyCheck = false) =>
{
let result = [];
if((elements !== null) && (typeof elements !== 'undefined'))
{
if(Array.isArray(elements))
{
for(let index = 0; index < elements.length; index++)
{
if(!callback)
{
result.push(elements[index]);
}
else
{
result.push(callback.call(elements[index], elements[index], index));
}
}
}
else if((typeof elements === 'object') || (typeof elements === 'function'))
{
for(let index in elements)
{
if((optionalSkipHasOwnPropertyCheck === true) || Object.prototype.hasOwnProperty.call(elements, index))
{
if(!callback)
{
result.push(elements[index]);
}
else
{
result.push(callback.call(elements[index], elements[index], index));
}
}
}
}
else
{
console.warn('Executed mapToArray() on an invalid type: [' + (typeof elements) + ']', elements);
}
}
return result;
};
/**
* @callback __mapCallback
* @param {*} value
* @param {*} index
* @returns {*}
*/
/**
* Loops through the given elements, and returns a new array or object, with the elements that were returned from the callback.
*
* @param {*[]|object|Function} elements
* @param {__mapCallback} [callback]
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
* @returns {*[]|object|Function}
*/
export const map = (elements, callback, optionalSkipHasOwnPropertyCheck = false) =>
{
if((elements !== null) && (typeof elements !== 'undefined'))
{
if(Array.isArray(elements))
{
let result = [];
for(let index = 0; index < elements.length; index++)
{
if(!callback)
{
result[index] = elements[index];
}
else
{
result[index] = callback.call(elements[index], elements[index], index);
}
}
return result;
}
else if((typeof elements === 'object') || (typeof elements === 'function'))
{
let result = {};
for(let index in elements)
{
if((optionalSkipHasOwnPropertyCheck === true) || Object.prototype.hasOwnProperty.call(elements, index))
{
if(!callback)
{
result[index] = elements[index];
}
else
{
result[index] = callback.call(elements[index], elements[index], index);
}
}
}
return result;
}
else
{
console.warn('Executed map() on an invalid type: [' + (typeof elements) + ']', elements);
}
}
return elements;
};
/**
* @callback __findIndexValueCallback
* @param {*} value
* @param {*} index
* @returns {boolean|undefined}
*/
/**
* Finds the first element in the given array or object that returns true from the callback, and returns an object with the index and value.
*
* @param {*[]|object|Function} elements
* @param {__findIndexValueCallback} callback
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
* @returns {{index:*, value:*}|null}
*/
export const findIndexValue = (elements, callback, optionalSkipHasOwnPropertyCheck = false) =>
{
let result = null;
each(elements, (value, index) =>
{
if(callback.call(elements[index], elements[index], index))
{
result = {index, value};
return false;
}
});
return result;
};
/**
* Finds the first element in the given array or object that returns true from the callback, and returns the index.
*
* @param {*[]|object|Function} elements
* @param {__findIndexValueCallback} callback
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
* @returns {*|null}
*/
export const findIndex = (elements, callback, optionalSkipHasOwnPropertyCheck = false) => findIndexValue(elements, callback, optionalSkipHasOwnPropertyCheck)?.index ?? null;
/**
* Finds the first element in the given array or object that returns true from the callback, and returns the value.
*
* @param {*[]|object|Function} elements
* @param {__findIndexValueCallback} callback
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
* @returns {*|null}
*/
export const find = (elements, callback, optionalSkipHasOwnPropertyCheck = false) => findIndexValue(elements, callback, optionalSkipHasOwnPropertyCheck)?.value ?? null;
/**
* Attempts to obtain and return an error message from the given error, regardless of what is passed to this function.
*
* @param {*} error
* @returns {string}
*/
export const purgeErrorMessage = (error) =>
{
if(error?.message)
{
error = error.message;
}
if(typeof error === 'string')
{
const errorParts = error.split('threw an error:');
error = errorParts[errorParts.length - 1];
}
else
{
try
{
error = JSON.stringify(error);
}
catch(e)
{
error = 'An unknown error occurred';
}
}
return error.trim();
};
/**
* Returns a data URL of a 1x1 transparent pixel.
*
* @returns {string}
*/
export const getEmptyImageSrc = () =>
{
// noinspection SpellCheckingInspection
return 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
};
/**
* Returns true if the given hostname is private (such as localhost, 192.168.1.1, etc).
*
* Only "localhost" and IPv4 addresses are supported. IPv6 addresses will always return false.
*
* @param {string} host
* @returns {boolean}
*/
const isGivenHostPrivate = (host) =>
{
host = STRING(host).trim().toLowerCase();
if((host === 'localhost') || (host === '127.0.0.1'))
{
return true;
}
if(!/^(\d{1,3}\.){3}\d{1,3}$/.test(host))
{
return false;
}
const parts = host.split('.');
return (parts[0] === '10') || // 10.0.0.0 - 10.255.255.255
((parts[0] === '172') && ((parseInt(parts[1], 10) >= 16) && (parseInt(parts[1], 10) <= 31))) || // 172.16.0.0 - 172.31.255.255
((parts[0] === '192') && (parts[1] === '168')); // 192.168.0.0 - 192.168.255.255
};
/**
* Returns true if the given host is a private URL (like localhost, 127.0.0.1, etc).
*
* @param {string} host
* @returns {boolean}
*/
export const isHostPrivate = (() =>
{
const cache = {};
return (host) =>
{
if(!(host in cache))
{
let hostLower = host.toLowerCase().trim();
hostLower = hostLower.replace(/^[a-z]+:\/\//, '');
hostLower = hostLower.replace(/[:/].*$/, '');
cache[host] = isGivenHostPrivate(hostLower);
}
return cache[host];
};
})();