homebridge-adt-pulse
Version:
Homebridge security system platform for ADT Pulse
806 lines • 30.5 kB
JavaScript
import { createHash } from 'node:crypto';
import { createRequire } from 'node:module';
import os from 'node:os';
import util from 'node:util';
import axios from 'axios';
import chalk from 'chalk';
import { JSDOM } from 'jsdom';
import _ from 'lodash';
import { DateTime } from 'luxon';
import semver from 'semver';
import { collectionDoSubmitHandlers, collectionOrbSecurityButtons, deviceGateways, deviceSecurityPanels, itemPanelStatusNotes, itemPanelStatusStates, itemPanelStatusStatuses, } from './items.js';
import { characterBackslashForwardSlash, characterHtmlLineBreak, characterWhitespace, functionDoSubmit, functionGoToUrl, functionSetArmState, paramSat, textOrbSensorZone, textOrbTextSummarySections, textSyncCode, } from './regex.js';
export function clearHtmlLineBreak(data) {
return data.replace(characterHtmlLineBreak, ' ').trim();
}
export function clearWhitespace(data) {
return data.replace(characterWhitespace, ' ').trim();
}
export function condensePanelStates(characteristic, panelStates) {
let condensed;
switch (true) {
case panelStates.includes('Armed Away'):
condensed = {
armValue: 'away',
characteristicValue: {
current: characteristic.SecuritySystemCurrentState.AWAY_ARM,
target: characteristic.SecuritySystemTargetState.AWAY_ARM,
},
};
break;
case panelStates.includes('Armed Night'):
condensed = {
armValue: 'night',
characteristicValue: {
current: characteristic.SecuritySystemCurrentState.NIGHT_ARM,
target: characteristic.SecuritySystemTargetState.NIGHT_ARM,
},
};
break;
case panelStates.includes('Armed Stay'):
condensed = {
armValue: 'stay',
characteristicValue: {
current: characteristic.SecuritySystemCurrentState.STAY_ARM,
target: characteristic.SecuritySystemTargetState.STAY_ARM,
},
};
break;
case panelStates.includes('Disarmed'):
condensed = {
armValue: 'off',
characteristicValue: {
current: characteristic.SecuritySystemCurrentState.DISARMED,
target: characteristic.SecuritySystemTargetState.DISARM,
},
};
break;
default:
break;
}
return condensed;
}
export function condenseSensorType(sensorType) {
let condensed;
switch (sensorType) {
case 'Carbon Monoxide Detector':
condensed = 'co';
break;
case 'Door/Window Sensor':
case 'Door Sensor':
case 'Window Sensor':
condensed = 'doorWindow';
break;
case 'Fire (Smoke/Heat) Detector':
condensed = 'fire';
break;
case 'Water/Flood Sensor':
condensed = 'flood';
break;
case 'Glass Break Detector':
condensed = 'glass';
break;
case 'Heat (Rate-of-Rise) Detector':
condensed = 'heat';
break;
case 'Motion Sensor':
case 'Motion Sensor (Notable Events Only)':
condensed = 'motion';
break;
case 'Shock Sensor':
condensed = 'shock';
break;
case 'Temperature Sensor':
condensed = 'temperature';
break;
default:
break;
}
return condensed;
}
export function convertPanelCharacteristicValue(mode, characteristic, value) {
switch (true) {
case mode === 'current-to-target' && value === characteristic.SecuritySystemCurrentState.AWAY_ARM:
return characteristic.SecuritySystemTargetState.AWAY_ARM;
case mode === 'current-to-target' && value === characteristic.SecuritySystemCurrentState.STAY_ARM:
return characteristic.SecuritySystemTargetState.STAY_ARM;
case mode === 'current-to-target' && value === characteristic.SecuritySystemCurrentState.NIGHT_ARM:
return characteristic.SecuritySystemTargetState.NIGHT_ARM;
case mode === 'current-to-target' && value === characteristic.SecuritySystemCurrentState.DISARMED:
return characteristic.SecuritySystemTargetState.DISARM;
case mode === 'target-to-current' && value === characteristic.SecuritySystemTargetState.AWAY_ARM:
return characteristic.SecuritySystemCurrentState.AWAY_ARM;
case mode === 'target-to-current' && value === characteristic.SecuritySystemTargetState.STAY_ARM:
return characteristic.SecuritySystemCurrentState.STAY_ARM;
case mode === 'target-to-current' && value === characteristic.SecuritySystemTargetState.NIGHT_ARM:
return characteristic.SecuritySystemCurrentState.NIGHT_ARM;
case mode === 'target-to-current' && value === characteristic.SecuritySystemTargetState.DISARM:
return characteristic.SecuritySystemCurrentState.DISARMED;
default:
return undefined;
}
}
export function debugLog(logger, caller, type, message) {
const logMessage = chalk.reset(chalk.underline(caller), '-', message, '...');
switch (type) {
case 'error':
if (logger !== null) {
logger.error(chalk.gray('DEBUG'), chalk.redBright('ERROR:'), logMessage);
}
else {
console.error('[ADT Pulse]', chalk.redBright('ERROR:'), logMessage);
}
break;
case 'warn':
if (logger !== null) {
logger.warn(chalk.gray('DEBUG'), chalk.yellowBright('WARNING:'), logMessage);
}
else {
console.warn('[ADT Pulse]', chalk.yellowBright('WARNING:'), logMessage);
}
break;
case 'success':
if (logger !== null) {
logger.info(chalk.gray('DEBUG'), chalk.greenBright('SUCCESS:'), logMessage);
}
else {
console.info('[ADT Pulse]', chalk.greenBright('SUCCESS:'), logMessage);
}
break;
case 'info':
if (logger !== null) {
logger.info(chalk.gray('DEBUG'), chalk.blueBright('INFO:'), logMessage);
}
else {
console.info('[ADT Pulse]', chalk.blueBright('INFO:'), logMessage);
}
break;
default:
break;
}
}
export function fetchErrorMessage(response) {
if (response === undefined || typeof response.data !== 'string') {
return null;
}
const jsdom = new JSDOM(response.data, {
url: response.config.url,
referrer: response.config.headers.Referer,
contentType: 'text/html',
pretendToBeVisual: true,
});
const warnMessage = jsdom.window.document.querySelector('#warnMsgContents');
if (warnMessage !== null) {
return clearWhitespace(clearHtmlLineBreak(warnMessage.innerHTML));
}
return null;
}
export function fetchMissingSatCode(response) {
if (typeof response.data !== 'string') {
return null;
}
const satCode = response.data.match(paramSat);
if (satCode !== null && satCode.length >= 2) {
return satCode[1];
}
return null;
}
export function fetchTableCells(nodeElements, matchItems, incrementFrom, incrementTo) {
const matched = {};
let newIncrementFrom = incrementFrom;
let newIncrementTo = incrementTo;
if (incrementFrom < 0) {
newIncrementFrom = 0;
}
if (incrementTo < incrementFrom) {
newIncrementTo = incrementFrom;
}
nodeElements.forEach((nodeElement, nodeElementKey) => {
const currentNode = nodeElement.textContent;
if (currentNode === null) {
return;
}
const currentNodeCleaned = clearWhitespace(currentNode);
const collectedNodes = [];
if (!matchItems.includes(currentNodeCleaned)) {
return;
}
for (let i = newIncrementFrom; i <= newIncrementTo; i += 1) {
const incrementedNode = nodeElements[nodeElementKey + i].textContent;
if (incrementedNode !== null) {
const incrementedNodeCleaned = clearWhitespace(incrementedNode);
if (incrementedNodeCleaned !== '') {
collectedNodes.push(incrementedNodeCleaned);
}
}
}
if (collectedNodes.length > 0) {
matched[currentNodeCleaned] = collectedNodes;
}
});
return matched;
}
export function findGatewayManufacturerModel(mode, manufacturer, model) {
let newManufacturer = manufacturer;
let newModel = model;
switch (true) {
case manufacturer === 'ADT Pulse Gateway' && model === 'iHub-3001':
newManufacturer = 'iControl';
newModel = 'ADT Pulse Gateway iHub-3001';
break;
case manufacturer === 'ADT Pulse Gateway' && model === 'PGZNG1':
newManufacturer = 'NETGEAR';
newModel = 'ADT Pulse Gateway PGZNG1';
break;
case manufacturer === null && model === 'Compact SMA Protocol Gateway':
newManufacturer = 'iControl';
newModel = 'Compact SMA Protocol Gateway';
break;
case manufacturer === null && model === 'Lynx/QuickConnect Cellular-Only Gateway':
newManufacturer = 'Ademco/ADT';
newModel = 'Lynx/QuickConnect Cellular-Only Gateway';
break;
default:
break;
}
return (mode === 'manufacturer') ? newManufacturer : newModel;
}
export function findIndexWithValue(array, condition) {
let index = -1;
let value;
for (let i = 0; i < array.length; i += 1) {
if (condition(array[i])) {
index = i;
value = array[i];
break;
}
}
return {
index,
value,
};
}
export function findNullKeys(properties, parentKey = '') {
const found = [];
Object.entries(properties).forEach(([propertyKey, property]) => {
const currentKey = (parentKey !== '') ? `${parentKey}.${propertyKey}` : propertyKey;
if (_.isPlainObject(property)) {
found.push(...findNullKeys(property, currentKey));
}
else if (property == null) {
found.push(currentKey);
}
});
return found;
}
export function findPanelManufacturer(manufacturerProvider, typeModel) {
if (manufacturerProvider !== null) {
return manufacturerProvider;
}
switch (typeModel) {
case 'Security Panel - LYNX/QuickConnect':
return 'Ademco/ADT';
default:
return null;
}
}
export function generateHash(data) {
const staticData = _.omit(data, [
'masterCode',
'network.broadband.ip',
'network.broadband.mac',
'network.device.ip',
'network.device.mac',
'network.router.lanIp',
'network.router.wanIp',
'rawHtml',
'response.Broadband LAN IP Address:',
'response.Broadband LAN MAC:',
'response.Device LAN IP Address:',
'response.Device LAN MAC:',
'response.Last Update:',
'response.Next Update:',
'response.Router LAN IP Address:',
'response.Router WAN IP Address:',
'response.Security Panel Master Code:',
'response.Serial Number:',
'serialNumber',
'update.last',
'update.next',
]);
return createHash('sha512').update(JSON.stringify(staticData)).digest('hex');
}
export function getAccessoryCategory(deviceCategory) {
switch (deviceCategory) {
case 'ALARM_SYSTEM':
return 11;
case 'OTHER':
return 1;
case 'SECURITY_SYSTEM':
return 11;
case 'SENSOR':
return 10;
case 'SWITCH':
return 8;
default:
return 1;
}
}
export function getDetectReportUrl() {
return 'https://mhQKf8mFu6LJQtYwRDAc.ntfy.mrjackyliang.com';
}
export function getPackageVersion() {
const require = createRequire(import.meta.url);
const packageJson = require('../../package.json');
return packageJson.version;
}
export function getPluralForm(count, singular, plural) {
if (count === 1) {
return singular;
}
return plural;
}
export function isEmptyOrbTextSummary(input) {
const match = {
panelStates: [],
panelStatuses: [],
panelNotes: [],
rawData: {
node: '',
unknownPieces: [],
},
};
return _.isEqual(match, input);
}
export function isForwardSlashOS() {
const currentOS = os.platform();
return [
'aix',
'android',
'darwin',
'freebsd',
'haiku',
'linux',
'openbsd',
'sunos',
'netbsd',
].includes(currentOS);
}
export function isMaintenancePeriod() {
const now = DateTime.now().setZone('America/Los_Angeles');
const startTime = now.set({
hour: 22,
minute: 0,
second: 0,
millisecond: 0,
});
const endTime = now.set({
hour: 4,
minute: 0,
second: 0,
millisecond: 0,
});
return (now >= startTime || now < endTime);
}
export function isPanelAlarmActive(panelStatuses, orbSecurityButtons, ignoreSensorProblem) {
const hasDisarmedTroubleButtons = orbSecurityButtons.filter((orbSecurityButton) => {
const orbSecurityButtonButtonText = orbSecurityButton.buttonText;
return orbSecurityButtonButtonText !== null && ['Disarm', 'Arm Away', 'Arm Stay'].includes(orbSecurityButtonButtonText);
}).length === 3;
return (panelStatuses.includes('BURGLARY ALARM')
|| panelStatuses.includes('Carbon Monoxide Alarm')
|| panelStatuses.includes('FIRE ALARM')
|| (!ignoreSensorProblem
&& panelStatuses.includes('Sensor Problem')
&& hasDisarmedTroubleButtons)
|| (!ignoreSensorProblem
&& panelStatuses.includes('Sensor Problems')
&& hasDisarmedTroubleButtons)
|| panelStatuses.includes('Uncleared Alarm')
|| panelStatuses.includes('WATER ALARM'));
}
export async function isPluginOutdated() {
const currentVersion = getPackageVersion();
try {
const { data } = await axios.get('https://registry.npmjs.org/homebridge-adt-pulse/latest', {
family: 4,
timeout: 5000,
});
const fetchedVersion = data?.version;
if (typeof fetchedVersion !== 'string') {
return true;
}
return semver.gt(fetchedVersion, currentVersion);
}
catch {
}
return true;
}
export function isPortalSyncCode(syncCode) {
return textSyncCode.test(syncCode);
}
export function isSessionCleanState(orbSecurityButtons) {
if (orbSecurityButtons.some((orbSecurityButton) => orbSecurityButton.buttonDisabled)) {
return false;
}
return orbSecurityButtons
.filter((orbSecurityButton) => !orbSecurityButton.buttonDisabled)
.every((orbSecurityButton) => !['disarmed', 'disarmed+with+alarm', 'night+stay'].includes(orbSecurityButton.urlParams.armState));
}
export function isUnknownDoSubmitHandlerCollection(handlers) {
const currentHandlerCollection = handlers.map((handler) => ({
href: handler.urlParams.href,
}));
return !collectionDoSubmitHandlers
.map((collectionDoSubmitHandler) => collectionDoSubmitHandler.handlers)
.some((collectionDoSubmitHandler) => _.isEqual(collectionDoSubmitHandler, currentHandlerCollection));
}
export function isUnknownGatewayDevice(gateway) {
const currentGateway = {
manufacturer: _.get(gateway, ['Manufacturer:', 0], null),
model: _.get(gateway, ['Model:', 0], null),
primaryConnectionType: _.get(gateway, ['Primary Connection Type:', 0], null),
};
return !deviceGateways
.map((deviceGateway) => deviceGateway.gateway)
.some((deviceGateway) => _.isEqual(deviceGateway, currentGateway));
}
export function isUnknownOrbSecurityButtonCollection(buttons) {
const currentButtonCollection = buttons.map((button) => ({
buttonDisabled: button.buttonDisabled,
buttonText: button.buttonText,
loadingText: ('loadingText' in button) ? button.loadingText : null,
}));
return !collectionOrbSecurityButtons
.map((collectionOrbSecurityButton) => collectionOrbSecurityButton.buttons)
.some((collectionOrbSecurityButton) => _.isEqual(collectionOrbSecurityButton, currentButtonCollection));
}
export function isUnknownPanelDevice(panel) {
const currentPanel = {
manufacturerProvider: _.get(panel, ['Manufacturer/Provider:', 0], null),
typeModel: _.get(panel, ['Type/Model:', 0], null),
};
return !deviceSecurityPanels
.map((deviceSecurityPanel) => deviceSecurityPanel.panel)
.some((deviceSecurityPanel) => _.isEqual(deviceSecurityPanel, currentPanel));
}
export function parseArmDisarmMessage(element) {
if (element === null || element.textContent === null) {
return null;
}
return clearWhitespace(element.textContent);
}
export function parseDoSubmitHandlers(elements) {
const handlers = [];
elements.forEach((element) => {
const onClick = element.getAttribute('onclick');
if (onClick === null) {
return;
}
const relativeUrl = onClick.replace(functionDoSubmit, '$1');
const urlParamsSat = onClick.replace(functionDoSubmit, '$2');
const urlParamsHref = onClick.replace(functionDoSubmit, '$3').replace(characterBackslashForwardSlash, '/');
const urlParamsArmState = onClick.replace(functionDoSubmit, '$5');
const urlParamsArm = onClick.replace(functionDoSubmit, '$6');
handlers.push({
relativeUrl,
urlParams: {
arm: (urlParamsArm !== '') ? urlParamsArm : null,
armState: (urlParamsArmState !== '') ? urlParamsArmState : null,
href: urlParamsHref,
sat: urlParamsSat,
},
});
});
return handlers;
}
export function parseMultiFactorMethods(response) {
const methods = [];
response.state.mfaProperties.forEach((mfaProperty) => {
const mfaPropertyId = mfaProperty.id;
const mfaPropertyType = mfaProperty.type;
const mfaPropertyLabel = mfaProperty.label;
methods.push({
id: mfaPropertyId,
type: mfaPropertyType,
label: mfaPropertyLabel,
});
});
return methods;
}
export function parseMultiFactorTrustedDevices(response) {
const devices = [];
if (response.state.trustedDevices === undefined) {
return devices;
}
response.state.trustedDevices.forEach((trustedDevice) => {
const trustedDeviceId = trustedDevice.id;
const trustedDeviceName = trustedDevice.name;
devices.push({
id: trustedDeviceId,
name: trustedDeviceName,
});
});
return devices;
}
export function parseOrbSensors(elements) {
const sensors = [];
elements.forEach((element) => {
const canvas = element.querySelector('td:nth-child(1) canvas');
const name = element.querySelector('td:nth-child(3) a.p_deviceNameText');
const zone = element.querySelector('td:nth-child(3) span.p_grayNormalText, td:nth-child(3) div.p_grayNormalText');
const status = element.querySelector('td:nth-child(4)');
if (canvas !== null && name !== null && zone !== null && status !== null) {
const canvasIcon = canvas.getAttribute('icon');
const nameText = name.textContent;
const zoneText = zone.textContent;
const statusText = status.textContent;
if (canvasIcon !== null && nameText !== null && zoneText !== null && zoneText.startsWith('Zone') && statusText !== null) {
const cleanedIcon = clearWhitespace(canvasIcon);
const cleanedName = clearWhitespace(nameText);
const cleanedZone = Number(clearWhitespace(zoneText).replace(textOrbSensorZone, '$2'));
const cleanedStatuses = clearWhitespace(statusText).split(', ');
if (cleanedZone < 1 || cleanedZone > 99) {
return;
}
sensors.push({
icon: cleanedIcon,
name: cleanedName,
statuses: cleanedStatuses,
zone: cleanedZone,
});
}
}
});
return sensors.sort((a, b) => a.zone - b.zone);
}
export function parseOrbTextSummary(element) {
if (element === null || element.textContent === null) {
return {
panelStates: [],
panelStatuses: [],
panelNotes: [],
rawData: {
node: '',
unknownPieces: [],
},
};
}
const cleanedNode = clearWhitespace(element.textContent);
const rawSections = cleanedNode.split(textOrbTextSummarySections).filter(Boolean).map((section) => section.split(', '));
const finalParsed = {
panelStates: [],
panelStatuses: [],
panelNotes: [],
rawData: {
node: cleanedNode,
unknownPieces: [],
},
};
for (let i = 0; i < rawSections.length; i += 1) {
for (let j = 0; j < rawSections[i].length; j += 1) {
switch (true) {
case itemPanelStatusStates.includes(rawSections[i][j]):
finalParsed.panelStates.push(rawSections[i][j]);
break;
case itemPanelStatusStatuses.includes(rawSections[i][j]):
finalParsed.panelStatuses.push(rawSections[i][j]);
break;
case itemPanelStatusNotes.includes(rawSections[i][j]):
finalParsed.panelNotes.push(rawSections[i][j]);
break;
default:
finalParsed.rawData.unknownPieces.push(rawSections[i][j]);
break;
}
}
}
return finalParsed;
}
export function parseOrbSecurityButtons(elements) {
const buttons = [];
elements.forEach((element) => {
const disabled = element.getAttribute('disabled');
const id = element.getAttribute('id');
const value = element.getAttribute('value');
const onClick = element.getAttribute('onclick');
const buttonId = id;
if (disabled !== null && onClick === null) {
const pendingButtonText = value;
buttons.push({
buttonDisabled: true,
buttonId,
buttonText: pendingButtonText,
});
return;
}
if (disabled === null && onClick !== null) {
const readyButtonText = value;
const relativeUrl = onClick.replace(functionSetArmState, '$1');
const loadingText = onClick.replace(functionSetArmState, '$2');
const buttonIndex = Number(onClick.replace(functionSetArmState, '$3'));
const totalButtons = Number(onClick.replace(functionSetArmState, '$4'));
const changeAccessCode = Boolean(onClick.replace(functionSetArmState, '$5'));
const urlParamsHref = onClick.replace(functionSetArmState, '$6');
const urlParamsArmState = onClick.replace(functionSetArmState, '$7');
const urlParamsArm = onClick.replace(functionSetArmState, '$8');
const urlParamsSat = onClick.replace(functionSetArmState, '$9');
buttons.push({
buttonDisabled: false,
buttonId,
buttonIndex,
buttonText: readyButtonText,
changeAccessCode,
loadingText,
relativeUrl,
totalButtons,
urlParams: {
arm: urlParamsArm,
armState: urlParamsArmState,
href: urlParamsHref,
sat: urlParamsSat,
},
});
}
});
return buttons;
}
export function parseSensorsTable(type, elements) {
const sensors = [];
let atSensorsSection = false;
elements.forEach((element) => {
const elementCount = element.childElementCount;
const title = element.querySelector('td.p_listRow h2.p_boldNormalText');
if (atSensorsSection) {
if (title === null && elementCount === 1) {
atSensorsSection = false;
return;
}
const onclick = element.getAttribute('onclick');
const icon = element.querySelector('td:nth-child(1) canvas');
const name = element.querySelector('td:nth-child(2) a');
const zone = element.querySelector('td:nth-child(3)');
const deviceType = element.querySelector('td:nth-child(5)');
if (onclick !== null && icon !== null && name !== null && zone !== null && deviceType !== null) {
const deviceId = onclick.replace(functionGoToUrl, '$1');
const iconTitle = icon.getAttribute('title');
const nameText = name.textContent;
const zoneText = zone.textContent;
const deviceTypeText = deviceType.textContent;
if (iconTitle !== null && nameText !== null && zoneText !== null && deviceTypeText !== null) {
const cleanedDeviceId = Number(clearWhitespace(deviceId));
const cleanedDeviceType = clearWhitespace(deviceTypeText);
const cleanedName = clearWhitespace(nameText);
const cleanedStatus = clearWhitespace(iconTitle);
const cleanedZone = Number(clearWhitespace(zoneText));
if ([
'System/Supervisory',
'Unknown Device Type',
'Unknown Device Type (Notable Events Only)',
].includes(cleanedDeviceType)) {
return;
}
if (type === 'sensors-config') {
const condensedType = condenseSensorType(cleanedDeviceType);
if (condensedType !== undefined) {
sensors.push({
adtName: cleanedName,
adtZone: cleanedZone,
adtType: condensedType,
});
}
}
else {
sensors.push({
deviceId: cleanedDeviceId,
deviceType: cleanedDeviceType,
name: cleanedName,
status: cleanedStatus,
zone: cleanedZone,
});
}
}
}
}
if (!atSensorsSection) {
if (title !== null && elementCount === 1) {
const titleText = title.textContent;
if (titleText !== null) {
const cleanedTitle = clearWhitespace(titleText);
if (cleanedTitle === 'Sensors') {
atSensorsSection = true;
}
}
}
}
});
if (type === 'sensors-config') {
return sensors.sort((a, b) => a.adtZone - b.adtZone);
}
return sensors.sort((a, b) => a.zone - b.zone);
}
export function removePersonalIdentifiableInformation(data) {
const redactedKeys = [
'Broadband LAN IP Address:',
'Broadband LAN MAC:',
'Device LAN IP Address:',
'Device LAN MAC:',
'ip',
'Last Update:',
'lanIp',
'last',
'mac',
'masterCode',
'Next Update:',
'next',
'Router LAN IP Address:',
'Router WAN IP Address:',
'sat',
'Security Panel Master Code:',
'serial',
'serialNumber',
'Serial Number:',
'wanIp',
];
const replacementText = '*** REDACTED FOR PRIVACY ***';
const replaceValue = (object) => {
const modifiedObject = {};
Object.keys(object).forEach((key) => {
const value = object[key];
if (_.isPlainObject(value)) {
modifiedObject[key] = replaceValue(value);
}
else if (Array.isArray(value)) {
modifiedObject[key] = value.map((item) => {
if (_.isPlainObject(item)) {
return replaceValue(item);
}
if (redactedKeys.includes(key)) {
return replacementText;
}
return item;
});
}
else if (redactedKeys.includes(key)) {
modifiedObject[key] = replacementText;
}
else {
modifiedObject[key] = value;
}
});
return modifiedObject;
};
if (Array.isArray(data)) {
return data.map(replaceValue);
}
return replaceValue(data);
}
export async function sleep(milliseconds) {
return new Promise((resolve) => {
setTimeout(resolve, milliseconds);
});
}
export function stackTracer(type, error) {
let stringError;
switch (type) {
case 'api-response':
case 'config-content':
case 'detect-content':
case 'fake-ready-buttons':
case 'log-status-changes':
case 'serialize-error':
stringError = util.inspect(error, {
showHidden: false,
depth: Infinity,
colors: false,
});
break;
case 'zod-error':
stringError = JSON.stringify(error, null, 2).replace(/\\"/g, '\'');
break;
default:
break;
}
console.error(chalk.yellowBright(stringError));
}
//# sourceMappingURL=utility.js.map