diff --git a/src/CGDGarageDoor.ts b/src/CGDGarageDoor.ts index e62f27b..fffc2d6 100644 --- a/src/CGDGarageDoor.ts +++ b/src/CGDGarageDoor.ts @@ -1,4 +1,5 @@ import { Logging } from 'homebridge'; +import retry from './retry'; interface Status { lamp: 'on' | 'off'; @@ -37,28 +38,39 @@ export class CGDGarageDoor { } private run = async ({ cmd, value, softValue = value }) => { - this.log.debug(`Running command: ${cmd}=${value}`); - const { deviceHostname, deviceLocalKey } = this.config; - const response = await fetch(`http://${deviceHostname}/api?key=${deviceLocalKey}&${cmd}=${value}`); - - const data = await response.json(); - - this.log.debug(response.status.toString()); - this.log.debug(JSON.stringify(data)); - - if (!response.ok) { - this.log.error(`Failed to run command: ${cmd}=${value}`); - - return; - } - - if (this.status?.[cmd]) { - this.log.debug(`Setting ${cmd} to ${softValue}`); - this.status[cmd] = softValue; - } - return data; + return retry(async () => { + const response = await fetch(`http://${deviceHostname}/api?key=${deviceLocalKey}&${cmd}=${value}`); + const data = await response.json(); + + this.log.debug(`Running command: ${cmd}=${value}`); + + const level = response.ok ? 'debug' : 'error'; + this.log[level](response.status.toString()); + this.log[level](JSON.stringify(data)); + + if (!response.ok) { + throw new Error(`Fetch failed with status ${response.status}, ${JSON.stringify(data)}`); + } + + if (this.status?.[cmd]) { + this.log.debug(`Setting ${cmd} to ${softValue}`); + this.status[cmd] = softValue; + } + + return data; + }, { + retries: 3, + onRetry: (error, retries) => { + this.log.warn(`Failed to run command[Retrying ${retries}]: ${cmd}=${value}`); + this.log.warn(JSON.stringify(error)); + }, + onFail: (error) => { + this.log.error(`Failed to run command: ${cmd}=${value}`); + this.log.error(JSON.stringify(error)); + }, + }); }; private refreshStatus = async () => { @@ -72,7 +84,7 @@ export class CGDGarageDoor { return; } - this.status = data; + this.status = data as Status; }; private getDoorState = (): DoorState => { diff --git a/src/retry.ts b/src/retry.ts new file mode 100644 index 0000000..6ca2630 --- /dev/null +++ b/src/retry.ts @@ -0,0 +1,30 @@ +interface Config { + retries: number; + onRetry: (error: unknown, retries: number) => void; + onFail: (error: unknown) => void; +} + +const retry = async (fn: () => Promise, config: Config) => { + const { + retries = 3, + onRetry, + onFail, + } = config; + + try { + return await fn(); + } catch (error) { + if (retries === 0) { + return onFail(error); + } + + onRetry(error, retries); + + return retry(fn, { + ...config, + retries: retries - 1, + }); + } +}; + +export default retry;