From b57b938755724899a9c932d2764ad2bc9ba77254 Mon Sep 17 00:00:00 2001 From: Bubba8291 <37317442+Bubba8291@users.noreply.github.com> Date: Thu, 29 Aug 2024 13:52:16 -0700 Subject: [PATCH 01/23] More descriptive error and debug messages for getting property values --- src/sharkiq-js/sharkiq.ts | 49 +++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/src/sharkiq-js/sharkiq.ts b/src/sharkiq-js/sharkiq.ts index 656c8fe..ba8f318 100644 --- a/src/sharkiq-js/sharkiq.ts +++ b/src/sharkiq-js/sharkiq.ts @@ -154,8 +154,36 @@ class SharkIqVacuum { const params = { 'names': 'GET_' + property }; const auth_header = await this.ayla_api.auth_header(); const resp = await this.ayla_api.makeRequest('GET', url + formatParams(params), null, auth_header); + try { + const properties = JSON.parse(resp.response); + if (resp.status !== 200) { + this.log.error('Error getting property value', property); + if (properties['error'] !== undefined) { + this.log.error(`Message: ${JSON.stringify(properties['error'])}`); + } + const status = await this.ayla_api.attempt_refresh(attempt); + if (!status && attempt === 1) { + return; + } else { + await this.update(property_list, attempt + 1); + return; + } + } + this._do_update(full_update, properties); + } catch { + this.log.error('Error parsing JSON response for property: ' + property); + } + } + } else { + const auth_header = await this.ayla_api.auth_header(); + const resp = await this.ayla_api.makeRequest('GET', url, null, auth_header); + const properties = JSON.parse(resp.response); + try { if (resp.status !== 200) { this.log.error('Error getting property values.'); + if (properties['error'] !== undefined) { + this.log.error(`Message: ${JSON.stringify(properties['error'])}`); + } const status = await this.ayla_api.attempt_refresh(attempt); if (!status && attempt === 1) { return; @@ -164,27 +192,14 @@ class SharkIqVacuum { return; } } - const properties = JSON.parse(resp.response); this._do_update(full_update, properties); + } catch { + this.log.error('Error parsing JSON response for properties.'); } - } else { - const auth_header = await this.ayla_api.auth_header(); - const resp = await this.ayla_api.makeRequest('GET', url, null, auth_header); - if (resp.status !== 200) { - this.log.error('Error getting property values.'); - const status = await this.ayla_api.attempt_refresh(attempt); - if (!status && attempt === 1) { - return; - } else { - await this.update(property_list, attempt + 1); - return; - } - } - const properties = JSON.parse(resp.response); - this._do_update(full_update, properties); } - } catch { + } catch (e) { this.log.debug('Promise Rejected with updating properties.'); + this.log.debug('Error:', e); } } From 4adfe47455651df8928f7bf70d93a05bf7f0e5d2 Mon Sep 17 00:00:00 2001 From: Bubba8291 <37317442+Bubba8291@users.noreply.github.com> Date: Fri, 6 Sep 2024 16:49:39 -0700 Subject: [PATCH 02/23] Changed ouath tokens to be stored in config file. Optimized vacuum syncing --- config.schema.json | 35 ++++++--- src/config.ts | 56 ++++++++++++--- src/login.ts | 23 +++--- src/platform.ts | 9 +-- src/platformAccessory.ts | 141 ++++++++++++++++++++++++++----------- src/sharkiq-js/ayla_api.ts | 43 ++++++++--- src/sharkiq-js/const.ts | 3 +- src/sharkiq-js/sharkiq.ts | 88 +++++++++++++++++++++-- src/type.ts | 2 +- src/utils.ts | 18 +++-- 10 files changed, 312 insertions(+), 106 deletions(-) diff --git a/config.schema.json b/config.schema.json index 73206a2..e5f8442 100644 --- a/config.schema.json +++ b/config.schema.json @@ -40,20 +40,32 @@ "title": "Docked Update Interval", "type": "integer", "required": false, - "default": 5000 + "default": 30000 + }, + "credentials": { + "title": "SharkClean OAuth", + "type": "object", + "properties": { + "access_token": { + "title": "Access Token", + "type": "string", + "required": false + }, + "refresh_token": { + "title": "Refresh Token", + "type": "string", + "required": false + }, + "expiration": { + "title": "Expiration", + "type": "date", + "required": false + } + } } } }, "layout": [ - { - "type": "fieldset", - "title": "SharkIQ Auth Info", - "expandable": true, - "expanded": true, - "items": [ - "oAuthCode" - ] - }, { "type": "fieldset", "title": "Vacuum Settings", @@ -81,7 +93,8 @@ }, "europe", "invertDockedStatus", - "dockedUpdateInterval" + "dockedUpdateInterval", + "oAuthCode" ] } ] diff --git a/src/config.ts b/src/config.ts index 8f07fc1..d700605 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,19 +1,59 @@ import { promises as fs } from 'fs'; import { AuthData, OAuthData } from './type'; +import { PLATFORM_NAME } from './settings'; -async function getAuthFile(authFilePath: string): Promise { +async function getAuthData(configPath: string): Promise { try { - const data = await fs.readFile(authFilePath, 'utf8'); - const authData = JSON.parse(data); - return authData; + const data = await fs.readFile(configPath, 'utf8'); + const currentConfig = JSON.parse(data); + + if (!Array.isArray(currentConfig.platforms)) { + return null; + } + + const pluginConfig = currentConfig.platforms.find((x: { platform: string }) => x.platform === PLATFORM_NAME); + + if (!pluginConfig) { + return null; + } + + if (typeof pluginConfig.credentials !== 'object') { + return null; + } + + return pluginConfig.credentials; } catch { return null; } } -async function setAuthFile(authFilePath: string, data: AuthData): Promise { +async function setAuthData(configPath: string, data: AuthData): Promise { try { - await fs.writeFile(authFilePath, JSON.stringify(data), 'utf8'); + if (!data) { + return false; + } + const currentConfigData = await fs.readFile(configPath, 'utf8'); + const currentConfig = JSON.parse(currentConfigData); + + if (!Array.isArray(currentConfig.platforms)) { + return false; + } + + const pluginConfig = currentConfig.platforms.find((x: { platform: string }) => x.platform === PLATFORM_NAME); + + if (!pluginConfig) { + return false; + } + + if (typeof pluginConfig.credentials !== 'object') { + pluginConfig.credentials = {}; + } + + pluginConfig.credentials.access_token = data.access_token; + pluginConfig.credentials.refresh_token = data.refresh_token; + pluginConfig.credentials.expiration = data.expiration; + + await fs.writeFile(configPath, JSON.stringify(currentConfig, null, 4), 'utf8'); return true; } catch { return false; @@ -32,7 +72,7 @@ async function getOAuthData(oAuthFilePath: string): Promise { async function setOAuthData(oAuthFilePath: string, data: OAuthData): Promise { try { - await fs.writeFile(oAuthFilePath, JSON.stringify(data), 'utf8'); + await fs.writeFile(oAuthFilePath, JSON.stringify(data, null, 4), 'utf8'); return true; } catch { return false; @@ -47,4 +87,4 @@ async function removeFile(filePath: string): Promise { } } -export { getAuthFile, setAuthFile, getOAuthData, setOAuthData, removeFile }; +export { getAuthData, setAuthData, getOAuthData, setOAuthData, removeFile }; diff --git a/src/login.ts b/src/login.ts index 320f23c..3c3f4d1 100644 --- a/src/login.ts +++ b/src/login.ts @@ -1,5 +1,5 @@ import { Logger } from 'homebridge'; -import { getAuthFile, setAuthFile, getOAuthData, setOAuthData, removeFile } from './config'; +import { getAuthData, setAuthData, getOAuthData, setOAuthData, removeFile } from './config'; import { OAuthData } from './type'; import { global_vars } from './sharkiq-js/const'; @@ -10,35 +10,34 @@ import { join } from 'path'; export class Login { public log: Logger; public storagePath: string; - public authFile: string; public oAuthFile: string; public oAuthCode: string; public app_id: string; public app_secret: string; - public auth_file_path: string; + public config_file: string; public oauth_file_path: string; constructor(log: Logger, storagePath: string, oAuthCode: string, + config_file: string, app_id = global_vars.SHARK_APP_ID, app_secret = global_vars.SHARK_APP_SECRET, ) { this.log = log; this.storagePath = storagePath; this.oAuthCode = oAuthCode; - this.authFile = global_vars.FILE; + this.config_file = config_file; this.oAuthFile = global_vars.OAUTH.FILE; this.app_id = app_id; this.app_secret = app_secret; - this.auth_file_path = join(this.storagePath, this.authFile); this.oauth_file_path = join(this.storagePath, this.oAuthFile); } public async checkLogin(): Promise { - const auth_file = await getAuthFile(this.auth_file_path); + const auth_data = await getAuthData(this.config_file); const oauth_file = await getOAuthData(this.oauth_file_path); - if (!auth_file) { + if (!auth_data) { if (this.oAuthCode !== '') { if (!oauth_file) { this.log.error('No OAuth data found with oAuthCode present. Please remove the oAuthCode from the config.'); @@ -69,7 +68,7 @@ export class Login { private async login(code: string, oAuthData: OAuthData): Promise { const data = { grant_type: 'authorization_code', - client_id: global_vars.SHARK_CLIENT_ID, + client_id: global_vars.OAUTH.CLIENT_ID, code: code, code_verifier: oAuthData.code_verify, redirect_uri: global_vars.OAUTH.REDIRECT_URI, @@ -101,7 +100,13 @@ export class Login { }; const response2 = await fetch(`${global_vars.LOGIN_URL}/api/v1/token_sign_in`, reqData2); const aylaTokenData = await response2.json(); +<<<<<<< Updated upstream const status = setAuthFile(this.auth_file_path, aylaTokenData); +======= + const dateNow = new Date(); + aylaTokenData['expiration'] = addSeconds(dateNow, aylaTokenData['expires_in']); + const status = setAuthData(this.config_file, aylaTokenData); +>>>>>>> Stashed changes if (!status) { this.log.error('Error saving auth file.'); return false; @@ -133,7 +138,7 @@ export class Login { const url = global_vars.OAUTH.AUTH_URL + '?response_type=code' - + '&client_id='+encodeURIComponent(global_vars.SHARK_CLIENT_ID) + + '&client_id='+encodeURIComponent(global_vars.OAUTH.CLIENT_ID) + '&state='+encodeURIComponent(oAuthData.state) + '&scope='+encodeURIComponent(global_vars.OAUTH.SCOPES) + '&redirect_uri='+encodeURIComponent(global_vars.OAUTH.REDIRECT_URI) diff --git a/src/platform.ts b/src/platform.ts index f280634..ac9b457 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -4,13 +4,10 @@ import { PLATFORM_NAME, PLUGIN_NAME } from './settings'; import { SharkIQAccessory } from './platformAccessory'; import { Login } from './login'; -import { global_vars } from './sharkiq-js/const'; import { get_ayla_api } from './sharkiq-js/ayla_api'; import { SharkIqVacuum } from './sharkiq-js/sharkiq'; -import { join } from 'path'; - // SharkIQPlatform Main Class export class SharkIQPlatform implements DynamicPlatformPlugin { public readonly Service: typeof Service = this.api.hap.Service; @@ -57,15 +54,15 @@ export class SharkIQPlatform implements DynamicPlatformPlugin { login = async (): Promise => { const oAuthCode = this.config.oAuthCode || ''; const europe = this.config.europe || false; - const login = new Login(this.log, this.api.user.storagePath(), oAuthCode); + const configFilePath = this.api.user.configPath(); + const login = new Login(this.log, this.api.user.storagePath(), oAuthCode, configFilePath); try { const status = await login.checkLogin(); if (!status) { this.log.error('Error logging in to Shark'); return []; } - const authFilePath = join(this.api.user.storagePath(), global_vars.FILE); - const ayla_api = get_ayla_api(authFilePath, this.log, europe); + const ayla_api = get_ayla_api(configFilePath, this.log, europe); await ayla_api.sign_in(); const devices = await ayla_api.get_devices(); return devices; diff --git a/src/platformAccessory.ts b/src/platformAccessory.ts index 714d5f1..c5857a4 100644 --- a/src/platformAccessory.ts +++ b/src/platformAccessory.ts @@ -16,6 +16,7 @@ export class SharkIQAccessory { private readonly log: Logger, private readonly invertDockedStatus: boolean, private readonly dockedUpdateInterval: number, + private dockedDelay: number = 0, ) { // Get device serial number @@ -27,7 +28,7 @@ export class SharkIQAccessory { // Vacuum Name - Default is device name this.service.setCharacteristic(this.platform.Characteristic.Name, device._name.toString()); - // Vacuum Active + // // Vacuum Active this.service.getCharacteristic(this.platform.Characteristic.Active) .onSet(this.setVacuumActive.bind(this)) .onGet(this.getVacuumActive.bind(this)); @@ -46,6 +47,8 @@ export class SharkIQAccessory { this.dockedStatusService = this.accessory.getService('Vacuum Docked') || this.accessory.addService(this.platform.Service.ContactSensor, 'Vacuum Docked', 'Docked'); this.dockedStatusService.setCharacteristic(this.platform.Characteristic.Name, device._name.toString() + ' Docked'); + this.dockedStatusService.getCharacteristic(this.platform.Characteristic.ContactSensorState) + .onGet(this.retrieveDockedStatus.bind(this)); // Vacuum Paused Status this.vacuumPausedService = this.accessory.getService('Vacuum Paused') || @@ -59,51 +62,42 @@ export class SharkIQAccessory { this.updateStates(); - // Monitor vacuum state - this.monitorVacuumState().then(() => { - this.monitorVacuumStateInterval(); - }) - .catch(() => { - this.log.debug('Promise Rejected with first interval update.'); - this.monitorVacuumStateInterval(); - }); - } - - // Monitor vacuum state interval function - async monitorVacuumStateInterval(): Promise { - setInterval(async () => { - await this.monitorVacuumState() - .catch(() => { - this.log.debug('Promise Rejected with interval update.'); - }); - }, this.dockedUpdateInterval); + // // Monitor vacuum state + // this.monitorVacuumState().then(() => { + // this.monitorVacuumStateInterval(); + // }) + // .catch(() => { + // this.log.debug('Promise Rejected with first interval update.'); + // this.monitorVacuumStateInterval(); + // }); } - // Monitor vacuum state function - async monitorVacuumState(): Promise { + // // Monitor vacuum state interval function + // async monitorVacuumStateInterval(): Promise { + // setInterval(async () => { + // await this.monitorVacuumState() + // .catch(() => { + // this.log.debug('Promise Rejected with interval update.'); + // }); + // }, this.dockedUpdateInterval + this.dockedDelay); + // } + + async retrieveDockedStatus(): Promise { + this.log.debug('Triggering GET Docked Status'); + await this.device.update(Properties.DOCKED_STATUS); + + const docked_status = this.device.docked_status(); let vacuumDocked = false; - - await this.device.update(Properties.DOCKED_STATUS) - .catch(() => { - this.log.debug('Promise Rejected with docked status update.'); - }); - if(!this.invertDockedStatus) { - vacuumDocked = this.device.get_property_value(Properties.DOCKED_STATUS) === 1; + vacuumDocked = docked_status === 1; } else { - vacuumDocked = this.device.get_property_value(Properties.DOCKED_STATUS) !== 1; + vacuumDocked = docked_status !== 1; } - await this.updateItems(vacuumDocked) - .catch(() => { - this.log.debug('Promise Rejected with running docked update.'); - }); - - this.dockedStatusService.updateCharacteristic(this.platform.Characteristic.ContactSensorState, vacuumDocked); - - this.log.debug('Triggering Vacuum Docked:', vacuumDocked); + return vacuumDocked; } +<<<<<<< Updated upstream // Update docked, active, and paused state async updateItems(vacuumDocked: boolean): Promise { await this.device.update(Properties.OPERATING_MODE) @@ -131,8 +125,66 @@ export class SharkIQAccessory { }); } } +======= + async retrieveOperatingMode(): Promise { + await this.device.update(Properties.OPERATING_MODE); + // .then((delay) => { + // this.dockedDelay = delay; + // }); +>>>>>>> Stashed changes + } + + async retrievePowerMode(): Promise { + await this.device.update(Properties.POWER_MODE); + // .then((delay) => { + // this.dockedDelay = delay; + // }); } + // // Monitor vacuum state function + // async retrieveVacuumStates(): Promise { + // let vacuumDocked = false; + + // await this.device.update([Properties.DOCKED_STATUS, Properties.OPERATING_MODE, Properties.POWER_MODE]) + // .then((delay) => { + // this.dockedDelay = delay; + // }); + + // const docked_status = this.device.docked_status(); + // if(!this.invertDockedStatus) { + // vacuumDocked = docked_status === 1; + // } else { + // vacuumDocked = docked_status !== 1; + // } + // await this.updateItems(vacuumDocked) + // .catch(() => { + // this.log.debug('Promise Rejected with running docked update.'); + // }); + + + // this.dockedStatusService.updateCharacteristic(this.platform.Characteristic.ContactSensorState, vacuumDocked); + + // this.log.debug('Triggering Vacuum Docked:', vacuumDocked); + // } + + // // Update docked, active, and paused state + // async updateItems(vacuumDocked: boolean): Promise { + // if (!vacuumDocked) { + // const mode = this.device.operating_mode(); + // if (mode === OperatingModes.START || mode === OperatingModes.STOP) { + // const service = this.service; + // const platform = this.platform; + // await this.getFanSpeed() + // .then((power_mode) => { + // service.updateCharacteristic(platform.Characteristic.RotationSpeed, power_mode); + // }) + // .catch(() => { + // this.log.debug('Promise Rejected with getting power mode.'); + // }); + // } + // } + // } + // Update paused and active state on switch updateStates(): void { const mode = this.device.operating_mode(); @@ -152,11 +204,13 @@ export class SharkIQAccessory { } // Get paused state - getPaused(): boolean { + async getPaused(): Promise { this.log.debug('Triggering GET Paused'); + await this.retrieveOperatingMode(); - const mode = this.device.operating_mode() === OperatingModes.STOP; - if (mode) { + const mode = this.device.operating_mode(); + this.log.debug('State:', mode); + if (mode === OperatingModes.STOP) { return true; } else { return false; @@ -188,7 +242,6 @@ export class SharkIQAccessory { } - // Check if the vacuum is active for UI async getVacuumActive(): Promise { this.log.debug('Triggering GET Vacuum Active'); @@ -219,11 +272,12 @@ export class SharkIQAccessory { // Get vacuum power for UI async getFanSpeed(): Promise { this.log.debug('Triggering GET Fan Speed'); + await this.retrievePowerMode(); const mode = this.device.operating_mode(); const vacuumActive = mode === OperatingModes.START || mode === OperatingModes.STOP; if (vacuumActive) { - const power_mode = this.device.get_property_value(Properties.POWER_MODE); + const power_mode = this.device.power_mode(); if (power_mode === PowerModes.MAX) { return 90; } else if (power_mode === PowerModes.ECO) { @@ -238,6 +292,7 @@ export class SharkIQAccessory { // Set vacuum power from UI (and start/stop vacuum if needed) async setFanSpeed(value: CharacteristicValue): Promise { this.log.debug('Triggering SET Fan Speed'); + this.log.debug('Value:', value); let power_mode = PowerModes.NORMAL; if (value === 30) { @@ -253,7 +308,7 @@ export class SharkIQAccessory { this.vacuumPausedService.updateCharacteristic(this.platform.Characteristic.On, false); return; } - const isPaused = this.getPaused(); + const isPaused = await this.getPaused(); if (isPaused) { await this.device.set_operating_mode(OperatingModes.START) .catch(() => { diff --git a/src/sharkiq-js/ayla_api.ts b/src/sharkiq-js/ayla_api.ts index 3a51d1a..89f6aca 100644 --- a/src/sharkiq-js/ayla_api.ts +++ b/src/sharkiq-js/ayla_api.ts @@ -4,26 +4,32 @@ import { Logger } from 'homebridge'; import { global_vars } from './const'; import { SharkIqVacuum } from './sharkiq'; +<<<<<<< Updated upstream import { getAuthFile, setAuthFile } from '../config'; import { addSeconds, subtractSeconds } from '../utils'; +======= +import { getAuthData, setAuthData } from '../config'; +import { addSeconds, subtractSeconds, isValidDate } from '../utils'; +>>>>>>> Stashed changes import { AuthData } from '../type'; type APIResponse = { status: number; response: string; + ok: boolean; }; // New AylaApi object -const get_ayla_api = function (auth_file_path: string, log: Logger, europe = false): AylaApi { +const get_ayla_api = function (config_file_path: string, log: Logger, europe = false): AylaApi { if (europe) { - return new AylaApi(auth_file_path, global_vars.EU_SHARK_APP_ID, global_vars.EU_SHARK_APP_SECRET, log, europe); + return new AylaApi(config_file_path, global_vars.EU_SHARK_APP_ID, global_vars.EU_SHARK_APP_SECRET, log, europe); } else { - return new AylaApi(auth_file_path, global_vars.SHARK_APP_ID, global_vars.SHARK_APP_SECRET, log, europe); + return new AylaApi(config_file_path, global_vars.SHARK_APP_ID, global_vars.SHARK_APP_SECRET, log, europe); } }; class AylaApi { - _auth_file_path: string; + _config_file_path: string; _access_token: string | null; _refresh_token: string | null; _auth_expiration: Date | null; @@ -35,8 +41,8 @@ class AylaApi { // Simple Ayla Networks API wrapper - constructor(auth_file_path, app_id, app_secret, log, europe = false) { - this._auth_file_path = auth_file_path; + constructor(config_file_path, app_id, app_secret, log, europe = false) { + this._config_file_path = config_file_path; this._access_token = null; this._refresh_token = null; this._auth_expiration = null; @@ -74,33 +80,42 @@ class AylaApi { return { status: statusCode, response: responseText, + ok: response.ok, }; } catch { return { status: 500, response: '', + ok: false, }; } } _set_credentials(login_result: AuthData): void { // Update credentials for cache +<<<<<<< Updated upstream this._access_token = login_result['access_token']; this._refresh_token = login_result['refresh_token']; const dateNow = new Date(); this._auth_expiration = addSeconds(dateNow, login_result['expires_in']); +======= + this._access_token = login_result.access_token; + this._refresh_token = login_result.refresh_token; + const _auth_expiration = new Date(login_result.expiration); + this._auth_expiration = isValidDate(_auth_expiration) ? _auth_expiration : null; +>>>>>>> Stashed changes this._is_authed = true; } // Sign in with auth file async sign_in(): Promise { this.log.debug('Signing in.'); - const authFile = await getAuthFile(this._auth_file_path); - if (!authFile) { + const authData = await getAuthData(this._config_file_path); + if (!authData) { this.log.error('Auth file not found.'); return false; } - this._set_credentials(authFile); + this._set_credentials(authData); return true; } @@ -120,7 +135,13 @@ class AylaApi { } return false; } +<<<<<<< Updated upstream setAuthFile(this._auth_file_path, jsonResponse); +======= + const dateNow = new Date(); + jsonResponse['expiration'] = addSeconds(dateNow, jsonResponse['expires_in']); + setAuthData(this._config_file_path, jsonResponse); +>>>>>>> Stashed changes this._set_credentials(jsonResponse); return true; } catch { @@ -216,7 +237,11 @@ class AylaApi { this.log.info('Attempting to refresh access token.'); const status = await this.refresh_auth(); if (!status) { +<<<<<<< Updated upstream this.log.error('Refreshing access token failed. Please check your auth file and recreate it if needed.'); +======= + this.log.error('Refreshing access token failed. Please check your credentials and delete them from the config if needed.'); +>>>>>>> Stashed changes this.log.error(this.exit_error_message); return false; } diff --git a/src/sharkiq-js/const.ts b/src/sharkiq-js/const.ts index d439ec9..41b2237 100644 --- a/src/sharkiq-js/const.ts +++ b/src/sharkiq-js/const.ts @@ -4,8 +4,8 @@ const global_vars = { LOGIN_URL: 'https://user-field-39a9391a.aylanetworks.com', SHARK_APP_ID: 'ios_shark_prod-3A-id', SHARK_APP_SECRET: 'ios_shark_prod-74tFWGNg34LQCmR0m45SsThqrqs', - SHARK_CLIENT_ID: 'wsguxrqm77mq4LtrTrwg8ZJUxmSrexGi', OAUTH: { + CLIENT_ID: 'wsguxrqm77mq4LtrTrwg8ZJUxmSrexGi', AUTH_URL: 'https://login.sharkninja.com/authorize', TOKEN_URL: 'https://login.sharkninja.com/oauth/token', REDIRECT_URI: 'com.sharkninja.shark://login.sharkninja.com/ios/com.sharkninja.shark/callback', @@ -13,7 +13,6 @@ const global_vars = { SCOPES: 'openid profile email offline_access read:users read:current_user read:user_idp_tokens', FILE: '.sharkiq_oauth.json', }, - FILE: '.sharkiq.json', EU_DEVICE_URL: 'https://ads-eu.aylanetworks.com', EU_LOGIN_URL: 'https://user-field-eu.aylanetworks.com', EU_SHARK_APP_ID: 'Shark-Android-EUField-Fw-id', diff --git a/src/sharkiq-js/sharkiq.ts b/src/sharkiq-js/sharkiq.ts index 656c8fe..7136538 100644 --- a/src/sharkiq-js/sharkiq.ts +++ b/src/sharkiq-js/sharkiq.ts @@ -1,4 +1,3 @@ -import { formatParams } from '../utils'; import { global_vars } from './const'; import { AylaApi } from './ayla_api'; import { OperatingModes, PowerModes, Properties } from './properties'; @@ -15,6 +14,9 @@ function _clean_property_name(raw_property_name): string { } } +const ERROR_DELAY = 10000; +const TIMEOUT_DELAY = 30000; + class SharkIqVacuum { ayla_api: AylaApi; _dsn: string; @@ -78,6 +80,16 @@ class SharkIqVacuum { return this.get_property_value(Properties.OPERATING_MODE); } + // Get current docked status + docked_status(): number { + return this.get_property_value(Properties.DOCKED_STATUS); + } + + // Get current power mode + power_mode(): number { + return this.get_property_value(Properties.POWER_MODE); + } + // Update vacuum details such as the model and serial number. _update_metadata(): void { const model_and_serial = this.get_property_value(Properties.DEVICE_SERIAL_NUMBER); @@ -117,8 +129,9 @@ class SharkIqVacuum { try { const auth_header = await this.ayla_api.auth_header(); const resp = await this.ayla_api.makeRequest('POST', end_point, data, auth_header); - if (resp.status !== 200) { - this.log.error('Error setting property value.'); + if (resp.ok !== true) { + this.log.warn('Error setting property value:', property_name, value); + this.log.debug(`API Error: ${resp.response}`); const status = await this.ayla_api.attempt_refresh(attempt); if (!status && attempt === 1) { return; @@ -139,7 +152,7 @@ class SharkIqVacuum { } // Get properties - async update(property_list, attempt = 0): Promise { + async update(property_list, attempt = 0): Promise { if (property_list) { if (!Array.isArray(property_list)) { property_list = [property_list]; @@ -149,6 +162,7 @@ class SharkIqVacuum { const url = this.update_url; try { if (!full_update && property_list.length !== 0) { +<<<<<<< Updated upstream for (let i = 0; i < property_list.length; i++) { const property = property_list[i]; const params = { 'names': 'GET_' + property }; @@ -156,16 +170,72 @@ class SharkIqVacuum { const resp = await this.ayla_api.makeRequest('GET', url + formatParams(params), null, auth_header); if (resp.status !== 200) { this.log.error('Error getting property values.'); +======= + const params = new URLSearchParams(); + property_list.forEach((property) => { + params.append('names[]', `GET_${property}`); + }); + const auth_header = await this.ayla_api.auth_header(); + const resp = await this.ayla_api.makeRequest('GET', `${url}?${params.toString()}`, null, auth_header); + try { + const properties = JSON.parse(resp.response); + if (resp.status === 429) { + this.log.debug('API Error: Too many requests'); + this.log.debug('Waiting an extra 30 seconds before retrying...'); + return TIMEOUT_DELAY; + } else if (resp.ok !== true) { + this.log.warn('Error getting property values', property_list.join(', ')); + if (properties['error'] !== undefined) { + this.log.debug(`Error Message: ${JSON.stringify(properties['error'])}`); + } const status = await this.ayla_api.attempt_refresh(attempt); if (!status && attempt === 1) { - return; + return ERROR_DELAY; } else { - await this.update(property_list, attempt + 1); - return; + return await this.update(property_list, attempt + 1); } + } else { + this._do_update(full_update, properties); + return 0; + } + } catch (e) { + this.log.warn('Error parsing JSON response for properties: ' + property_list.join(', ')); + this.log.debug('Error Message: ' + e); + return ERROR_DELAY; + } + } else { + const auth_header = await this.ayla_api.auth_header(); + const resp = await this.ayla_api.makeRequest('GET', url, null, auth_header); + const properties = JSON.parse(resp.response); + try { + if (resp.status === 429) { + this.log.debug('API Error: Too many requests'); + this.log.debug('Waiting an extra 30 seconds before retrying...'); + return TIMEOUT_DELAY; + } else if (resp.ok !== true) { + this.log.warn('Error getting property values.'); + if (properties['error'] !== undefined) { + this.log.debug(`Error Message: ${JSON.stringify(properties['error'])}`); + } +>>>>>>> Stashed changes + const status = await this.ayla_api.attempt_refresh(attempt); + if (!status && attempt === 1) { + return ERROR_DELAY; + } else { + return await this.update(property_list, attempt + 1); + } + } else { + this._do_update(full_update, properties); + return 0; } +<<<<<<< Updated upstream const properties = JSON.parse(resp.response); this._do_update(full_update, properties); +======= + } catch { + this.log.warn('Error parsing JSON response for properties.'); + return ERROR_DELAY; +>>>>>>> Stashed changes } } else { const auth_header = await this.ayla_api.auth_header(); @@ -185,6 +255,10 @@ class SharkIqVacuum { } } catch { this.log.debug('Promise Rejected with updating properties.'); +<<<<<<< Updated upstream +======= + return ERROR_DELAY; +>>>>>>> Stashed changes } } diff --git a/src/type.ts b/src/type.ts index 2cb9cdd..e58a781 100644 --- a/src/type.ts +++ b/src/type.ts @@ -1,7 +1,7 @@ type AuthData = { access_token: string; - expires_in: number; refresh_token: string; + expiration: Date; }; type OAuthData = { diff --git a/src/utils.ts b/src/utils.ts index 065b46a..bc7b15f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,13 +1,3 @@ -// Format paramaters for GET request -function formatParams(params): string { - return '?' + Object - .keys(params) - .map((key) => { - return key + '=' + encodeURIComponent(params[key]); - }) - .join('&'); -} - // Add seconds to a date function addSeconds(date: Date, seconds: number): Date { return new Date(date.getTime() + seconds * 1000); @@ -18,4 +8,12 @@ function subtractSeconds(date: Date, seconds: number): Date { return new Date(date.getTime() - seconds * 1000); } +<<<<<<< Updated upstream export { formatParams, addSeconds, subtractSeconds }; +======= +function isValidDate(d: Date): boolean { + return d instanceof Date && !isNaN(d.getTime()); +} + +export { addSeconds, subtractSeconds, isValidDate }; +>>>>>>> Stashed changes From 1ca64bb302a269f8110003b19ede2c92bd025dfb Mon Sep 17 00:00:00 2001 From: Bubba8291 <37317442+Bubba8291@users.noreply.github.com> Date: Fri, 6 Sep 2024 16:56:20 -0700 Subject: [PATCH 03/23] Changed ouath tokens to be stored in config file. Optimized vacuum syncing --- src/login.ts | 5 +---- src/platformAccessory.ts | 30 ------------------------------ src/sharkiq-js/ayla_api.ts | 20 -------------------- src/sharkiq-js/sharkiq.ts | 33 --------------------------------- src/utils.ts | 4 ---- 5 files changed, 1 insertion(+), 91 deletions(-) diff --git a/src/login.ts b/src/login.ts index 3c3f4d1..501d28e 100644 --- a/src/login.ts +++ b/src/login.ts @@ -1,6 +1,7 @@ import { Logger } from 'homebridge'; import { getAuthData, setAuthData, getOAuthData, setOAuthData, removeFile } from './config'; import { OAuthData } from './type'; +import { addSeconds } from './utils'; import { global_vars } from './sharkiq-js/const'; import fetch from 'node-fetch'; @@ -100,13 +101,9 @@ export class Login { }; const response2 = await fetch(`${global_vars.LOGIN_URL}/api/v1/token_sign_in`, reqData2); const aylaTokenData = await response2.json(); -<<<<<<< Updated upstream - const status = setAuthFile(this.auth_file_path, aylaTokenData); -======= const dateNow = new Date(); aylaTokenData['expiration'] = addSeconds(dateNow, aylaTokenData['expires_in']); const status = setAuthData(this.config_file, aylaTokenData); ->>>>>>> Stashed changes if (!status) { this.log.error('Error saving auth file.'); return false; diff --git a/src/platformAccessory.ts b/src/platformAccessory.ts index c5857a4..205dc04 100644 --- a/src/platformAccessory.ts +++ b/src/platformAccessory.ts @@ -97,41 +97,11 @@ export class SharkIQAccessory { return vacuumDocked; } -<<<<<<< Updated upstream - // Update docked, active, and paused state - async updateItems(vacuumDocked: boolean): Promise { - await this.device.update(Properties.OPERATING_MODE) - .catch(() => { - this.log.debug('Promise Rejected with operating mode update.'); - }); - - if (!vacuumDocked) { - const mode = this.device.operating_mode(); - if (mode === OperatingModes.START || mode === OperatingModes.STOP) { - await this.device.update(Properties.POWER_MODE) - .catch(() => { - this.log.debug('Promise Rejected with power mode update.'); - }); - const service = this.service; - const platform = this.platform; - await this.getFanSpeed() - .then((power_mode) => { - if (power_mode !== null) { - service.updateCharacteristic(platform.Characteristic.RotationSpeed, power_mode); - } - }) - .catch(() => { - this.log.debug('Promise Rejected with getting power mode.'); - }); - } - } -======= async retrieveOperatingMode(): Promise { await this.device.update(Properties.OPERATING_MODE); // .then((delay) => { // this.dockedDelay = delay; // }); ->>>>>>> Stashed changes } async retrievePowerMode(): Promise { diff --git a/src/sharkiq-js/ayla_api.ts b/src/sharkiq-js/ayla_api.ts index 89f6aca..82e99e8 100644 --- a/src/sharkiq-js/ayla_api.ts +++ b/src/sharkiq-js/ayla_api.ts @@ -4,13 +4,8 @@ import { Logger } from 'homebridge'; import { global_vars } from './const'; import { SharkIqVacuum } from './sharkiq'; -<<<<<<< Updated upstream -import { getAuthFile, setAuthFile } from '../config'; -import { addSeconds, subtractSeconds } from '../utils'; -======= import { getAuthData, setAuthData } from '../config'; import { addSeconds, subtractSeconds, isValidDate } from '../utils'; ->>>>>>> Stashed changes import { AuthData } from '../type'; type APIResponse = { @@ -93,17 +88,10 @@ class AylaApi { _set_credentials(login_result: AuthData): void { // Update credentials for cache -<<<<<<< Updated upstream - this._access_token = login_result['access_token']; - this._refresh_token = login_result['refresh_token']; - const dateNow = new Date(); - this._auth_expiration = addSeconds(dateNow, login_result['expires_in']); -======= this._access_token = login_result.access_token; this._refresh_token = login_result.refresh_token; const _auth_expiration = new Date(login_result.expiration); this._auth_expiration = isValidDate(_auth_expiration) ? _auth_expiration : null; ->>>>>>> Stashed changes this._is_authed = true; } @@ -135,13 +123,9 @@ class AylaApi { } return false; } -<<<<<<< Updated upstream - setAuthFile(this._auth_file_path, jsonResponse); -======= const dateNow = new Date(); jsonResponse['expiration'] = addSeconds(dateNow, jsonResponse['expires_in']); setAuthData(this._config_file_path, jsonResponse); ->>>>>>> Stashed changes this._set_credentials(jsonResponse); return true; } catch { @@ -237,11 +221,7 @@ class AylaApi { this.log.info('Attempting to refresh access token.'); const status = await this.refresh_auth(); if (!status) { -<<<<<<< Updated upstream - this.log.error('Refreshing access token failed. Please check your auth file and recreate it if needed.'); -======= this.log.error('Refreshing access token failed. Please check your credentials and delete them from the config if needed.'); ->>>>>>> Stashed changes this.log.error(this.exit_error_message); return false; } diff --git a/src/sharkiq-js/sharkiq.ts b/src/sharkiq-js/sharkiq.ts index 7136538..6b27d29 100644 --- a/src/sharkiq-js/sharkiq.ts +++ b/src/sharkiq-js/sharkiq.ts @@ -162,15 +162,6 @@ class SharkIqVacuum { const url = this.update_url; try { if (!full_update && property_list.length !== 0) { -<<<<<<< Updated upstream - for (let i = 0; i < property_list.length; i++) { - const property = property_list[i]; - const params = { 'names': 'GET_' + property }; - const auth_header = await this.ayla_api.auth_header(); - const resp = await this.ayla_api.makeRequest('GET', url + formatParams(params), null, auth_header); - if (resp.status !== 200) { - this.log.error('Error getting property values.'); -======= const params = new URLSearchParams(); property_list.forEach((property) => { params.append('names[]', `GET_${property}`); @@ -217,7 +208,6 @@ class SharkIqVacuum { if (properties['error'] !== undefined) { this.log.debug(`Error Message: ${JSON.stringify(properties['error'])}`); } ->>>>>>> Stashed changes const status = await this.ayla_api.attempt_refresh(attempt); if (!status && attempt === 1) { return ERROR_DELAY; @@ -228,37 +218,14 @@ class SharkIqVacuum { this._do_update(full_update, properties); return 0; } -<<<<<<< Updated upstream - const properties = JSON.parse(resp.response); - this._do_update(full_update, properties); -======= } catch { this.log.warn('Error parsing JSON response for properties.'); return ERROR_DELAY; ->>>>>>> Stashed changes - } - } else { - const auth_header = await this.ayla_api.auth_header(); - const resp = await this.ayla_api.makeRequest('GET', url, null, auth_header); - if (resp.status !== 200) { - this.log.error('Error getting property values.'); - const status = await this.ayla_api.attempt_refresh(attempt); - if (!status && attempt === 1) { - return; - } else { - await this.update(property_list, attempt + 1); - return; - } } - const properties = JSON.parse(resp.response); - this._do_update(full_update, properties); } } catch { this.log.debug('Promise Rejected with updating properties.'); -<<<<<<< Updated upstream -======= return ERROR_DELAY; ->>>>>>> Stashed changes } } diff --git a/src/utils.ts b/src/utils.ts index bc7b15f..e54c350 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -8,12 +8,8 @@ function subtractSeconds(date: Date, seconds: number): Date { return new Date(date.getTime() - seconds * 1000); } -<<<<<<< Updated upstream -export { formatParams, addSeconds, subtractSeconds }; -======= function isValidDate(d: Date): boolean { return d instanceof Date && !isNaN(d.getTime()); } export { addSeconds, subtractSeconds, isValidDate }; ->>>>>>> Stashed changes From 02dd3979a2c0a6abfdd1e4363ef10e6b996be345 Mon Sep 17 00:00:00 2001 From: Bubba8291 <37317442+Bubba8291@users.noreply.github.com> Date: Fri, 6 Sep 2024 17:08:03 -0700 Subject: [PATCH 04/23] Fix a promise rejection for getting vacuum power --- src/platformAccessory.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/platformAccessory.ts b/src/platformAccessory.ts index 205dc04..71bdeda 100644 --- a/src/platformAccessory.ts +++ b/src/platformAccessory.ts @@ -240,7 +240,7 @@ export class SharkIQAccessory { } // Get vacuum power for UI - async getFanSpeed(): Promise { + async getFanSpeed(): Promise { this.log.debug('Triggering GET Fan Speed'); await this.retrievePowerMode(); @@ -255,8 +255,9 @@ export class SharkIQAccessory { } else { return 60; } + } else { + return 0; } - return null; } // Set vacuum power from UI (and start/stop vacuum if needed) From 1e529071d8ebb70b43e9e05c2aa606301a8b9103 Mon Sep 17 00:00:00 2001 From: Bubba8291 <37317442+Bubba8291@users.noreply.github.com> Date: Sun, 8 Sep 2024 14:32:14 -0700 Subject: [PATCH 05/23] Reverted back to email/password login that automatically logs in --- config.schema.json | 45 +- package-lock.json | 1499 ++++++++++++++++++++++++++++++++++-- package.json | 5 +- src/config.ts | 185 +++-- src/login.ts | 301 +++++--- src/platform.ts | 28 +- src/platformAccessory.ts | 4 +- src/sharkiq-js/ayla_api.ts | 33 +- src/sharkiq-js/const.ts | 1 + tsconfig.json | 3 +- 10 files changed, 1783 insertions(+), 321 deletions(-) diff --git a/config.schema.json b/config.schema.json index e5f8442..4016658 100644 --- a/config.schema.json +++ b/config.schema.json @@ -10,10 +10,15 @@ "type": "string", "default": "SharkIQ" }, - "oAuthCode": { - "title": "SharkClean OAuth Code", + "email": { + "title": "SharkClean Account Email", "type": "string", - "required": false + "required": true + }, + "password": { + "title": "SharkClean Account Password", + "type": "string", + "required": true }, "vacuums": { "title": "SharkClean Vacuum Devicess", @@ -41,31 +46,20 @@ "type": "integer", "required": false, "default": 30000 - }, - "credentials": { - "title": "SharkClean OAuth", - "type": "object", - "properties": { - "access_token": { - "title": "Access Token", - "type": "string", - "required": false - }, - "refresh_token": { - "title": "Refresh Token", - "type": "string", - "required": false - }, - "expiration": { - "title": "Expiration", - "type": "date", - "required": false - } - } } } }, "layout": [ + { + "type": "fieldset", + "title": "SharkIQ Account Info", + "expandable": true, + "expanded": true, + "items": [ + "email", + "password" + ] + }, { "type": "fieldset", "title": "Vacuum Settings", @@ -93,8 +87,7 @@ }, "europe", "invertDockedStatus", - "dockedUpdateInterval", - "oAuthCode" + "dockedUpdateInterval" ] } ] diff --git a/package-lock.json b/package-lock.json index 7f09643..3197363 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,10 @@ "version": "1.2.0", "license": "Apache-2.0", "dependencies": { - "node-fetch": "^2.6.1" + "node-fetch": "^2.6.1", + "puppeteer": "^23.3.0", + "puppeteer-extra": "^3.3.6", + "puppeteer-extra-plugin-stealth": "^2.11.2" }, "devDependencies": { "@eslint/compat": "^1.1.1", @@ -43,6 +46,114 @@ "node": ">=0.10.0" } }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -317,6 +428,34 @@ "node": ">= 8" } }, + "node_modules/@puppeteer/browsers": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.4.0.tgz", + "integrity": "sha512-x8J1csfIygOwf6D6qUAZ0ASk3z63zPb7wkNeHRerCMh82qWKUrOgkuP005AJC8lDL6/evtXETGEJVcwykKT4/g==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.3.6", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "license": "MIT" + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -341,17 +480,32 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.12", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "18.17.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.12.tgz", "integrity": "sha512-d6xjC9fJ/nSnfDeU0AMDsaJyb1iHsqCSOdi84w4u+SlN/UgQdY5tRhpMzaFYsI4mnpvgTivEaQd0yOUhAtOnEQ==", - "dev": true + "devOptional": true }, "node_modules/@types/semver": { "version": "7.5.0", @@ -359,6 +513,16 @@ "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", "dev": true }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", @@ -584,6 +748,18 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -604,7 +780,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -613,7 +788,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -646,8 +820,16 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, "node_modules/array-buffer-byte-length": { "version": "1.0.1", @@ -681,6 +863,18 @@ "node": ">=8" } }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -697,11 +891,92 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", + "license": "Apache-2.0" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/bare-events": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", + "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-fs": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.3.tgz", + "integrity": "sha512-7RYKL+vZVCyAsMLi5SPu7QGauGGT8avnP/HO571ndEuV4MYdGXvLhtW67FuLPeEI8EiIY7zbbRR9x7x7HU0kgw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "node_modules/bare-os": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.2.tgz", + "integrity": "sha512-HZoJwzC+rZ9lqEemTMiO0luOePoGYNBgsLLgegKR/cljiJvcDNhDZQkzC+NC5Oh0aHbdBNSOHpghwMuB5tqhjg==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, + "node_modules/bare-stream": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.2.1.tgz", + "integrity": "sha512-YTB47kHwBW9zSG8LD77MIBAAQXjU2WjAkMHeeb7hUplVs6+IoM5I7uEVQNPMB7lj9r8I76UMdoMkGnCodHOLqg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "b4a": "^1.6.6", + "streamx": "^2.18.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } }, "node_modules/binary-extensions": { "version": "2.2.0", @@ -729,7 +1004,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -748,6 +1022,39 @@ "node": ">=8" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -778,7 +1085,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -838,11 +1144,54 @@ "node": ">= 6" } }, + "node_modules/chromium-bidi": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.5.tgz", + "integrity": "sha512-RuLrmzYrxSb0s9SgpB+QN5jJucPduZQ/9SIe76MDxYJuecPW5mxMdacJ1f4EtgiV+R0p3sCkznTMvH0MPGFqjA==", + "license": "Apache-2.0", + "dependencies": { + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-deep": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", + "integrity": "sha512-we+NuQo2DHhSl+DP6jlUiAhyAjBQrYnpOk15rN6c6JSPScjiCLh8IbSU+VTcph6YS3o7mASE8a0+gbZ7ChLpgg==", + "license": "MIT", + "dependencies": { + "for-own": "^0.1.3", + "is-plain-object": "^2.0.1", + "kind-of": "^3.0.2", + "lazy-cache": "^1.0.3", + "shallow-clone": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -853,8 +1202,7 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/commander": { "version": "5.1.0", @@ -868,8 +1216,33 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } }, "node_modules/create-require": { "version": "1.1.1", @@ -891,13 +1264,22 @@ "node": ">= 8" } }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -947,6 +1329,15 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -983,6 +1374,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1330662", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1330662.tgz", + "integrity": "sha512-pzh6YQ8zZfz3iKlCvgzVCu22NdpZ8hNmwU6WnQjNVquh0A9iVosPtNLWDwaWVGyrntQlltPFztTMK5Cg6lfCuw==", + "license": "BSD-3-Clause" + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -1034,6 +1445,39 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", @@ -1078,6 +1522,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1090,7 +1543,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint": { + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint": { "version": "8.48.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.48.0.tgz", "integrity": "sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==", @@ -1245,6 +1728,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", @@ -1300,7 +1796,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -1320,12 +1815,38 @@ "through": "^2.3.8" } }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", @@ -1384,6 +1905,15 @@ "reusify": "^1.0.4" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -1455,6 +1985,27 @@ "is-callable": "^1.1.3" } }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", + "license": "MIT", + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/from": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", @@ -1465,7 +2016,6 @@ "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -1478,8 +2028,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.3", @@ -1524,6 +2073,15 @@ "node": ">=8" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -1544,11 +2102,54 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", + "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4", + "fs-extra": "^11.2.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -1627,8 +2228,7 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/graphemer": { "version": "1.4.0", @@ -1774,6 +2374,52 @@ "node": ">=10.17.0" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -1793,7 +2439,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -1818,7 +2463,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -1827,8 +2471,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/internal-slot": { "version": "1.0.7", @@ -1845,6 +2488,19 @@ "node": ">= 0.4" } }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -1879,6 +2535,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, "node_modules/is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -1921,6 +2583,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -1950,6 +2618,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -1959,6 +2636,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -2019,6 +2705,18 @@ "node": ">=8" } }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -2140,11 +2838,25 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -2152,12 +2864,24 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "license": "MIT" + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2174,7 +2898,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, "dependencies": { "universalify": "^2.0.0" }, @@ -2191,6 +2914,27 @@ "json-buffer": "3.0.1" } }, + "node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2204,6 +2948,12 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2226,15 +2976,12 @@ "dev": true }, "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/make-error": { @@ -2249,6 +2996,20 @@ "integrity": "sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ==", "dev": true }, + "node_modules/merge-deep": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", + "integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==", + "license": "MIT", + "dependencies": { + "arr-union": "^3.1.0", + "clone-deep": "^0.2.4", + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2259,12 +3020,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -2275,7 +3037,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2292,6 +3053,34 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA==", + "license": "MIT", + "dependencies": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-object/node_modules/for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -2305,10 +3094,10 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/multicast-dns": { "version": "7.2.5", @@ -2341,6 +3130,15 @@ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", "dev": true }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -2515,7 +3313,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -2567,11 +3364,42 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pac-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.2.tgz", + "integrity": "sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg==", + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.5", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -2579,20 +3407,37 @@ "node": ">=6" } }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, "engines": { "node": ">=8" - } + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -2624,6 +3469,18 @@ "through": "~2.3" } }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "license": "ISC" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -2655,12 +3512,56 @@ "node": ">= 0.8.0" } }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "dev": true }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -2670,6 +3571,180 @@ "node": ">=6" } }, + "node_modules/puppeteer": { + "version": "23.3.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.3.0.tgz", + "integrity": "sha512-e2jY8cdWSUGsrLxqGm3hIbJq/UIk1uOY8XY7SM51leXkH7shrIyE91lK90Q9byX6tte+cyL3HKqlWBEd6TjWTA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.4.0", + "chromium-bidi": "0.6.5", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1330662", + "puppeteer-core": "23.3.0", + "typed-query-selector": "^2.12.0" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "23.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.3.0.tgz", + "integrity": "sha512-sB2SsVMFs4gKad5OCdv6w5vocvtEUrRl0zQqSyRPbo/cj1Ktbarmhxy02Zyb9R9HrssBcJDZbkrvBnbaesPyYg==", + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.4.0", + "chromium-bidi": "0.6.5", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1330662", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-extra": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/puppeteer-extra/-/puppeteer-extra-3.3.6.tgz", + "integrity": "sha512-rsLBE/6mMxAjlLd06LuGacrukP2bqbzKCLzV1vrhHFavqQE/taQ2UXv3H5P0Ls7nsrASa+6x3bDbXHpqMwq+7A==", + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.0", + "debug": "^4.1.1", + "deepmerge": "^4.2.2" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "@types/puppeteer": "*", + "puppeteer": "*", + "puppeteer-core": "*" + }, + "peerDependenciesMeta": { + "@types/puppeteer": { + "optional": true + }, + "puppeteer": { + "optional": true + }, + "puppeteer-core": { + "optional": true + } + } + }, + "node_modules/puppeteer-extra-plugin": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.3.tgz", + "integrity": "sha512-6RNy0e6pH8vaS3akPIKGg28xcryKscczt4wIl0ePciZENGE2yoaQJNd17UiEbdmh5/6WW6dPcfRWT9lxBwCi2Q==", + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.0", + "debug": "^4.1.1", + "merge-deep": "^3.0.1" + }, + "engines": { + "node": ">=9.11.2" + }, + "peerDependencies": { + "playwright-extra": "*", + "puppeteer-extra": "*" + }, + "peerDependenciesMeta": { + "playwright-extra": { + "optional": true + }, + "puppeteer-extra": { + "optional": true + } + } + }, + "node_modules/puppeteer-extra-plugin-stealth": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-stealth/-/puppeteer-extra-plugin-stealth-2.11.2.tgz", + "integrity": "sha512-bUemM5XmTj9i2ZerBzsk2AN5is0wHMNE6K0hXBzBXOzP5m5G3Wl0RHhiqKeHToe/uIH8AoZiGhc1tCkLZQPKTQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "puppeteer-extra-plugin": "^3.2.3", + "puppeteer-extra-plugin-user-preferences": "^2.4.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "playwright-extra": "*", + "puppeteer-extra": "*" + }, + "peerDependenciesMeta": { + "playwright-extra": { + "optional": true + }, + "puppeteer-extra": { + "optional": true + } + } + }, + "node_modules/puppeteer-extra-plugin-user-data-dir": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-data-dir/-/puppeteer-extra-plugin-user-data-dir-2.4.1.tgz", + "integrity": "sha512-kH1GnCcqEDoBXO7epAse4TBPJh9tEpVEK/vkedKfjOVOhZAvLkHGc9swMs5ChrJbRnf8Hdpug6TJlEuimXNQ+g==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^10.0.0", + "puppeteer-extra-plugin": "^3.2.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "playwright-extra": "*", + "puppeteer-extra": "*" + }, + "peerDependenciesMeta": { + "playwright-extra": { + "optional": true + }, + "puppeteer-extra": { + "optional": true + } + } + }, + "node_modules/puppeteer-extra-plugin-user-preferences": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.4.1.tgz", + "integrity": "sha512-i1oAZxRbc1bk8MZufKCruCEC3CCafO9RKMkkodZltI4OqibLFXF3tj6HZ4LZ9C5vCXZjYcDWazgtY69mnmrQ9A==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "deepmerge": "^4.2.2", + "puppeteer-extra-plugin": "^3.2.3", + "puppeteer-extra-plugin-user-data-dir": "^2.4.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "playwright-extra": "*", + "puppeteer-extra": "*" + }, + "peerDependenciesMeta": { + "playwright-extra": { + "optional": true + }, + "puppeteer-extra": { + "optional": true + } + } + }, "node_modules/q": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/q/-/q-1.1.2.tgz", @@ -2709,6 +3784,12 @@ } ] }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "license": "MIT" + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -2740,11 +3821,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -2763,7 +3852,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -2824,13 +3912,10 @@ "dev": true }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -2872,6 +3957,42 @@ "node": ">= 0.4" } }, + "node_modules/shallow-clone": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", + "integrity": "sha512-J1zdXCky5GmNnuauESROVu31MQSnLoYvlyEn6j2Ztk6Q5EHFIhxkMhYcv6vuDzl2XEzoRr856QwzMgWM/TmZgw==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.1", + "kind-of": "^2.0.1", + "lazy-cache": "^0.2.3", + "mixin-object": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shallow-clone/node_modules/kind-of": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", + "integrity": "sha512-0u8i1NZ/mg0b+W3MGGw5I7+6Eib2nx72S/QvXa0hYjEkjTknYmEYQJwGu3mLC0BrhtJjtQafTkyRUQ75Kx0LVg==", + "license": "MIT", + "dependencies": { + "is-buffer": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shallow-clone/node_modules/lazy-cache": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", + "integrity": "sha512-gkX52wvU/R8DVMMt78ATVPFMJqfW8FPz1GZ1sVHBVQHmu/WvhIWE4cE1GBzhJNFicDeYhnwp6Rl35BcAIM3YOQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -2933,11 +4054,49 @@ "node": ">=8" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", + "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -2964,6 +4123,12 @@ "node": "*" } }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause" + }, "node_modules/stop-iteration-iterator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", @@ -2987,11 +4152,38 @@ "through": "~2.3.4" } }, + "node_modules/streamx": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.20.0.tgz", + "integrity": "sha512-ZGd1LhDeGFucr1CUCTBOS58ZhEendd0ttpGT3usTvosS4ntIwKN9LJFp+OeCSprsCPL14BXVRZlHGRY1V9PVzQ==", + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -3023,6 +4215,40 @@ "node": ">=8" } }, + "node_modules/tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/text-decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.1.tgz", + "integrity": "sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -3032,8 +4258,7 @@ "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" }, "node_modules/thunky": { "version": "1.1.0", @@ -3117,8 +4342,7 @@ "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tsutils": { "version": "3.21.0", @@ -3171,11 +4395,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "license": "MIT" + }, "node_modules/typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3184,6 +4414,16 @@ "node": ">=4.2.0" } }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -3194,7 +4434,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, "engines": { "node": ">= 10.0.0" } @@ -3208,6 +4447,12 @@ "punycode": "^2.1.0" } }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "license": "MIT" + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -3299,11 +4544,48 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } }, "node_modules/xml2js": { "version": "0.5.0", @@ -3327,11 +4609,51 @@ "node": ">=4.0" } }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } }, "node_modules/yn": { "version": "3.1.1", @@ -3353,6 +4675,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 42293eb..66ebf2b 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,10 @@ "sharkiq" ], "dependencies": { - "node-fetch": "^2.6.1" + "node-fetch": "^2.6.1", + "puppeteer": "^23.3.0", + "puppeteer-extra": "^3.3.6", + "puppeteer-extra-plugin-stealth": "^2.11.2" }, "devDependencies": { "@eslint/compat": "^1.1.1", diff --git a/src/config.ts b/src/config.ts index d700605..3663a4e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,90 +1,159 @@ import { promises as fs } from 'fs'; +import crypto from 'crypto'; + import { AuthData, OAuthData } from './type'; import { PLATFORM_NAME } from './settings'; +import { global_vars } from './sharkiq-js/const'; -async function getAuthData(configPath: string): Promise { +export async function getAuthData(authFilePath: string): Promise { try { - const data = await fs.readFile(configPath, 'utf8'); - const currentConfig = JSON.parse(data); + const data = await fs.readFile(authFilePath, 'utf8'); + const authData = JSON.parse(data); + return authData; + } catch (error) { + return Promise.reject('Error reading OAuth data: ' + error); + } +} - if (!Array.isArray(currentConfig.platforms)) { - return null; - } +// export async function getAuthData(configPath: string): Promise { +// try { +// const data = await fs.readFile(configPath, 'utf8'); +// const currentConfig = JSON.parse(data); - const pluginConfig = currentConfig.platforms.find((x: { platform: string }) => x.platform === PLATFORM_NAME); +// if (!Array.isArray(currentConfig.platforms)) { +// return Promise.reject('No platforms array found in config'); +// } - if (!pluginConfig) { - return null; - } +// const pluginConfig = currentConfig.platforms.find((x: { platform: string }) => x.platform === PLATFORM_NAME); - if (typeof pluginConfig.credentials !== 'object') { - return null; - } +// if (!pluginConfig) { +// return Promise.reject(`${PLATFORM_NAME} platform not found in config`); +// } - return pluginConfig.credentials; - } catch { - return null; - } -} +// if (typeof pluginConfig.credentials !== 'object') { +// return Promise.reject(`No credentials object found in ${PLATFORM_NAME} platform config`); +// } + +// if (typeof pluginConfig.credentials.access_token !== 'string' || +// typeof pluginConfig.credentials.refresh_token !== 'string' || +// typeof pluginConfig.credentials.expiration !== 'string') { +// return Promise.reject('Invalid types found in credentials object'); +// } -async function setAuthData(configPath: string, data: AuthData): Promise { +// return pluginConfig.credentials; +// } catch (error) { +// return Promise.reject('Error reading auth data from config: ' + error); +// } +// } + +export async function setAuthData(authFilePath: string, data: AuthData): Promise { try { - if (!data) { - return false; - } - const currentConfigData = await fs.readFile(configPath, 'utf8'); - const currentConfig = JSON.parse(currentConfigData); - - if (!Array.isArray(currentConfig.platforms)) { - return false; - } - - const pluginConfig = currentConfig.platforms.find((x: { platform: string }) => x.platform === PLATFORM_NAME); - - if (!pluginConfig) { - return false; - } - - if (typeof pluginConfig.credentials !== 'object') { - pluginConfig.credentials = {}; - } - - pluginConfig.credentials.access_token = data.access_token; - pluginConfig.credentials.refresh_token = data.refresh_token; - pluginConfig.credentials.expiration = data.expiration; - - await fs.writeFile(configPath, JSON.stringify(currentConfig, null, 4), 'utf8'); - return true; - } catch { - return false; + await fs.writeFile(authFilePath, JSON.stringify(data, null, 4), 'utf8'); + } catch (error) { + return Promise.reject('Error writing auth data: ' + error); } } -async function getOAuthData(oAuthFilePath: string): Promise { +// export async function setAuthData(configPath: string, data: AuthData): Promise { +// try { +// if (!data) { +// return Promise.reject('No data provided'); +// } +// const currentConfigData = await fs.readFile(configPath, 'utf8'); +// const currentConfig = JSON.parse(currentConfigData); + +// if (!Array.isArray(currentConfig.platforms)) { +// return Promise.reject('No platforms array found in config'); +// } + +// const pluginConfig = currentConfig.platforms.find((x: { platform: string }) => x.platform === PLATFORM_NAME); + +// if (!pluginConfig) { +// return Promise.reject('No platform found in config'); +// } + +// if (typeof pluginConfig.credentials !== 'object') { +// pluginConfig.credentials = {}; +// } + +// pluginConfig.credentials.access_token = data.access_token; +// pluginConfig.credentials.refresh_token = data.refresh_token; +// pluginConfig.credentials.expiration = data.expiration; + +// try { +// await fs.writeFile(configPath, JSON.stringify(currentConfig, null, 4), 'utf8'); +// } catch (error) { +// return Promise.reject(`${error}`); +// } +// } catch (error) { +// return Promise.reject('Error writing auth data to config: ' + error); +// } +// } + +export async function getOAuthData(oAuthFilePath: string): Promise { try { const data = await fs.readFile(oAuthFilePath, 'utf8'); const oAuthData = JSON.parse(data); return oAuthData; - } catch { - return null; + } catch (error) { + return Promise.reject('Error reading OAuth data: ' + error); } } -async function setOAuthData(oAuthFilePath: string, data: OAuthData): Promise { +async function setOAuthData(oAuthFilePath: string, data: OAuthData): Promise { try { await fs.writeFile(oAuthFilePath, JSON.stringify(data, null, 4), 'utf8'); - return true; - } catch { - return false; + } catch (error) { + return Promise.reject('Error writing OAuth data: ' + error); } } -async function removeFile(filePath: string): Promise { +export async function removeFile(filePath: string): Promise { try { await fs.unlink(filePath); - } catch { - return; + } catch (error) { + return Promise.reject('Error removing file: ' + error); } } -export { getAuthData, setAuthData, getOAuthData, setOAuthData, removeFile }; +export async function generateURL(oauth_file_path: string): Promise { + const state = generateRandomString(43); + const code_verify = generateRandomString(43); + const code_challenge = crypto.createHash('sha256').update(code_verify).digest('base64') + .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); + + const oAuthData = { + state: state, + code_verify: code_verify, + code_challenge: code_challenge, + }; + + try { + await setOAuthData(oauth_file_path, oAuthData); + + const url = global_vars.OAUTH.AUTH_URL + + '?response_type=code' + + '&client_id='+encodeURIComponent(global_vars.OAUTH.CLIENT_ID) + + '&state='+encodeURIComponent(oAuthData.state) + + '&scope='+encodeURIComponent(global_vars.OAUTH.SCOPES) + + '&redirect_uri='+encodeURIComponent(global_vars.OAUTH.REDIRECT_URI) + + '&code_challenge='+encodeURIComponent(oAuthData.code_challenge) + + '&code_challenge_method=S256' + + '&ui_locales=en' + + '&auth0Client='+ global_vars.OAUTH.AUTH0_CLIENT; + + return url; + } catch (error) { + return Promise.reject('Error generating Shark login URL: ' + error); + } +} + +function generateRandomString(length: number): string { + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let result = ''; + for (let i = 0; i < length; i++) { + const randomIndex = Math.floor(Math.random() * characters.length); + result += characters.charAt(randomIndex); + } + return result; +} diff --git a/src/login.ts b/src/login.ts index 501d28e..097ba8c 100644 --- a/src/login.ts +++ b/src/login.ts @@ -1,72 +1,167 @@ import { Logger } from 'homebridge'; -import { getAuthData, setAuthData, getOAuthData, setOAuthData, removeFile } from './config'; -import { OAuthData } from './type'; -import { addSeconds } from './utils'; -import { global_vars } from './sharkiq-js/const'; - +import { setTimeout } from 'node:timers/promises'; import fetch from 'node-fetch'; -import crypto from 'crypto'; -import { join } from 'path'; +import puppeteer from 'puppeteer-extra'; +import StealthPlugin from 'puppeteer-extra-plugin-stealth'; + +import { global_vars } from './sharkiq-js/const'; +import { getAuthData, setAuthData, getOAuthData, removeFile, generateURL } from './config'; +import { addSeconds } from './utils'; +import { OAuthData } from './type'; export class Login { + private oAuthCode = ''; + public log: Logger; - public storagePath: string; - public oAuthFile: string; - public oAuthCode: string; + public auth_file: string; + public oauth_file: string; + public email: string; + public password: string; public app_id: string; public app_secret: string; - public config_file: string; - public oauth_file_path: string; constructor(log: Logger, - storagePath: string, - oAuthCode: string, - config_file: string, + auth_file: string, + oauth_file: string, + email: string, + password: string, app_id = global_vars.SHARK_APP_ID, app_secret = global_vars.SHARK_APP_SECRET, ) { this.log = log; - this.storagePath = storagePath; - this.oAuthCode = oAuthCode; - this.config_file = config_file; - this.oAuthFile = global_vars.OAUTH.FILE; + this.auth_file = auth_file; + this.oauth_file = oauth_file; + this.email = email; + this.password = password; this.app_id = app_id; this.app_secret = app_secret; - this.oauth_file_path = join(this.storagePath, this.oAuthFile); } - public async checkLogin(): Promise { - const auth_data = await getAuthData(this.config_file); - const oauth_file = await getOAuthData(this.oauth_file_path); - if (!auth_data) { - if (this.oAuthCode !== '') { - if (!oauth_file) { - this.log.error('No OAuth data found with oAuthCode present. Please remove the oAuthCode from the config.'); - return false; - } - const status = await this.login(this.oAuthCode, oauth_file); - if (!status) { - this.log.error('Error logging in to Shark'); - return false; + public async checkLogin(): Promise { + try { + await getAuthData(this.auth_file); + this.log.debug('Already logged in to Shark'); + } catch { + this.log.debug('Not logged in to Shark'); + const email = this.email; + const password = this.password; + + try { + const url = await generateURL(this.oauth_file); + + await this.login(email, password, url); + if (this.oAuthCode === '') { + return Promise.reject('Error: No OAuth code found'); } else { - this.log.info('Successfully logged in to Shark'); - return true; + const ouath_data = await getOAuthData(this.oauth_file); + await this.loginCallback(this.oAuthCode, ouath_data); } + } catch (error) { + return Promise.reject(`${error}`); } - const url = await this.generateURL(); - if (!url) { - this.log.error('Error generating Shark login URL'); - return false; + } + } + + private async login(email: string, password: string, url: string): Promise { + const stealth = StealthPlugin(); + + puppeteer.use(stealth); + + const headless = true; + + this.log.debug('Headless:', headless); + let error = ''; + try { + const browser = await puppeteer.launch({ + headless: headless, + targetFilter: (target) => target.type() !== 'other', + }); + this.log.debug('Opening chromium browser...'); + const page = await browser.newPage(); + const pages = await browser.pages(); + pages[0].close(); + this.log.debug('Navigating to Shark login page...'); + await page.goto(url, { waitUntil: 'domcontentloaded' }); + + page.on('response', async (response) => { + if (response.url().includes('login?')) { + this.log.debug('Retrieving login response...'); + if (!response.ok() && ![301, 302].includes(response.status())) { + this.log.debug('Error logging in: HTTP', response.status()); + await setTimeout(1000); + await page.screenshot({ path: 'login_error.png' }); + const errorMessages = await page.$$eval('span[class="ulp-input-error-message"]', (el) => el.map((x) => x.innerText.trim())); + const promptAlert = await page.$('div[id="prompt-alert"]'); + if (promptAlert) { + const alertP = await promptAlert.$('p'); + if (alertP) { + const alertText = await alertP.evaluate((el) => el.textContent?.trim()); + if (alertText) { + errorMessages.push(alertText); + } + } + } + error = errorMessages.join(', '); + await browser.close(); + } + } else if (response.url().includes('resume?')) { + this.log.debug('Retrieving callback response...'); + const headers = response.headers(); + const queries = headers.location.split('?'); + if (queries.length > 1) { + const code = queries[1].split('&').find((query: string) => query.includes('code=')); + if (code) { + this.oAuthCode = code.slice(5); + } + await browser.close(); + } + } + }); + + if (headless) { + this.log.debug('Inputing login info...'); + await page.waitForSelector('button[name="action"]'); + await setTimeout(1000); + + await page.waitForSelector('input[inputMode="email"]'); + await page.type('input[inputMode="email"]', email); + + await setTimeout(1000); + await page.type('input[type="password"]', password); + let verified = false; + let attempts = 0; + while (!verified) { + await setTimeout(5000); + const captchaInput = await page.$('input[name="captcha"]'); + const needsCaptcha = await captchaInput?.$eval('input[name="captcha"]', (el) => el.value === ''); + if (!needsCaptcha) { + verified = true; + } else { + attempts++; + if (attempts > 3) { + error = `Unable to verify captcha after ${attempts} attempts`; + await browser.close(); + } else { + this.log.debug('Captcha not verified. Attempt #', attempts); + const checkbox = await page.$('input[type="checkbox"]'); + if (checkbox) { + await checkbox.click(); + } + } + } + } + await page.click('button[name="action"]'); + await setTimeout(5000); } - this.log.info('Please visit the following URL to login to Shark:', url); - return false; - } else { - this.log.debug('Already logged in to Shark'); - return true; + } catch (error) { + return Promise.reject(`Error: ${error}`); + } + if (error !== '') { + return Promise.reject(`Error: ${error}`); } } - private async login(code: string, oAuthData: OAuthData): Promise { + private async loginCallback(code: string, oAuthData: OAuthData): Promise { const data = { grant_type: 'authorization_code', client_id: global_vars.OAUTH.CLIENT_ID, @@ -75,86 +170,52 @@ export class Login { redirect_uri: global_vars.OAUTH.REDIRECT_URI, }; - try { - const reqData = { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Auth0-Client': global_vars.OAUTH.AUTH0_CLIENT, - }, - body: JSON.stringify(data), - }; - - const response = await fetch(global_vars.OAUTH.TOKEN_URL, reqData); - const tokenData = await response.json(); - - const reqData2 = { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - 'app_id': this.app_id, - 'app_secret': this.app_secret, - 'token': tokenData.id_token, - }), - }; - const response2 = await fetch(`${global_vars.LOGIN_URL}/api/v1/token_sign_in`, reqData2); - const aylaTokenData = await response2.json(); - const dateNow = new Date(); - aylaTokenData['expiration'] = addSeconds(dateNow, aylaTokenData['expires_in']); - const status = setAuthData(this.config_file, aylaTokenData); - if (!status) { - this.log.error('Error saving auth file.'); - return false; - } - await removeFile(this.oauth_file_path); - return true; - } catch (error) { - this.log.error('Error: ' + error); - return false; - } - } - - private async generateURL(): Promise { - const state = this.generateRandomString(43); - const code_verify = this.generateRandomString(43); - const code_challenge = crypto.createHash('sha256').update(code_verify).digest('base64') - .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); - - const oAuthData = { - state: state, - code_verify: code_verify, - code_challenge: code_challenge, + const reqData = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Auth0-Client': global_vars.OAUTH.AUTH0_CLIENT, + }, + body: JSON.stringify(data), }; + this.log.debug('Request Data', JSON.stringify(data)); - const status = await setOAuthData(this.oauth_file_path, oAuthData); - if (!status) { - return null; + const response = await fetch(global_vars.OAUTH.TOKEN_URL, reqData); + if (!response.ok) { + return Promise.reject('Unable to get token data. HTTP ' + response.status); } + const tokenData = await response.json(); + this.log.debug('Token Data:', JSON.stringify(tokenData)); - const url = global_vars.OAUTH.AUTH_URL - + '?response_type=code' - + '&client_id='+encodeURIComponent(global_vars.OAUTH.CLIENT_ID) - + '&state='+encodeURIComponent(oAuthData.state) - + '&scope='+encodeURIComponent(global_vars.OAUTH.SCOPES) - + '&redirect_uri='+encodeURIComponent(global_vars.OAUTH.REDIRECT_URI) - + '&code_challenge='+encodeURIComponent(oAuthData.code_challenge) - + '&code_challenge_method=S256' - + '&ui_locales=en' - + '&auth0Client='+ global_vars.OAUTH.AUTH0_CLIENT; - - return url; - } - - private generateRandomString(length: number): string { - const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - let result = ''; - for (let i = 0; i < length; i++) { - const randomIndex = Math.floor(Math.random() * characters.length); - result += characters.charAt(randomIndex); + const reqData2 = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + 'app_id': this.app_id, + 'app_secret': this.app_secret, + 'token': tokenData.id_token, + }), + }; + const response2 = await fetch(`${global_vars.LOGIN_URL}/api/v1/token_sign_in`, reqData2); + if (!response2.ok) { + return Promise.reject('Unable to get authorization tokens. HTTP ' + response2.status); + } + const aylaTokenData = await response2.json(); + const dateNow = new Date(); + aylaTokenData['expiration'] = addSeconds(dateNow, aylaTokenData['expires_in']); + this.log.debug('Setting auth data...', JSON.stringify(aylaTokenData)); + try { + await setAuthData(this.auth_file, aylaTokenData); + } catch (error) { + return Promise.reject(`${error}`); + } + try { + await removeFile(this.oauth_file); + } catch { + return; } - return result; } } diff --git a/src/platform.ts b/src/platform.ts index ac9b457..9c86c76 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -1,4 +1,5 @@ import { API, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service, Characteristic } from 'homebridge'; +import { join } from 'path'; import { PLATFORM_NAME, PLUGIN_NAME } from './settings'; import { SharkIQAccessory } from './platformAccessory'; @@ -7,6 +8,7 @@ import { Login } from './login'; import { get_ayla_api } from './sharkiq-js/ayla_api'; import { SharkIqVacuum } from './sharkiq-js/sharkiq'; +import { global_vars } from './sharkiq-js/const'; // SharkIQPlatform Main Class export class SharkIQPlatform implements DynamicPlatformPlugin { @@ -44,7 +46,7 @@ export class SharkIQPlatform implements DynamicPlatformPlugin { this.discoverDevices(); }) .catch((error) => { - log.error('Error with login. Please check logs:'); + log.error('Error with login.'); log.error(error); }); }); @@ -52,22 +54,24 @@ export class SharkIQPlatform implements DynamicPlatformPlugin { // Attempt to login and fetch devices. login = async (): Promise => { - const oAuthCode = this.config.oAuthCode || ''; const europe = this.config.europe || false; - const configFilePath = this.api.user.configPath(); - const login = new Login(this.log, this.api.user.storagePath(), oAuthCode, configFilePath); + const storagePath = this.api.user.storagePath(); + const auth_file = join(storagePath, global_vars.FILE); + const oauth_file = join(storagePath, global_vars.OAUTH.FILE); + const email = this.config.email; + const password = this.config.password; + if (!email || typeof email !== 'string' || !password || typeof password !== 'string') { + return Promise.reject('Error: Email and password must be present in the config'); + } + const login = new Login(this.log, auth_file, oauth_file, email, password); try { - const status = await login.checkLogin(); - if (!status) { - this.log.error('Error logging in to Shark'); - return []; - } - const ayla_api = get_ayla_api(configFilePath, this.log, europe); + await login.checkLogin(); + const ayla_api = get_ayla_api(auth_file, this.log, europe); await ayla_api.sign_in(); const devices = await ayla_api.get_devices(); return devices; - } catch { - return []; + } catch (error) { + return Promise.reject(`${error}`); } }; diff --git a/src/platformAccessory.ts b/src/platformAccessory.ts index 71bdeda..4c1a806 100644 --- a/src/platformAccessory.ts +++ b/src/platformAccessory.ts @@ -158,9 +158,7 @@ export class SharkIQAccessory { // Update paused and active state on switch updateStates(): void { const mode = this.device.operating_mode(); - if (mode === OperatingModes.START) { - this.service.updateCharacteristic(this.platform.Characteristic.Active, this.platform.Characteristic.Active.ACTIVE); - } else if (mode === OperatingModes.STOP) { + if (mode === OperatingModes.START || mode === OperatingModes.STOP) { this.service.updateCharacteristic(this.platform.Characteristic.Active, this.platform.Characteristic.Active.ACTIVE); } else { this.service.updateCharacteristic(this.platform.Characteristic.Active, this.platform.Characteristic.Active.INACTIVE); diff --git a/src/sharkiq-js/ayla_api.ts b/src/sharkiq-js/ayla_api.ts index 82e99e8..19f2a84 100644 --- a/src/sharkiq-js/ayla_api.ts +++ b/src/sharkiq-js/ayla_api.ts @@ -15,16 +15,16 @@ type APIResponse = { }; // New AylaApi object -const get_ayla_api = function (config_file_path: string, log: Logger, europe = false): AylaApi { +const get_ayla_api = function (auth_file_path: string, log: Logger, europe = false): AylaApi { if (europe) { - return new AylaApi(config_file_path, global_vars.EU_SHARK_APP_ID, global_vars.EU_SHARK_APP_SECRET, log, europe); + return new AylaApi(auth_file_path, global_vars.EU_SHARK_APP_ID, global_vars.EU_SHARK_APP_SECRET, log, europe); } else { - return new AylaApi(config_file_path, global_vars.SHARK_APP_ID, global_vars.SHARK_APP_SECRET, log, europe); + return new AylaApi(auth_file_path, global_vars.SHARK_APP_ID, global_vars.SHARK_APP_SECRET, log, europe); } }; class AylaApi { - _config_file_path: string; + _auth_file_path: string; _access_token: string | null; _refresh_token: string | null; _auth_expiration: Date | null; @@ -36,8 +36,8 @@ class AylaApi { // Simple Ayla Networks API wrapper - constructor(config_file_path, app_id, app_secret, log, europe = false) { - this._config_file_path = config_file_path; + constructor(auth_file_path, app_id, app_secret, log, europe = false) { + this._auth_file_path = auth_file_path; this._access_token = null; this._refresh_token = null; this._auth_expiration = null; @@ -98,13 +98,14 @@ class AylaApi { // Sign in with auth file async sign_in(): Promise { this.log.debug('Signing in.'); - const authData = await getAuthData(this._config_file_path); - if (!authData) { - this.log.error('Auth file not found.'); + try { + const authData = await getAuthData(this._auth_file_path); + this._set_credentials(authData); + return true; + } catch (error) { + this.log.error(`${error}`); return false; } - this._set_credentials(authData); - return true; } // Refresh auth token @@ -125,7 +126,7 @@ class AylaApi { } const dateNow = new Date(); jsonResponse['expiration'] = addSeconds(dateNow, jsonResponse['expires_in']); - setAuthData(this._config_file_path, jsonResponse); + await setAuthData(this._auth_file_path, jsonResponse); this._set_credentials(jsonResponse); return true; } catch { @@ -260,7 +261,7 @@ class AylaApi { return d; } catch { this.log.debug('Promise Rejected with list devices.'); - return []; + return Promise.reject('Error: Unable to list devices.'); } } @@ -278,9 +279,9 @@ class AylaApi { } } return devices; - } catch { - this.log.debug('Promise Rejected with getting devices.'); - return []; + } catch (error) { + this.log.error(`${error}`); + return Promise.reject('Error: Unable to get devices.'); } } } diff --git a/src/sharkiq-js/const.ts b/src/sharkiq-js/const.ts index 41b2237..6f5a802 100644 --- a/src/sharkiq-js/const.ts +++ b/src/sharkiq-js/const.ts @@ -13,6 +13,7 @@ const global_vars = { SCOPES: 'openid profile email offline_access read:users read:current_user read:user_idp_tokens', FILE: '.sharkiq_oauth.json', }, + FILE: '.sharkiq.json', EU_DEVICE_URL: 'https://ads-eu.aylanetworks.com', EU_LOGIN_URL: 'https://user-field-eu.aylanetworks.com', EU_SHARK_APP_ID: 'Shark-Android-EUField-Fw-id', diff --git a/tsconfig.json b/tsconfig.json index bf07099..31b5ae3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,8 @@ "es2015", "es2016", "es2017", - "es2018" + "es2018", + "dom" ], "declaration": true, "declarationMap": true, From 0b4ba56ef1d9c9132bd1883496c7c4316f73c528 Mon Sep 17 00:00:00 2001 From: Bubba8291 <37317442+Bubba8291@users.noreply.github.com> Date: Sun, 8 Sep 2024 14:53:10 -0700 Subject: [PATCH 06/23] Remove extra import and linted code --- src/config.ts | 1 - src/platformAccessory.ts | 36 ++++++++++++++++++------------------ 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/config.ts b/src/config.ts index 3663a4e..dd0ab66 100644 --- a/src/config.ts +++ b/src/config.ts @@ -2,7 +2,6 @@ import { promises as fs } from 'fs'; import crypto from 'crypto'; import { AuthData, OAuthData } from './type'; -import { PLATFORM_NAME } from './settings'; import { global_vars } from './sharkiq-js/const'; export async function getAuthData(authFilePath: string): Promise { diff --git a/src/platformAccessory.ts b/src/platformAccessory.ts index f1f226a..c524993 100644 --- a/src/platformAccessory.ts +++ b/src/platformAccessory.ts @@ -109,24 +109,24 @@ export class SharkIQAccessory { // .then((delay) => { // this.dockedDelay = delay; // }); -// if (!vacuumDocked) { -// const mode = this.device.operating_mode(); -// if (mode === OperatingModes.START || mode === OperatingModes.STOP) { -// await this.device.update(Properties.POWER_MODE) -// .catch(() => { -// this.log.debug('Promise Rejected with power mode update.'); -// }); -// const service = this.service; -// const platform = this.platform; -// await this.getFanSpeed() -// .then((power_mode) => { -// service.updateCharacteristic(platform.Characteristic.RotationSpeed, power_mode); -// }) -// .catch(() => { -// this.log.debug('Promise Rejected with getting power mode.'); -// }); -// } -// } + // if (!vacuumDocked) { + // const mode = this.device.operating_mode(); + // if (mode === OperatingModes.START || mode === OperatingModes.STOP) { + // await this.device.update(Properties.POWER_MODE) + // .catch(() => { + // this.log.debug('Promise Rejected with power mode update.'); + // }); + // const service = this.service; + // const platform = this.platform; + // await this.getFanSpeed() + // .then((power_mode) => { + // service.updateCharacteristic(platform.Characteristic.RotationSpeed, power_mode); + // }) + // .catch(() => { + // this.log.debug('Promise Rejected with getting power mode.'); + // }); + // } + // } } // // Monitor vacuum state function From 54060eb90c10364a75f6aee1c59b378a73815295 Mon Sep 17 00:00:00 2001 From: Bubba8291 <37317442+Bubba8291@users.noreply.github.com> Date: Wed, 11 Sep 2024 18:47:07 -0700 Subject: [PATCH 07/23] Re added vacuum state monitoring --- src/platformAccessory.ts | 161 +++++++++++++++++---------------------- 1 file changed, 72 insertions(+), 89 deletions(-) diff --git a/src/platformAccessory.ts b/src/platformAccessory.ts index c524993..16f2b7c 100644 --- a/src/platformAccessory.ts +++ b/src/platformAccessory.ts @@ -62,26 +62,27 @@ export class SharkIQAccessory { this.updateStates(); - // // Monitor vacuum state - // this.monitorVacuumState().then(() => { - // this.monitorVacuumStateInterval(); - // }) - // .catch(() => { - // this.log.debug('Promise Rejected with first interval update.'); - // this.monitorVacuumStateInterval(); - // }); + // Retrieve vacuum states + this.retrieveVacuumStates().then(() => { + this.retrieveVacuumStateInterval(); + }) + .catch(() => { + this.log.debug('Promise Rejected with first interval update.'); + this.retrieveVacuumStateInterval(); + }); } - // // Monitor vacuum state interval function - // async monitorVacuumStateInterval(): Promise { - // setInterval(async () => { - // await this.monitorVacuumState() - // .catch(() => { - // this.log.debug('Promise Rejected with interval update.'); - // }); - // }, this.dockedUpdateInterval + this.dockedDelay); - // } + // Retrieve vacuum states interval function + async retrieveVacuumStateInterval(): Promise { + setInterval(async () => { + await this.retrieveVacuumStates() + .catch(() => { + this.log.debug('Promise Rejected with interval update.'); + }); + }, this.dockedUpdateInterval + this.dockedDelay); + } + // Retrieve docked status async retrieveDockedStatus(): Promise { this.log.debug('Triggering GET Docked Status'); await this.device.update(Properties.DOCKED_STATUS); @@ -97,81 +98,64 @@ export class SharkIQAccessory { return vacuumDocked; } + // Retrieve operating mode async retrieveOperatingMode(): Promise { - await this.device.update(Properties.OPERATING_MODE); - // .then((delay) => { - // this.dockedDelay = delay; - // }); + this.log.debug('Triggering GET Operating Mode'); + await this.device.update(Properties.OPERATING_MODE) + .then((delay) => { + this.dockedDelay = delay; + }); } + // Retrieve power mode async retrievePowerMode(): Promise { - await this.device.update(Properties.POWER_MODE); - // .then((delay) => { - // this.dockedDelay = delay; - // }); - // if (!vacuumDocked) { - // const mode = this.device.operating_mode(); - // if (mode === OperatingModes.START || mode === OperatingModes.STOP) { - // await this.device.update(Properties.POWER_MODE) - // .catch(() => { - // this.log.debug('Promise Rejected with power mode update.'); - // }); - // const service = this.service; - // const platform = this.platform; - // await this.getFanSpeed() - // .then((power_mode) => { - // service.updateCharacteristic(platform.Characteristic.RotationSpeed, power_mode); - // }) - // .catch(() => { - // this.log.debug('Promise Rejected with getting power mode.'); - // }); - // } - // } + this.log.debug('Triggering GET Power Mode'); + await this.device.update(Properties.POWER_MODE) + .then((delay) => { + this.dockedDelay = delay; + }); } - // // Monitor vacuum state function - // async retrieveVacuumStates(): Promise { - // let vacuumDocked = false; - - // await this.device.update([Properties.DOCKED_STATUS, Properties.OPERATING_MODE, Properties.POWER_MODE]) - // .then((delay) => { - // this.dockedDelay = delay; - // }); - - // const docked_status = this.device.docked_status(); - // if(!this.invertDockedStatus) { - // vacuumDocked = docked_status === 1; - // } else { - // vacuumDocked = docked_status !== 1; - // } - // await this.updateItems(vacuumDocked) - // .catch(() => { - // this.log.debug('Promise Rejected with running docked update.'); - // }); - - - // this.dockedStatusService.updateCharacteristic(this.platform.Characteristic.ContactSensorState, vacuumDocked); - - // this.log.debug('Triggering Vacuum Docked:', vacuumDocked); - // } - - // // Update docked, active, and paused state - // async updateItems(vacuumDocked: boolean): Promise { - // if (!vacuumDocked) { - // const mode = this.device.operating_mode(); - // if (mode === OperatingModes.START || mode === OperatingModes.STOP) { - // const service = this.service; - // const platform = this.platform; - // await this.getFanSpeed() - // .then((power_mode) => { - // service.updateCharacteristic(platform.Characteristic.RotationSpeed, power_mode); - // }) - // .catch(() => { - // this.log.debug('Promise Rejected with getting power mode.'); - // }); - // } - // } - // } + // Monitor vacuum state function + async retrieveVacuumStates(): Promise { + this.log.debug('Triggering GET Vacuum States'); + let vacuumDocked = false; + + await this.device.update([Properties.DOCKED_STATUS, Properties.OPERATING_MODE, Properties.POWER_MODE]) + .then((delay) => { + this.dockedDelay = delay; + }); + + const docked_status = this.device.docked_status(); + if(!this.invertDockedStatus) { + vacuumDocked = docked_status === 1; + } else { + vacuumDocked = docked_status !== 1; + } + const power_mode = this.device.power_mode(); + const mode = this.device.operating_mode(); + const vacuumActive = mode === OperatingModes.START || mode === OperatingModes.STOP; + this.service.updateCharacteristic(this.platform.Characteristic.Active, vacuumActive); + if (vacuumActive) { + if (power_mode === PowerModes.MAX) { + this.service.updateCharacteristic(this.platform.Characteristic.RotationSpeed, 90); + } else if (power_mode === PowerModes.ECO) { + this.service.updateCharacteristic(this.platform.Characteristic.RotationSpeed, 30); + } else { + this.service.updateCharacteristic(this.platform.Characteristic.RotationSpeed, 60); + } + if (mode === OperatingModes.STOP) { + this.vacuumPausedService.updateCharacteristic(this.platform.Characteristic.On, true); + } else { + this.vacuumPausedService.updateCharacteristic(this.platform.Characteristic.On, false); + } + } else { + this.service.updateCharacteristic(this.platform.Characteristic.RotationSpeed, 0); + } + this.dockedStatusService.updateCharacteristic(this.platform.Characteristic.ContactSensorState, vacuumDocked); + + this.log.debug('Vacuum Docked:', vacuumDocked, 'Vacuum Active:', vacuumActive, 'Power Mode:', power_mode); + } // Update paused and active state on switch updateStates(): void { @@ -205,7 +189,7 @@ export class SharkIQAccessory { // Set paused state async setPaused(value: CharacteristicValue): Promise { - this.log.debug('Triggering SET Paused'); + this.log.debug('Triggering SET Paused. Paused:', value); const mode = this.device.operating_mode(); if (mode === OperatingModes.START || mode === OperatingModes.STOP) { @@ -278,8 +262,7 @@ export class SharkIQAccessory { // Set vacuum power from UI (and start/stop vacuum if needed) async setFanSpeed(value: CharacteristicValue): Promise { - this.log.debug('Triggering SET Fan Speed'); - this.log.debug('Value:', value); + this.log.debug('Triggering SET Fan Speed. Value:', value); let power_mode = PowerModes.NORMAL; if (value === 30) { From a7a567ee5d837851af287fa39bfd8615ba04fb70 Mon Sep 17 00:00:00 2001 From: Bubba8291 <37317442+Bubba8291@users.noreply.github.com> Date: Wed, 11 Sep 2024 18:48:21 -0700 Subject: [PATCH 08/23] Added option for manually inputting OAuth code. --- config.schema.json | 22 ++++++++++++++++++++-- src/login.ts | 46 +++++++++++++++++++++++++++++++++++----------- src/platform.ts | 16 +++++++++++----- 3 files changed, 66 insertions(+), 18 deletions(-) diff --git a/config.schema.json b/config.schema.json index 4016658..61a73ed 100644 --- a/config.schema.json +++ b/config.schema.json @@ -13,12 +13,17 @@ "email": { "title": "SharkClean Account Email", "type": "string", - "required": true + "required": false }, "password": { "title": "SharkClean Account Password", "type": "string", - "required": true + "required": false + }, + "oAuthCode": { + "title": "SharkClean OAuth Code", + "type": "string", + "required": false }, "vacuums": { "title": "SharkClean Vacuum Devicess", @@ -60,6 +65,19 @@ "password" ] }, + { + "type": "fieldset", + "title": "SharkIQ OAuth Info", + "expandable": true, + "expanded": false, + "items": [ + { + "type": "help", + "helpvalue": "
OAuth Code
Enter the OAuth code you received from SharkClean. This is used for manually obtaining credentials." + }, + "oAuthCode" + ] + }, { "type": "fieldset", "title": "Vacuum Settings", diff --git a/src/login.ts b/src/login.ts index 097ba8c..61f72df 100644 --- a/src/login.ts +++ b/src/login.ts @@ -10,8 +10,6 @@ import { addSeconds } from './utils'; import { OAuthData } from './type'; export class Login { - private oAuthCode = ''; - public log: Logger; public auth_file: string; public oauth_file: string; @@ -19,12 +17,14 @@ export class Login { public password: string; public app_id: string; public app_secret: string; + public oAuthCode: string; constructor(log: Logger, auth_file: string, oauth_file: string, email: string, password: string, + oAuthCode: string, app_id = global_vars.SHARK_APP_ID, app_secret = global_vars.SHARK_APP_SECRET, ) { @@ -33,6 +33,7 @@ export class Login { this.oauth_file = oauth_file; this.email = email; this.password = password; + this.oAuthCode = oAuthCode; this.app_id = app_id; this.app_secret = app_secret; } @@ -46,18 +47,41 @@ export class Login { const email = this.email; const password = this.password; - try { - const url = await generateURL(this.oauth_file); - - await this.login(email, password, url); + if (email === '' && password === '') { if (this.oAuthCode === '') { - return Promise.reject('Error: No OAuth code found'); + try { + const url = await generateURL(this.oauth_file); + return Promise.reject(`Please login to Shark using the following URL: ${url}`); + } catch (error) { + return Promise.reject(error); + } } else { - const ouath_data = await getOAuthData(this.oauth_file); - await this.loginCallback(this.oAuthCode, ouath_data); + try { + const ouath_data = await getOAuthData(this.oauth_file); + try { + await this.loginCallback(this.oAuthCode, ouath_data); + } catch (error) { + return Promise.reject(error); + } + } catch (error) { + this.log.warn('OAuth data not found with OAuth code set. Please clear the OAuth code and try again.'); + return Promise.reject(error); + } + } + } else { + try { + const url = await generateURL(this.oauth_file); + + await this.login(email, password, url); + if (this.oAuthCode === '') { + return Promise.reject('Error: No OAuth code found'); + } else { + const ouath_data = await getOAuthData(this.oauth_file); + await this.loginCallback(this.oAuthCode, ouath_data); + } + } catch (error) { + return Promise.reject(error); } - } catch (error) { - return Promise.reject(`${error}`); } } } diff --git a/src/platform.ts b/src/platform.ts index 9c86c76..db33809 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -58,12 +58,18 @@ export class SharkIQPlatform implements DynamicPlatformPlugin { const storagePath = this.api.user.storagePath(); const auth_file = join(storagePath, global_vars.FILE); const oauth_file = join(storagePath, global_vars.OAUTH.FILE); - const email = this.config.email; - const password = this.config.password; + const oAuthCode = this.config.oAuthCode || ''; + const email = this.config.email || ''; + const password = this.config.password || ''; if (!email || typeof email !== 'string' || !password || typeof password !== 'string') { - return Promise.reject('Error: Email and password must be present in the config'); + this.log.warn('Email and password not present in the config. Using OAuth code login method instead.'); + this.log.info('Please provide email and password in the config if you want to use email/password login method.'); + } else if (email !== '' && password === '') { + return Promise.reject('Password must be present in the config if email is provided.'); + } else if (email === '' && password !== '') { + return Promise.reject('Email must be present in the config if password is provided.'); } - const login = new Login(this.log, auth_file, oauth_file, email, password); + const login = new Login(this.log, auth_file, oauth_file, email, password, oAuthCode); try { await login.checkLogin(); const ayla_api = get_ayla_api(auth_file, this.log, europe); @@ -71,7 +77,7 @@ export class SharkIQPlatform implements DynamicPlatformPlugin { const devices = await ayla_api.get_devices(); return devices; } catch (error) { - return Promise.reject(`${error}`); + return Promise.reject(error); } }; From 42c738bcbed3af11b52004b3f52d8ff8c1d24342 Mon Sep 17 00:00:00 2001 From: Bubba8291 <37317442+Bubba8291@users.noreply.github.com> Date: Wed, 11 Sep 2024 18:49:31 -0700 Subject: [PATCH 09/23] Small issue where plugin would not refresh access token fixed --- src/sharkiq-js/ayla_api.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sharkiq-js/ayla_api.ts b/src/sharkiq-js/ayla_api.ts index 77a71fb..f99a2e3 100644 --- a/src/sharkiq-js/ayla_api.ts +++ b/src/sharkiq-js/ayla_api.ts @@ -242,7 +242,7 @@ class AylaApi { } // List device objects - async list_devices(attempt = 1): Promise { + async list_devices(attempt = 0): Promise { const url = `${this.europe ? global_vars.EU_DEVICE_URL : global_vars.DEVICE_URL}/apiv1/devices.json`; try { const auth_header = await this.auth_header(); @@ -250,7 +250,7 @@ class AylaApi { if (resp.status === 401) { this.log.error('API Error: Unauthorized'); const status = await this.attempt_refresh(attempt); - if (!status && attempt === 2) { + if (!status && attempt === 1) { return []; } else { return await this.list_devices(attempt + 1); From 110be289f03c89737330df761714f5ac8dfc16f0 Mon Sep 17 00:00:00 2001 From: Bubba8291 <37317442+Bubba8291@users.noreply.github.com> Date: Wed, 11 Sep 2024 18:49:55 -0700 Subject: [PATCH 10/23] Update README.md --- README.md | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 3ceb794..b92b877 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,6 @@ -### 2024-08-19: Shark uses a new login method. Starting in v1.2.0, homebridge-sharkiq has switched to the new method. See Step 2 to update the login for your instance. - A new homebridge plugin for SharkIQ Vacuums. Contributions are always welcome. I used the [sharkiq](https://github.com/JeffResc/sharkiq/) python module as a reference for creating the javascript wrapper to control SharkIQ Vacuums. @@ -32,36 +30,30 @@ Configure Homebridge. The config file for SharkIQ should include: { "name": "SharkIQ", "platform": "SharkIQ", - "oAuthCode": "[OAuth Code for Shark Login]", + "email": "[Shark Clean Account Email]", + "password": "[Shark Clean Account Password]", + "oAuthCode": "[Optional. Use for manually obtaining credentials]", "vacuums": [ "[Shark Vacuum DSN]", "..." ], "europe": false, "invertDockedStatus": false, - "dockedUpdateInterval": 5000 + "dockedUpdateInterval": 30000 } ] } ``` -The OAuth Code value is for creating and storing the login for the plugin. Here is how to sign in. -1. Run Homebridge with the updated plugin version. -2. Open the Homebridge logs -3. Open the URL in the console printed by homebridge-sharkiq. Safari will not work due to the way Safari handles the results of the login -4. Before you login, open up developer tools in your browser (inspect element), and navigate to the network tab -5. Enter your login info, and press continue -6. Open the request with the uri of `/authorize/resume` that shows up and view the headers -7. Search `com.sharkninja.shark` in the headers -8. Copy the code in between `code=` and `&`. for example in `com.sharkninja.shark://login.sharkninja.com/ios/com.sharkninja.shark/callback?code=abcdefghijkl&state=`, `abcdefghijkl` is the code that needs to be copied -9. Open your Homebridge configuration, and paste the `code` value in the OAuth Code config option -10. Restart Homebridge +The email and password is your Shark Clean account you used to setup the vacuum. The Vacuums array is a list of your vacuum's device serial numbers (DSN). If you only have one vacuum, just include the one's DSN. The DSN(s) can be found in the SharkClean mobile app. + +If you would like to manually obtain your Shark Clean credentials without using your email and password, you can obtain a OAuth code instead. Refer to the `OAuth Code Login Method` section. The Vacuums array is a list of your vacuum's device serial numbers (DSN). If you only have one vacuum, just include the one's DSN. The DSN(s) can be found in the SharkClean mobile app. If you are in Europe, set the `europe` config value to `true`. SharkClean has separate servers for the U.S. and Europe. The default value is `false`, which connects to the U.S. server. -The default interval between updating the docked status is 5 seconds (5000 ms). To change the docked status interval, add `dockedUpdateInterval` to your config. Value is in milliseconds. +The default interval between updating the docked status is 30 seconds (30000 ms). To change the docked status interval, add `dockedUpdateInterval` to your config. Value is in milliseconds. If the interval is too low, you have the risk of your account being rate limited. ## Features @@ -72,6 +64,19 @@ The default interval between updating the docked status is 5 seconds (5000 ms). - Set `invertDockedStatus` to `true` to display as "closed" when the vacuum is docked and "opened" when the vacuum is not docked - Pause switch for pausing the vacuum while it's running +### OAuth Code Login Method +The OAuth Code value is for creating and storing the login for the plugin. Here is how to sign in with this method. +1. Run Homebridge with the latest plugin version. +2. Open the Homebridge logs +3. Open the URL in the console printed by homebridge-sharkiq. Safari will not work due to the way Safari handles the results of the login +4. Before you login, open up developer tools in your browser (inspect element), and navigate to the network tab +5. Enter your login info, and press continue +6. Open the request with the uri of `/authorize/resume` that shows up and view the headers +7. Search `com.sharkninja.shark` in the headers +8. Copy the code in between `code=` and `&`. for example in `com.sharkninja.shark://login.sharkninja.com/ios/com.sharkninja.shark/callback?code=abcdefghijkl&state=`, `abcdefghijkl` is the code that needs to be copied +9. Open your Homebridge configuration, and paste the `code` value in the OAuth Code config option +10. Restart Homebridge + ## Notes Contributions would be very helpful to help this Homebridge plugin stay maintained and up to date. If you have any problems, please create an issue. From e56e448785090aceabff0c3ef2bf29d57e0966d4 Mon Sep 17 00:00:00 2001 From: Bubba8291 <37317442+Bubba8291@users.noreply.github.com> Date: Wed, 11 Sep 2024 18:50:10 -0700 Subject: [PATCH 11/23] Updated workflows --- .github/npm-version-script.cjs | 81 +++++++++++++++++++++ .github/workflows/beta-release.yml | 40 ++++++++++ .github/workflows/build-and-test.yml | 42 +++++++++++ .github/workflows/build.yml | 38 ---------- .github/workflows/discord-webhooks.yml | 63 ++++++++++++++++ .github/workflows/eslint.yml | 31 ++++++++ .github/workflows/nodejs-build-and-test.yml | 42 +++++++++++ .github/workflows/npm-publish.yml | 71 ++++++++++++++++++ 8 files changed, 370 insertions(+), 38 deletions(-) create mode 100644 .github/npm-version-script.cjs create mode 100644 .github/workflows/beta-release.yml create mode 100644 .github/workflows/build-and-test.yml delete mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/discord-webhooks.yml create mode 100644 .github/workflows/eslint.yml create mode 100644 .github/workflows/nodejs-build-and-test.yml create mode 100644 .github/workflows/npm-publish.yml diff --git a/.github/npm-version-script.cjs b/.github/npm-version-script.cjs new file mode 100644 index 0000000..b61ad07 --- /dev/null +++ b/.github/npm-version-script.cjs @@ -0,0 +1,81 @@ +#!/bin/env node + +/** + * This scripts queries the npm registry to pull out the latest version for a given tag. + */ + +const fs = require("fs"); +const semver = require("semver"); +const child_process = require("child_process"); +const assert = require("assert"); + +const BRANCH_VERSION_PATTERN = /^([A-Za-z]*)-(\d+.\d+.\d+)$/ + +// Load the contents of the package.json file +const packageJSON = JSON.parse(fs.readFileSync("package.json", "utf8")); + +let refArgument = process.argv[2]; +let tagArgument = process.argv[3] || "latest"; + +if (refArgument == null) { + console.error("ref argument is missing"); + console.error("Usage: npm-version-script.cjs [tag]"); + process.exit(1); +} + +/** + * Queries the NPM registry for the latest version for the provided tag. + * @param tag The tag to query for. + * @returns {string} Returns the version. + */ +function getTagVersionFromNpm(tag) { + try { + return child_process.execSync(`npm info ${packageJSON.name} version --tag="${tag}"`).toString("utf8").trim(); + } catch (e) { + throw e; + } +} + +function desiredTargetVersion(ref) { + // ref is a GitHub action ref string + if (ref.startsWith("refs/pull/")) { + throw Error("The version script was executed inside a PR!"); + } + + assert(ref.startsWith("refs/heads/")) + let branchName = ref.slice("refs/heads/".length); + + let results = branchName.match(BRANCH_VERSION_PATTERN); + if (results != null) { + if (results[1] !== tagArgument) { + console.warn(`The base branch name (${results[1]}) differs from the tag name ${tagArgument}`); + } + + return results[2]; + } + + // legacy mode were we use the `betaVersion` property in the package.json + if (branchName === "beta" && packageJSON.betaVersion) { + return packageJSON.betaVersion + } + + throw new Error("Malformed branch name for ref: " + ref + ". Can't derive the base version. Use a branch name like: beta-x.x.x!"); +} + +// derive the base version from the branch ref +const baseVersion = desiredTargetVersion(refArgument); + +// query the npm registry for the latest version of the provided tag name +const latestReleasedVersion = getTagVersionFromNpm(tagArgument); // e.g. 0.7.0-beta.12 +const latestReleaseBase = semver.inc(latestReleasedVersion, "patch"); // will produce 0.7.0 (removing the preid, needed for the equality check below) + +let publishTag; +if (semver.eq(baseVersion, latestReleaseBase)) { // check if we are releasing another version for the latest beta + publishTag = latestReleasedVersion; // set the current latest beta to be incremented +} else { + publishTag = baseVersion; // start of with a new beta version +} + +// save the package.json +packageJSON.version = publishTag; +fs.writeFileSync("package.json", JSON.stringify(packageJSON, null, 2)); \ No newline at end of file diff --git a/.github/workflows/beta-release.yml b/.github/workflows/beta-release.yml new file mode 100644 index 0000000..54ed8ba --- /dev/null +++ b/.github/workflows/beta-release.yml @@ -0,0 +1,40 @@ +name: Node-CI Beta + +on: + release: + types: [prereleased] + workflow_dispatch: + +jobs: + build_and_test: + uses: ./.github/workflows/nodejs-build-and-test.yml + with: + enable_coverage: true + secrets: + token: ${{ secrets.GITHUB_TOKEN }} + lint: + needs: build_and_test + uses: ./.github/workflows/eslint.yml + + publish: + needs: lint + + if: ${{ github.repository == 'Bubba8291/homebridge-sharkiq' }} + + uses: ./.github/workflows/npm-publish.yml + with: + tag: 'beta' + dynamically_adjust_version: true + npm_version_command: 'pre' + pre_id: 'beta' + secrets: + npm_auth_token: ${{ secrets.NPM_TOKEN }} + + github-releases-to-discord: + needs: publish + + if: ${{ github.repository == 'Bubba8291/homebridge-sharkiq' && github.event.release.prerelease == true }} + + uses: ./.github/workflows/discord-webhooks.yml + with: + footer_title: "SharkIQ" \ No newline at end of file diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 0000000..7b6b724 --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,42 @@ +name: Build and Test - NodeJS + +on: + workflow_call: + inputs: + enable_coverage: + description: 'A boolean value indicating if coverage reporting shall be enabled. If provided the secret token is required.' + default: false + required: false + type: boolean + secrets: + token: + description: 'The GitHub Token which is used to push to Coveralls' + required: false + +jobs: + build: + + strategy: + fail-fast: false + matrix: + node_version: [18] + os: [ubuntu-latest] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node_version }} + registry-url: 'https://registry.npmjs.org' + cache: 'npm' + + - name: install + run: npm ci + env: + CI: true + - name: build + run: npm run build --if-present + env: + CI: true \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index e567427..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Build and Lint - -on: [push, pull_request] - -jobs: - build: - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - # the Node.js versions to build on - node-version: [16.x, 18.x, 20.x] - - steps: - - uses: actions/checkout@v3 - - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - - - name: Install dependencies - run: npm install - - - name: Lint the project - run: npm run lint - - - name: Build the project - run: npm run build - - - name: List, audit, fix outdated dependencies and build again - run: | - npm list --outdated - npm audit || true # ignore failures - npm audit fix || true - npm list --outdated - npm run build diff --git a/.github/workflows/discord-webhooks.yml b/.github/workflows/discord-webhooks.yml new file mode 100644 index 0000000..9c0038d --- /dev/null +++ b/.github/workflows/discord-webhooks.yml @@ -0,0 +1,63 @@ +name: Discord Webhooks + +# Controls when the workflow will run +on: + workflow_call: + inputs: + color: + required: false + type: string + username: + required: false + type: string + avatar_url: + required: false + type: string + footer_title: + required: true + type: string + pre_release_footer_title: + required: false + type: string + footer_icon_url: + required: false + type: string + footer_timestamp: + required: false + type: string + secrets: + DISCORD_WEBHOOK_URL_LATEST: + required: false + DISCORD_WEBHOOK_URL_BETA: + required: false + +jobs: + github-releases-to-discord: + runs-on: ubuntu-latest + name: GitHub Releases to Discord + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Latest Release + if: ${{ github.event.release.prerelease == false }} + uses: SethCohen/github-releases-to-discord@v1.13.1 + with: + webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL_LATEST }} + color: ${{ inputs.color || '490899'}} + username: ${{ inputs.username || 'Bubba8291'}} + avatar_url: ${{ inputs.avatar_url || 'https://raw.githubusercontent.com/homebridge/branding/latest/logos/homebridge-color-round-stylized.png'}} + footer_title: ${{ inputs.footer_title }} + footer_icon_url: ${{ inputs.footer_icon_url || 'https://raw.githubusercontent.com/homebridge/branding/latest/logos/homebridge-color-round-stylized.png'}} + footer_timestamp: ${{ inputs.footer_timestamp || true }} + + - name: Pre-Release + if: ${{ github.event.release.prerelease == true }} + uses: SethCohen/github-releases-to-discord@v1.13.1 + with: + webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL_BETA }} + color: ${{ inputs.color || '490899'}} + username: ${{ inputs.username || 'Bubba8291'}} + avatar_url: ${{ inputs.avatar_url || 'https://raw.githubusercontent.com/homebridge/branding/latest/logos/homebridge-color-round-stylized.png'}} + footer_title: "Pre-Release: ${{ inputs.pre_release_footer_title || inputs.footer_title }}" + footer_icon_url: ${{ inputs.footer_icon_url || 'https://raw.githubusercontent.com/homebridge/branding/latest/logos/homebridge-color-round-stylized.png'}} + footer_timestamp: ${{ inputs.footer_timestamp || true }} \ No newline at end of file diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml new file mode 100644 index 0000000..1157079 --- /dev/null +++ b/.github/workflows/eslint.yml @@ -0,0 +1,31 @@ +name: ESLint + +on: + workflow_call: + inputs: + node_version: + description: 'Defines the node version setup to run the linter' + default: 18 + required: false + type: number + +jobs: + eslint: + name: ESLint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node_version }} + registry-url: 'https://registry.npmjs.org' + cache: 'npm' + + - name: install + run: npm ci + + - name: eslint + run: npm run lint --if-present + env: + CI: true \ No newline at end of file diff --git a/.github/workflows/nodejs-build-and-test.yml b/.github/workflows/nodejs-build-and-test.yml new file mode 100644 index 0000000..7b6b724 --- /dev/null +++ b/.github/workflows/nodejs-build-and-test.yml @@ -0,0 +1,42 @@ +name: Build and Test - NodeJS + +on: + workflow_call: + inputs: + enable_coverage: + description: 'A boolean value indicating if coverage reporting shall be enabled. If provided the secret token is required.' + default: false + required: false + type: boolean + secrets: + token: + description: 'The GitHub Token which is used to push to Coveralls' + required: false + +jobs: + build: + + strategy: + fail-fast: false + matrix: + node_version: [18] + os: [ubuntu-latest] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node_version }} + registry-url: 'https://registry.npmjs.org' + cache: 'npm' + + - name: install + run: npm ci + env: + CI: true + - name: build + run: npm run build --if-present + env: + CI: true \ No newline at end of file diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml new file mode 100644 index 0000000..639da7d --- /dev/null +++ b/.github/workflows/npm-publish.yml @@ -0,0 +1,71 @@ +name: NPM Publish + +on: + workflow_call: + inputs: + node_version: + description: 'Defines the node version setup to run the publish job.' + default: 18 + required: false + type: number + tag: + description: 'Defines the tag name used to tag the published version on npm.' + required: false + type: string + npm_version_command: + description: 'If supplied, the `npm version` command is run before publishing with the provided string as the third argument. E.g. `patch` to run npm run patch before publishing.' + required: false + type: string + pre_id: + description: 'If `npm_version_command` is supplied, this input can be supplied to pass a value to the `--preid` argument of the `npm version` command.' + default: '' + required: false + type: string + dynamically_adjust_version: + description: 'If enabled, the job executes the `npm-version-script.cjs` script. This is used to automatically query the latest version from the npm registry.' + default: false + required: false + type: boolean + secrets: + npm_auth_token: + description: 'The npm access token used to publish the package' + required: true + +jobs: + publish_npm: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node_version }} + registry-url: 'https://registry.npmjs.org' + cache: 'npm' + + - name: install + run: npm ci + + - name: Adjust version + if: ${{ inputs.dynamically_adjust_version }} + run: node .github/npm-version-script.cjs ${{ github.ref }} ${{ inputs.tag }} + + - name: npm version (with git commit) + if: ${{ inputs.npm_version_command != null && inputs.dynamically_adjust_version == false }} + run: npm version ${{ inputs.npm_version_command }} --preid=${{ inputs.pre_id }} + + - name: npm version (without git commit) + if: ${{ inputs.npm_version_command != null && inputs.dynamically_adjust_version }} + run: npm version ${{ inputs.npm_version_command }} --preid=${{ inputs.pre_id }} --no-git-tag-version + + - name: publish (without tag) + if: ${{ inputs.tag == null }} + run: npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.npm_auth_token }} + + - name: publish (with tag) + if: ${{ inputs.tag != null }} + run: npm publish --access public --tag=${{ inputs.tag }} + env: + NODE_AUTH_TOKEN: ${{ secrets.npm_auth_token }} \ No newline at end of file From 0e0145bf15a2a9307aaa3f8f320539048dc49499 Mon Sep 17 00:00:00 2001 From: Bubba8291 <37317442+Bubba8291@users.noreply.github.com> Date: Wed, 11 Sep 2024 18:50:29 -0700 Subject: [PATCH 12/23] 1.3.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 374138e..7597a57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "homebridge-sharkiq", - "version": "1.2.3", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "homebridge-sharkiq", - "version": "1.2.3", + "version": "1.3.0", "license": "Apache-2.0", "dependencies": { "node-fetch": "^2.6.1", diff --git a/package.json b/package.json index d43a982..fc0dd2d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "displayName": "SharkIQ Plugin", "name": "homebridge-sharkiq", - "version": "1.2.3", + "version": "1.3.0", "description": "A Homebridge plugin to connect your Shark Vacuum to homebridge.", "license": "Apache-2.0", "repository": { From aead6788618bc57b38a2f9fd7d56ef2bd0cd59a6 Mon Sep 17 00:00:00 2001 From: Bubba8291 <37317442+Bubba8291@users.noreply.github.com> Date: Wed, 11 Sep 2024 18:57:48 -0700 Subject: [PATCH 13/23] Create build.yml --- .github/workflows/build.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..2a9564e --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,18 @@ +name: Node Build + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +jobs: + build_and_test: + uses: ./.github/workflows/nodejs-build-and-test.yml + with: + enable_coverage: true + secrets: + token: ${{ secrets.GITHUB_TOKEN }} + lint: + needs: build_and_test + uses: ./.github/workflows/eslint.yml@latest \ No newline at end of file From 178196298038e4483e4d53eca21ca1f692787d34 Mon Sep 17 00:00:00 2001 From: Bubba8291 <37317442+Bubba8291@users.noreply.github.com> Date: Thu, 12 Sep 2024 09:14:54 -0700 Subject: [PATCH 14/23] Updated workflows --- .github/workflows/beta-release.yml | 4 +++- .github/workflows/build.yml | 2 +- .github/workflows/discord-webhooks.yml | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/beta-release.yml b/.github/workflows/beta-release.yml index 54ed8ba..3225b2f 100644 --- a/.github/workflows/beta-release.yml +++ b/.github/workflows/beta-release.yml @@ -1,6 +1,8 @@ name: Node-CI Beta on: + push: + branches: [beta-*.*.*] release: types: [prereleased] workflow_dispatch: @@ -24,7 +26,7 @@ jobs: uses: ./.github/workflows/npm-publish.yml with: tag: 'beta' - dynamically_adjust_version: true + dynamically_adjust_version: ${{ github.event.release.prerelease == false }} npm_version_command: 'pre' pre_id: 'beta' secrets: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2a9564e..ca08f05 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,4 +15,4 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} lint: needs: build_and_test - uses: ./.github/workflows/eslint.yml@latest \ No newline at end of file + uses: ./.github/workflows/eslint.yml \ No newline at end of file diff --git a/.github/workflows/discord-webhooks.yml b/.github/workflows/discord-webhooks.yml index 9c0038d..cbe32d1 100644 --- a/.github/workflows/discord-webhooks.yml +++ b/.github/workflows/discord-webhooks.yml @@ -49,7 +49,7 @@ jobs: footer_title: ${{ inputs.footer_title }} footer_icon_url: ${{ inputs.footer_icon_url || 'https://raw.githubusercontent.com/homebridge/branding/latest/logos/homebridge-color-round-stylized.png'}} footer_timestamp: ${{ inputs.footer_timestamp || true }} - + - name: Pre-Release if: ${{ github.event.release.prerelease == true }} uses: SethCohen/github-releases-to-discord@v1.13.1 From 9f5d3555e5348e4cc9d2bc5d23d0b1e2299804e0 Mon Sep 17 00:00:00 2001 From: Bubba8291 <37317442+Bubba8291@users.noreply.github.com> Date: Fri, 13 Sep 2024 06:55:10 -0700 Subject: [PATCH 15/23] Update beta-release.yml --- .github/workflows/beta-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/beta-release.yml b/.github/workflows/beta-release.yml index 3225b2f..9a7e42d 100644 --- a/.github/workflows/beta-release.yml +++ b/.github/workflows/beta-release.yml @@ -21,12 +21,12 @@ jobs: publish: needs: lint - if: ${{ github.repository == 'Bubba8291/homebridge-sharkiq' }} + if: ${{ github.repository == 'Bubba8291/homebridge-sharkiq' && github.event.release.prerelease == false }} uses: ./.github/workflows/npm-publish.yml with: tag: 'beta' - dynamically_adjust_version: ${{ github.event.release.prerelease == false }} + dynamically_adjust_version: true npm_version_command: 'pre' pre_id: 'beta' secrets: From 690da8e0384813d7614eae705c7312726e7a7444 Mon Sep 17 00:00:00 2001 From: Bubba8291 <37317442+Bubba8291@users.noreply.github.com> Date: Fri, 13 Sep 2024 07:00:18 -0700 Subject: [PATCH 16/23] Update beta-release.yml --- .github/workflows/beta-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/beta-release.yml b/.github/workflows/beta-release.yml index 9a7e42d..31f8e1c 100644 --- a/.github/workflows/beta-release.yml +++ b/.github/workflows/beta-release.yml @@ -33,7 +33,7 @@ jobs: npm_auth_token: ${{ secrets.NPM_TOKEN }} github-releases-to-discord: - needs: publish + needs: lint if: ${{ github.repository == 'Bubba8291/homebridge-sharkiq' && github.event.release.prerelease == true }} From 048e52499489226dc2bf2836a9f97d90481e3b6b Mon Sep 17 00:00:00 2001 From: Bubba8291 <37317442+Bubba8291@users.noreply.github.com> Date: Fri, 13 Sep 2024 07:09:37 -0700 Subject: [PATCH 17/23] Update beta-release.yml --- .github/workflows/beta-release.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/beta-release.yml b/.github/workflows/beta-release.yml index 31f8e1c..2950602 100644 --- a/.github/workflows/beta-release.yml +++ b/.github/workflows/beta-release.yml @@ -39,4 +39,6 @@ jobs: uses: ./.github/workflows/discord-webhooks.yml with: - footer_title: "SharkIQ" \ No newline at end of file + footer_title: "SharkIQ" + secrets: + DISCORD_WEBHOOK_URL_BETA: ${{ secrets.DISCORD_WEBHOOK_URL_BETA }} \ No newline at end of file From 1d469f25fc1e3d508fb97bea356a65f43905ab6d Mon Sep 17 00:00:00 2001 From: Bubba8291 <37317442+Bubba8291@users.noreply.github.com> Date: Fri, 13 Sep 2024 07:16:11 -0700 Subject: [PATCH 18/23] Made beta publush and prerelease workflows separate --- .github/workflows/beta-release-discord.yml | 16 ++++++++++++++++ .github/workflows/beta-release.yml | 15 +-------------- 2 files changed, 17 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/beta-release-discord.yml diff --git a/.github/workflows/beta-release-discord.yml b/.github/workflows/beta-release-discord.yml new file mode 100644 index 0000000..80c2366 --- /dev/null +++ b/.github/workflows/beta-release-discord.yml @@ -0,0 +1,16 @@ +name: Beta Release Webhook + +on: + release: + types: [prereleased] + workflow_dispatch: + +jobs: + github-releases-to-discord: + if: ${{ github.repository == 'Bubba8291/homebridge-sharkiq' }} + + uses: ./.github/workflows/discord-webhooks.yml + with: + footer_title: "SharkIQ" + secrets: + DISCORD_WEBHOOK_URL_BETA: ${{ secrets.DISCORD_WEBHOOK_URL_BETA }} \ No newline at end of file diff --git a/.github/workflows/beta-release.yml b/.github/workflows/beta-release.yml index 2950602..e9aa91f 100644 --- a/.github/workflows/beta-release.yml +++ b/.github/workflows/beta-release.yml @@ -3,8 +3,6 @@ name: Node-CI Beta on: push: branches: [beta-*.*.*] - release: - types: [prereleased] workflow_dispatch: jobs: @@ -30,15 +28,4 @@ jobs: npm_version_command: 'pre' pre_id: 'beta' secrets: - npm_auth_token: ${{ secrets.NPM_TOKEN }} - - github-releases-to-discord: - needs: lint - - if: ${{ github.repository == 'Bubba8291/homebridge-sharkiq' && github.event.release.prerelease == true }} - - uses: ./.github/workflows/discord-webhooks.yml - with: - footer_title: "SharkIQ" - secrets: - DISCORD_WEBHOOK_URL_BETA: ${{ secrets.DISCORD_WEBHOOK_URL_BETA }} \ No newline at end of file + npm_auth_token: ${{ secrets.NPM_TOKEN }} \ No newline at end of file From 109ebd3f9cf51d55f733eb2cfd49231675432940 Mon Sep 17 00:00:00 2001 From: Bubba8291 <37317442+Bubba8291@users.noreply.github.com> Date: Sun, 29 Sep 2024 10:42:26 -0700 Subject: [PATCH 19/23] Update package-lock.json --- package-lock.json | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7597a57..93f17a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -910,9 +910,9 @@ "optional": true }, "node_modules/bare-fs": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.3.tgz", - "integrity": "sha512-7RYKL+vZVCyAsMLi5SPu7QGauGGT8avnP/HO571ndEuV4MYdGXvLhtW67FuLPeEI8EiIY7zbbRR9x7x7HU0kgw==", + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.5.tgz", + "integrity": "sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -922,9 +922,9 @@ } }, "node_modules/bare-os": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.2.tgz", - "integrity": "sha512-HZoJwzC+rZ9lqEemTMiO0luOePoGYNBgsLLgegKR/cljiJvcDNhDZQkzC+NC5Oh0aHbdBNSOHpghwMuB5tqhjg==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.4.tgz", + "integrity": "sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==", "license": "Apache-2.0", "optional": true }, @@ -939,14 +939,14 @@ } }, "node_modules/bare-stream": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.2.1.tgz", - "integrity": "sha512-YTB47kHwBW9zSG8LD77MIBAAQXjU2WjAkMHeeb7hUplVs6+IoM5I7uEVQNPMB7lj9r8I76UMdoMkGnCodHOLqg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.3.0.tgz", + "integrity": "sha512-pVRWciewGUeCyKEuRxwv06M079r+fRjAQjBEK2P6OYGrO43O+Z0LrPZZEjlc4mB6C2RpZ9AxJ1s7NLEtOHO6eA==", "license": "Apache-2.0", "optional": true, "dependencies": { "b4a": "^1.6.6", - "streamx": "^2.18.0" + "streamx": "^2.20.0" } }, "node_modules/base64-js": { @@ -3553,9 +3553,9 @@ "dev": true }, "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", @@ -4153,9 +4153,9 @@ } }, "node_modules/streamx": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.20.0.tgz", - "integrity": "sha512-ZGd1LhDeGFucr1CUCTBOS58ZhEendd0ttpGT3usTvosS4ntIwKN9LJFp+OeCSprsCPL14BXVRZlHGRY1V9PVzQ==", + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.20.1.tgz", + "integrity": "sha512-uTa0mU6WUC65iUvzKH4X9hEdvSW7rbPxPtwfWiLMSj3qTdQbAiUboZTxauKfpFuGIGa1C2BYijZ7wgdUXICJhA==", "license": "MIT", "dependencies": { "fast-fifo": "^1.3.2", @@ -4241,9 +4241,9 @@ } }, "node_modules/text-decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.1.tgz", - "integrity": "sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.0.tgz", + "integrity": "sha512-n1yg1mOj9DNpk3NeZOx7T6jchTbyJS3i3cucbNN6FcdPriMZx7NsgrGpWWdWZZGxD7ES1XB+3uoqHMgOKaN+fg==", "license": "Apache-2.0", "dependencies": { "b4a": "^1.6.4" From 3e0cd95a2a26cc1068d3be5a9ed7bbd8d381a30a Mon Sep 17 00:00:00 2001 From: Bubba8291 <37317442+Bubba8291@users.noreply.github.com> Date: Sun, 29 Sep 2024 10:43:12 -0700 Subject: [PATCH 20/23] Require linux arm64 users to use OAuth login --- src/login.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/login.ts b/src/login.ts index 61f72df..872310a 100644 --- a/src/login.ts +++ b/src/login.ts @@ -47,6 +47,8 @@ export class Login { const email = this.email; const password = this.password; + const architecure = process.arch; + const platform = process.platform; if (email === '' && password === '') { if (this.oAuthCode === '') { try { @@ -69,6 +71,11 @@ export class Login { } } } else { + if (platform === 'linux' && architecure === 'arm64') { + this.log.warn(`${platform} ${architecure} architecture does not support automatic login. Please use OAuth code login method.`); + const url = await generateURL(this.oauth_file); + return Promise.reject(`Please login to Shark using the following URL: ${url}`); + } try { const url = await generateURL(this.oauth_file); From 8d2a260c3e6a99d93fff1ca0f4f6823f988125be Mon Sep 17 00:00:00 2001 From: Bubba8291 <37317442+Bubba8291@users.noreply.github.com> Date: Sun, 29 Sep 2024 11:05:16 -0700 Subject: [PATCH 21/23] Support Homebridge v2 beta --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 93f17a4..1882b90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "typescript": "^4.9.5" }, "engines": { - "homebridge": "^1.6.1", + "homebridge": "^1.6.1 || ^2.0.0-beta.0", "node": "^18.18.1 || ^20.8.0" }, "funding": { diff --git a/package.json b/package.json index fc0dd2d..d7eb311 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "url": "https://github.com/Bubba8291/homebridge-sharkiq/issues" }, "engines": { - "homebridge": "^1.6.1", + "homebridge": "^1.6.1 || ^2.0.0-beta.0", "node": "^18.18.1 || ^20.8.0" }, "main": "dist/index.js", From 2b13f49133a98f171fe473745094a131c691df4b Mon Sep 17 00:00:00 2001 From: Bubba8291 <37317442+Bubba8291@users.noreply.github.com> Date: Sun, 29 Sep 2024 11:15:57 -0700 Subject: [PATCH 22/23] Create release.yml --- .github/workflows/release.yml | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..9ae4e94 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,36 @@ +name: Node Release + +on: + push: + tags: + - 'v*.*.*' + workflow_dispatch: + +jobs: + build_and_test: + uses: ./.github/workflows/nodejs-build-and-test.yml + with: + enable_coverage: true + secrets: + token: ${{ secrets.GITHUB_TOKEN }} + + publish: + needs: build_and_test + + if: ${{ github.repository == 'Bubba8291/homebridge-sharkiq' }} + + uses: ./.github/workflows/npm-publish.yml + secrets: + npm_auth_token: ${{ secrets.npm_token }} + + github-releases-to-discord: + needs: publish + + if: ${{ github.repository == 'Bubba8291/homebridge-sharkiq' }} + + uses: ./.github/workflows/discord-webhooks.yml + with: + footer_title: "SharkIQ" + secrets: + DISCORD_WEBHOOK_URL_LATEST: ${{ secrets.DISCORD_WEBHOOK_URL_LATEST }} + DISCORD_WEBHOOK_URL_BETA: ${{ secrets.DISCORD_WEBHOOK_URL_BETA }} \ No newline at end of file From bab58d336d050e92ce13c8d51102bf7473518876 Mon Sep 17 00:00:00 2001 From: Bubba8291 <37317442+Bubba8291@users.noreply.github.com> Date: Sun, 29 Sep 2024 11:24:53 -0700 Subject: [PATCH 23/23] 1.3.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1882b90..c593de2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "homebridge-sharkiq", - "version": "1.3.0", + "version": "1.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "homebridge-sharkiq", - "version": "1.3.0", + "version": "1.3.1", "license": "Apache-2.0", "dependencies": { "node-fetch": "^2.6.1", diff --git a/package.json b/package.json index d7eb311..40f11b1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "displayName": "SharkIQ Plugin", "name": "homebridge-sharkiq", - "version": "1.3.0", + "version": "1.3.1", "description": "A Homebridge plugin to connect your Shark Vacuum to homebridge.", "license": "Apache-2.0", "repository": {