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-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 new file mode 100644 index 0000000..e9aa91f --- /dev/null +++ b/.github/workflows/beta-release.yml @@ -0,0 +1,31 @@ +name: Node-CI Beta + +on: + push: + branches: [beta-*.*.*] + 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' && github.event.release.prerelease == false }} + + 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 }} \ 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 index e567427..ca08f05 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,38 +1,18 @@ -name: Build and Lint +name: Node Build -on: [push, pull_request] +on: + push: + branches: [main] + pull_request: + workflow_dispatch: 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 + 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 \ No newline at end of file diff --git a/.github/workflows/discord-webhooks.yml b/.github/workflows/discord-webhooks.yml new file mode 100644 index 0000000..cbe32d1 --- /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 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 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. diff --git a/config.schema.json b/config.schema.json index 73206a2..61a73ed 100644 --- a/config.schema.json +++ b/config.schema.json @@ -10,6 +10,16 @@ "type": "string", "default": "SharkIQ" }, + "email": { + "title": "SharkClean Account Email", + "type": "string", + "required": false + }, + "password": { + "title": "SharkClean Account Password", + "type": "string", + "required": false + }, "oAuthCode": { "title": "SharkClean OAuth Code", "type": "string", @@ -40,17 +50,31 @@ "title": "Docked Update Interval", "type": "integer", "required": false, - "default": 5000 + "default": 30000 } } }, "layout": [ { "type": "fieldset", - "title": "SharkIQ Auth Info", + "title": "SharkIQ Account Info", "expandable": true, "expanded": true, "items": [ + "email", + "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" ] }, diff --git a/package-lock.json b/package-lock.json index 5601787..c593de2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,18 @@ { "name": "homebridge-sharkiq", - "version": "1.2.3", + "version": "1.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "homebridge-sharkiq", - "version": "1.2.3", + "version": "1.3.1", "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", @@ -26,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": { @@ -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.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": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "node_modules/bare-os": { + "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 + }, + "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.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.20.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,10 +1522,19 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "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", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "engines": { "node": ">=10" @@ -1090,6 +1543,36 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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", @@ -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,7 +3407,25 @@ "node": ">=6" } }, - "node_modules/path-exists": { + "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==", @@ -2592,7 +3438,6 @@ "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.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", + "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.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.20.1.tgz", + "integrity": "sha512-uTa0mU6WUC65iUvzKH4X9hEdvSW7rbPxPtwfWiLMSj3qTdQbAiUboZTxauKfpFuGIGa1C2BYijZ7wgdUXICJhA==", + "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.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" + } + }, "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 322dd89..40f11b1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "displayName": "SharkIQ Plugin", "name": "homebridge-sharkiq", - "version": "1.2.3", + "version": "1.3.1", "description": "A Homebridge plugin to connect your Shark Vacuum to homebridge.", "license": "Apache-2.0", "repository": { @@ -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", @@ -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 8f07fc1..dd0ab66 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,50 +1,158 @@ import { promises as fs } from 'fs'; +import crypto from 'crypto'; + import { AuthData, OAuthData } from './type'; +import { global_vars } from './sharkiq-js/const'; -async function getAuthFile(authFilePath: string): Promise { +export async function getAuthData(authFilePath: string): Promise { try { const data = await fs.readFile(authFilePath, 'utf8'); const authData = JSON.parse(data); return authData; - } catch { - return null; + } catch (error) { + return Promise.reject('Error reading OAuth data: ' + error); } } -async function setAuthFile(authFilePath: string, data: AuthData): Promise { +// export async function getAuthData(configPath: string): Promise { +// try { +// const data = await fs.readFile(configPath, 'utf8'); +// const currentConfig = JSON.parse(data); + +// 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(`${PLATFORM_NAME} platform not found in config`); +// } + +// 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'); +// } + +// 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 { - await fs.writeFile(authFilePath, JSON.stringify(data), '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), 'utf8'); - return true; - } catch { - return false; + await fs.writeFile(oAuthFilePath, JSON.stringify(data, null, 4), 'utf8'); + } 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 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); } } -export { getAuthFile, setAuthFile, getOAuthData, setOAuthData, removeFile }; +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 ab0d07d..872310a 100644 --- a/src/login.ts +++ b/src/login.ts @@ -1,161 +1,252 @@ import { Logger } from 'homebridge'; -import { getAuthFile, setAuthFile, 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 { public log: Logger; - public storagePath: string; - public authFile: 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 auth_file_path: string; - public oauth_file_path: string; + public oAuthCode: string; constructor(log: Logger, - storagePath: string, + 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, ) { this.log = log; - this.storagePath = storagePath; + this.auth_file = auth_file; + this.oauth_file = oauth_file; + this.email = email; + this.password = password; this.oAuthCode = oAuthCode; - this.authFile = global_vars.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 oauth_file = await getOAuthData(this.oauth_file_path); - if (!auth_file) { - 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; + + const architecure = process.arch; + const platform = process.platform; + if (email === '' && password === '') { + if (this.oAuthCode === '') { + 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 { - this.log.info('Successfully logged in to Shark'); - return true; + 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 { + 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); + + 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); } } - const url = await this.generateURL(); - if (!url) { - this.log.error('Error generating Shark login URL'); - return false; - } - 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; } } - private async login(code: string, oAuthData: OAuthData): Promise { - const data = { - grant_type: 'authorization_code', - client_id: global_vars.SHARK_CLIENT_ID, - code: code, - code_verifier: oAuthData.code_verify, - redirect_uri: global_vars.OAUTH.REDIRECT_URI, - }; + 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 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 = setAuthFile(this.auth_file_path, aylaTokenData); - if (!status) { - this.log.error('Error saving auth file.'); - return false; + 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); } - await removeFile(this.oauth_file_path); - return true; } catch (error) { - this.log.error('Error: ' + error); - return false; + return Promise.reject(`Error: ${error}`); + } + if (error !== '') { + return Promise.reject(`Error: ${error}`); } } - 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(/=+$/, ''); + private async loginCallback(code: string, oAuthData: OAuthData): Promise { + const data = { + grant_type: 'authorization_code', + client_id: global_vars.OAUTH.CLIENT_ID, + code: code, + code_verifier: oAuthData.code_verify, + redirect_uri: global_vars.OAUTH.REDIRECT_URI, + }; - 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.SHARK_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 f280634..db33809 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -1,15 +1,14 @@ 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'; 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'; +import { global_vars } from './sharkiq-js/const'; // SharkIQPlatform Main Class export class SharkIQPlatform implements DynamicPlatformPlugin { @@ -47,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); }); }); @@ -55,22 +54,30 @@ 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 login = new Login(this.log, this.api.user.storagePath(), oAuthCode); + const storagePath = this.api.user.storagePath(); + const auth_file = join(storagePath, global_vars.FILE); + const oauth_file = join(storagePath, global_vars.OAUTH.FILE); + const oAuthCode = this.config.oAuthCode || ''; + const email = this.config.email || ''; + const password = this.config.password || ''; + if (!email || typeof email !== 'string' || !password || typeof password !== 'string') { + 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, oAuthCode); 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); + 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 13fbc85..16f2b7c 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,84 +62,105 @@ export class SharkIQAccessory { this.updateStates(); - // Monitor vacuum state - this.monitorVacuumState().then(() => { - this.monitorVacuumStateInterval(); + // Retrieve vacuum states + this.retrieveVacuumStates().then(() => { + this.retrieveVacuumStateInterval(); }) .catch(() => { this.log.debug('Promise Rejected with first interval update.'); - this.monitorVacuumStateInterval(); + this.retrieveVacuumStateInterval(); }); } - // Monitor vacuum state interval function - async monitorVacuumStateInterval(): Promise { + // Retrieve vacuum states interval function + async retrieveVacuumStateInterval(): Promise { setInterval(async () => { - await this.monitorVacuumState() + await this.retrieveVacuumStates() .catch(() => { this.log.debug('Promise Rejected with interval update.'); }); - }, this.dockedUpdateInterval); + }, this.dockedUpdateInterval + this.dockedDelay); } - // Monitor vacuum state function - async monitorVacuumState(): Promise { - let vacuumDocked = false; - - await this.device.update(Properties.DOCKED_STATUS) - .catch(() => { - this.log.debug('Promise Rejected with docked status update.'); - }); + // Retrieve docked status + 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; 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.'); - }); + return vacuumDocked; + } - this.dockedStatusService.updateCharacteristic(this.platform.Characteristic.ContactSensorState, vacuumDocked); + // Retrieve operating mode + async retrieveOperatingMode(): Promise { + this.log.debug('Triggering GET Operating Mode'); + await this.device.update(Properties.OPERATING_MODE) + .then((delay) => { + this.dockedDelay = delay; + }); + } - this.log.debug('Triggering Vacuum Docked:', vacuumDocked); + // Retrieve power mode + async retrievePowerMode(): Promise { + this.log.debug('Triggering GET Power Mode'); + await this.device.update(Properties.POWER_MODE) + .then((delay) => { + this.dockedDelay = delay; + }); } - // 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.'); + // 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; }); - 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.'); - }); + 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 { 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); @@ -150,11 +174,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; @@ -163,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) { @@ -186,7 +212,6 @@ export class SharkIQAccessory { } - // Check if the vacuum is active for UI async getVacuumActive(): Promise { this.log.debug('Triggering GET Vacuum Active'); @@ -217,11 +242,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) { @@ -236,7 +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('Triggering SET Fan Speed. Value:', value); let power_mode = PowerModes.NORMAL; if (value === 30) { @@ -252,7 +278,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 5cea816..f99a2e3 100644 --- a/src/sharkiq-js/ayla_api.ts +++ b/src/sharkiq-js/ayla_api.ts @@ -4,13 +4,14 @@ import { Logger } from 'homebridge'; import { global_vars } from './const'; import { SharkIqVacuum } from './sharkiq'; -import { getAuthFile, setAuthFile } from '../config'; +import { getAuthData, setAuthData } from '../config'; import { addSeconds, subtractSeconds, isValidDate } from '../utils'; import { AuthData } from '../type'; type APIResponse = { status: number; response: string; + ok: boolean; }; // New AylaApi object @@ -74,20 +75,22 @@ 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 - this._access_token = login_result['access_token']; - this._refresh_token = login_result['refresh_token']; - const _auth_expiration = new Date(login_result['expiration']); + 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; this._is_authed = true; } @@ -95,13 +98,14 @@ class AylaApi { // Sign in with auth file async sign_in(): Promise { this.log.debug('Signing in.'); - const authFile = await getAuthFile(this._auth_file_path); - if (!authFile) { - 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(authFile); - return true; } // Refresh auth token @@ -122,7 +126,7 @@ class AylaApi { } const dateNow = new Date(); jsonResponse['expiration'] = addSeconds(dateNow, jsonResponse['expires_in']); - setAuthFile(this._auth_file_path, jsonResponse); + await setAuthData(this._auth_file_path, jsonResponse); this._set_credentials(jsonResponse); return true; } catch (error) { @@ -238,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(); @@ -246,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); @@ -259,7 +263,7 @@ class AylaApi { return d; } catch { this.log.debug('Promise Rejected with list devices.'); - return []; + return Promise.reject('Error: Unable to list devices.'); } } @@ -277,9 +281,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 d439ec9..6f5a802 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', diff --git a/src/sharkiq-js/sharkiq.ts b/src/sharkiq-js/sharkiq.ts index 656c8fe..28ee336 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,42 +162,70 @@ class SharkIqVacuum { const url = this.update_url; try { if (!full_update && property_list.length !== 0) { - 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}`); + }); + 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; } - const properties = JSON.parse(resp.response); - this._do_update(full_update, properties); + } 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); - 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; + 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'])}`); + } + 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 { - await this.update(property_list, attempt + 1); - return; + this._do_update(full_update, properties); + return 0; } + } catch { + this.log.warn('Error parsing JSON response for properties.'); + return ERROR_DELAY; } - const properties = JSON.parse(resp.response); - this._do_update(full_update, properties); } - } catch { + } catch (e) { this.log.debug('Promise Rejected with updating properties.'); + return ERROR_DELAY; } } 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 23298c8..e54c350 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); @@ -22,4 +12,4 @@ function isValidDate(d: Date): boolean { return d instanceof Date && !isNaN(d.getTime()); } -export { formatParams, addSeconds, subtractSeconds, isValidDate }; +export { addSeconds, subtractSeconds, isValidDate }; 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,