From 80ad78ac87053ca52c66d1b6b25b762a1dcbf798 Mon Sep 17 00:00:00 2001 From: nezu <29180158+dumbasPL@users.noreply.github.com> Date: Thu, 8 Sep 2022 01:47:17 +0200 Subject: [PATCH] rewrite everything --- .github/workflows/ci.yml | 48 ++++ .github/workflows/docker-build.yml | 16 -- .github/workflows/docker-publish.yml | 18 +- .gitignore | 3 +- Dockerfile | 3 + README.md | 30 +-- nodemon.json | 2 +- package.json | 25 +- src/DelugeClient.ts | 120 ++++++++++ src/WindscribeClient.ts | 272 +++++++++++++++++++++ src/config.ts | 112 +++++++++ src/deluge.ts | 52 ---- src/index.ts | 164 +++++++++---- src/windscribe.ts | 183 -------------- tsconfig.json | 12 +- yarn.lock | 341 +++++++++++++++++---------- 16 files changed, 926 insertions(+), 475 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/docker-build.yml create mode 100644 src/DelugeClient.ts create mode 100644 src/WindscribeClient.ts create mode 100644 src/config.ts delete mode 100644 src/deluge.ts delete mode 100644 src/windscribe.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..76bb8c2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +name: Docker build +on: + push: + pull_request: +jobs: + docker-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: docker/setup-qemu-action@v2 + + - uses: docker/setup-buildx-action@v2 + + - uses: docker/build-push-action@v3 + with: + context: . + platforms: linux/amd64,linux/arm/v7,linux/arm/v6,linux/arm64/v8 + + build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [16.x, 17.x, 18.x] + steps: + - uses: actions/checkout@v3 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + + - run: yarn install --pure-lockfile + + - run: yarn build + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 + with: + node-version: 16.x + + - run: yarn install --pure-lockfile + + - run: yarn lint diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml deleted file mode 100644 index 2a1138a..0000000 --- a/.github/workflows/docker-build.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Docker build -on: - push: - pull_request: -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - - name: Build image - uses: docker/build-push-action@v2 diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 6270fa7..0d67ee9 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -11,15 +11,11 @@ jobs: packages: write contents: read steps: - - name: Checkout - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v1 + - uses: docker/setup-buildx-action@v2 - name: Login to DockerHub uses: docker/login-action@v1 @@ -28,15 +24,14 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Log in to GitHub Container registry - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Docker meta + - uses: docker/metadata-action@v4 id: meta - uses: docker/metadata-action@v3 with: images: | dumbasPL/deluge-windscribe-ephemeral-port @@ -45,8 +40,7 @@ jobs: type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} - - name: Build and push - uses: docker/build-push-action@v2 + - uses: docker/build-push-action@v2 with: context: . push: true diff --git a/.gitignore b/.gitignore index 78d1b0a..8b43bed 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ dist/ !.yarn/plugins !.yarn/releases !.yarn/sdks -!.yarn/versions \ No newline at end of file +!.yarn/versions +cache/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 3bc52b7..f25eea1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,6 +14,9 @@ FROM node:16-alpine ENV NODE_ENV=production ENV PORT=3000 +ENV CACHE_DIR=/cache + +RUN mkdir -p $CACHE_DIR WORKDIR /app diff --git a/README.md b/README.md index e5a2630..8af4cf8 100644 --- a/README.md +++ b/README.md @@ -12,32 +12,4 @@ It will only update the port that deluge listens on to the same port that's conf # Running -## Using docker (and docker compose in this example) -```yml -version: '3' -services: - deluge-windscribe-ephemeral-port: - image: dumbaspl/deluge-windscribe-ephemeral-port - restart: unless-stopped - environment: - - WINDSCRIBE_USERNAME= - - WINDSCRIBE_PASSWORD= - - DELUGE_URL= - - DELUGE_PASSWORD= -``` - -## Using nodejs -Tested on node 16 but should work on node 14 as well. -This project uses [yarn](https://classic.yarnpkg.com/) to manage dependencies, make sure you have it installed first. - -1. Install dependencies by running `yarn` -2. Create a `.env` file with the necessary configuration -``` -WINDSCRIBE_USERNAME= -WINDSCRIBE_PASSWORD= -DELUGE_URL= -DELUGE_PASSWORD= -``` -3. Start using `yarn start` - -Tip: you can use tools like [pm2](https://www.npmjs.com/package/pm2) to manage nodejs applications +TODO \ No newline at end of file diff --git a/nodemon.json b/nodemon.json index 5709da3..eeb8612 100644 --- a/nodemon.json +++ b/nodemon.json @@ -3,7 +3,7 @@ "ignore": [".git", "node_modules/", "dist/"], "watch": ["src/"], "execMap": { - "ts": "node -r ts-node/register" + "ts": "node --loader ts-node/esm" }, "env": { "NODE_ENV": "development" diff --git a/package.json b/package.json index 45850d1..6f8ff25 100644 --- a/package.json +++ b/package.json @@ -1,34 +1,41 @@ { "name": "project", - "version": "1.0.0", + "version": "2.0.0-rc1", "description": "", "keywords": [], "license": "MIT", "author": "nezu", "main": "dist/index.js", + "type": "module", "scripts": { - "start": "ts-node src/index.ts", + "start": "node --loader ts-node/esm src/index.ts", "dev": "nodemon --config nodemon.json src/index.ts", "dev:debug": "nodemon --config nodemon.json --inspect-brk src/index.ts", "lint": "eslint . --ext .ts", "build": "tsc" }, "devDependencies": { - "@types/node": "^18.7.7", + "@types/async-lock": "^1.1.5", + "@types/node": "^18.7.16", + "@types/node-cron": "^3.0.4", "@types/qs": "^6.9.7", "@types/set-cookie-parser": "^2.4.2", - "@typescript-eslint/eslint-plugin": "^5.33.1", - "@typescript-eslint/parser": "^5.33.1", - "eslint": "^8.22.0", + "@typescript-eslint/eslint-plugin": "^5.36.2", + "@typescript-eslint/parser": "^5.36.2", + "eslint": "^8.23.0", "eslint-config-google": "^0.14.0", "nodemon": "^2.0.19", "ts-node": "^10.9.1", - "typescript": "^4.7.4" + "typescript": "^4.8.2" }, "dependencies": { - "@ctrl/deluge": "^4.1.0", + "@ctrl/deluge": "^4.2.0", + "async-lock": "^1.3.2", "axios": "^0.27.2", - "dotenv": "^16.0.1", + "dotenv": "^16.0.2", + "keyv": "^4.5.0", + "keyv-file": "^0.2.0", + "node-cron": "^3.0.2", "qs": "^6.11.0", "set-cookie-parser": "^2.5.1" } diff --git a/src/DelugeClient.ts b/src/DelugeClient.ts new file mode 100644 index 0000000..2390ee4 --- /dev/null +++ b/src/DelugeClient.ts @@ -0,0 +1,120 @@ +import {Deluge} from '@ctrl/deluge'; + +export class DelugeClient { + + private deluge: Deluge; + private currentHost?: string; + + constructor( + url: string, + password: string, + private defaultHostId?: string, + ) { + this.deluge = new Deluge({ + baseUrl: url, + password: password, + }); + } + + async updateConnection(): Promise<{ hostId: string; version: string; }> { + // session check + if (!await this.deluge.checkSession()) { + // login if not logged in already + if (!await this.deluge.login()) { + throw new Error('Failed to connect to deluge'); + } + } + + // connection check + if (!this.currentHost || !await this.deluge.connected()) { + const {result: hosts, error} = await this.deluge.getHosts(); + + if (error) { + throw new Error(`Deluge getHosts error: ${error}`); + } + + if (hosts.length == 0) { + throw new Error('No deluge hosts available'); + } + + let hostId = this.currentHost || this.defaultHostId; + if (hostId) { + // make sure the host actually exists + if (!hosts.some(host => host[0] == hostId)) { + throw new Error(`Deluge host with id ${hostId} does not exist`); + } + } else { + if (hosts.length == 1) { + // if we have a single host, just use it + hostId = hosts[0][0]; + console.log(`Selecting the only available deluge host: ${hostId}`); + } else { + console.log( + `Found ${hosts.length} deluge hosts(id: host:port - status): \n` + + hosts + .map(host => `\t${host[0]}: ${host[1]}:${host[2]} - ${host[3]}`) + .join('\n') + ); + throw new Error(`Found more than one deluge host, select one via DELUGE_HOST_ID env variable`); + } + } + + // try to connect if not connected already + await this.deluge.connect(hostId); + this.currentHost = hostId; + } + + // check the status of the current host + const {result: { + [0]: hostId, + [1]: status, + [2]: version, + }, error} = await this.deluge.getHostStatus(this.currentHost); + + if (error) { + throw new Error(`Deluge getHostStatus error: ${error}`); + } + + // this should never fail in theory + if (status != 'Connected') { + throw new Error('Not connected to deluge'); + } + + // report status + return { + hostId, + version, + }; + } + + async getPort() { + // make sure we are connected + await this.updateConnection(); + + const {error, result: config} = await this.deluge.getConfig(); + + if (error) { + throw new Error(`Deluge getConfig error: ${error}`); + } + + return config.random_port ? 0 : config.listen_ports[0]; + } + + async updatePort(port: number): Promise { + // make sure we are connected + await this.updateConnection(); + + // update port + const {error} = await this.deluge.setConfig({ + listen_ports: [port, port], + random_port: false, // turn of random port as well + }); + + if (error) { + throw new Error(`Deluge setConfig error: ${error}`); + } + + console.log('Deluge port successfully updated'); + } + +} diff --git a/src/WindscribeClient.ts b/src/WindscribeClient.ts new file mode 100644 index 0000000..f60faa8 --- /dev/null +++ b/src/WindscribeClient.ts @@ -0,0 +1,272 @@ +import AsyncLock from 'async-lock'; +import {AxiosResponse, default as axios} from 'axios'; +import {Store, default as Keyv} from 'keyv'; +import {Cookie, parse as parseCookie} from 'set-cookie-parser'; +import qs from 'qs'; + + +const lock = new AsyncLock(); + +const userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'; + +interface CsrfInfo { + csrfTime: number; + csrfToken: string; +} + +interface PortForwardingInfo { + epfExpires: number; + ports: number[]; +} + +export interface WindscribePort { + port: number, + expires: Date, +} + +export class WindscribeClient { + + private cache: Keyv; + + constructor( + private username: string, + private password: string, + cache?: Store, + ) { + this.cache = new Keyv({ + store: cache, + namespace: 'windscribe', + }); + } + + async updatePort(): Promise { + // get csrf token and time to pass on to future requests + // this will also verify if we are logged in and login if not + const csrfToken = await this.getMyAccountCsrfToken(); + + // check for current status + let portForwardingInfo = await this.getPortForwardingInfo(); + + // check for mismatched ports if any present + if (portForwardingInfo.ports.length == 2 && portForwardingInfo.ports[0] != portForwardingInfo.ports[1]) { + console.log('Detected mismatched ports, removing existing ports'); + await this.removeEphemeralPort(csrfToken); + + // update data to match current state + portForwardingInfo.ports = []; + portForwardingInfo.epfExpires = 0; + await this.cache.delete('port'); + } + + // request new port if we don't have any + if (portForwardingInfo.epfExpires == 0) { + console.log('No windscribe port configured, requesting new matching ephemeral port'); + portForwardingInfo = await this.requestMatchingEphemeralPort(csrfToken); + } else { + console.log(`Using existing windscribe ephemeral port: ${portForwardingInfo.ports[0]}`); + } + + const ret = { + port: portForwardingInfo.ports[0], + expires: new Date((portForwardingInfo.epfExpires + 86400 * 7) * 1000), + }; + + await this.cache.set('port', ret.port.toString(), ret.expires.getTime() - Date.now()); + + return ret; + } + + async getPort(): Promise { + const cachedPort = await this.cache.get('port', {raw: true}); + return cachedPort == undefined ? null : { + port: parseInt(cachedPort.value), + expires: new Date(cachedPort.expires), + }; + } + + private async getSession(forceLogin: boolean = false): Promise { + return lock.acquire('getSession', async () => { + if (forceLogin) { + // force clear the session + await this.cache.delete('sessionCookie'); + } else { + // try to get cached value + const cachedCookie = await this.cache.get('sessionCookie'); + if (cachedCookie != undefined) { + return cachedCookie; + } + } + + // get a new session + console.log(`Invalid/missing session cookie, logging into windscribe`); + const sessionCookie = await this.login(); + await this.cache.set('sessionCookie', sessionCookie.value, sessionCookie.expires.getTime() - Date.now()); + console.log(`Successfully logged into windscribe, session expires in ${Math.floor((sessionCookie.expires.getTime() - Date.now()) / (100 * 60)) / 10} minutes`); + + return sessionCookie.value; + }); + } + + private async login(): Promise { + try { + // get csrf token and time + const {data: csrfData} = await axios.post<{csrf_token: string, csrf_time: number}>('https://res.windscribe.com/res/logintoken', null, { + headers: {'User-Agent': userAgent}, + }); + + // log in + const res = await axios.post('https://windscribe.com/login', qs.stringify({ + login: '1', + upgrade: '0', + csrf_time: csrfData.csrf_time, + csrf_token: csrfData.csrf_token, + username: this.username, + password: this.password, + code: '' + }), { + headers: {'content-type': 'application/x-www-form-urlencoded', 'User-Agent': userAgent}, + maxRedirects: 0, + validateStatus: status => status == 302, + }); + + // extract the cookie + return parseCookie(res.headers['set-cookie'], {map: true, decodeValues: true})['ws_session_auth_hash']; + } catch (error) { + // try to extract windscribe message + if (error.response) { + const response = error.response as AxiosResponse; + const errorMessage = /
.*>(.*)<\/div/.exec(response.data); + if (response.status == 200 && errorMessage && errorMessage[1]) { + throw new Error(`Failed to log into windscribe: ${errorMessage[1]}`); + } + } + + // or throw a generic error if windscribe message not found + throw new Error(`Failed to log into windscribe: ${error.message}`); + } + } + + private async getMyAccountCsrfToken(forceLogin: boolean = false): Promise { + try { + const sessionCookie = await this.getSession(forceLogin); + + // get page + const res = await axios.get('https://windscribe.com/myaccount', { + headers: { + 'Cookie': `ws_session_auth_hash=${sessionCookie};`, + 'User-Agent': userAgent, + }, + maxRedirects: 0, + validateStatus: status => [302, 200].includes(status), + }); + + if (res.status == 302) { + // force to login again as the current session is invalid + return await this.getMyAccountCsrfToken(true); + } + + // extract csrf tokena and time from page content + const csrfTime = /csrf_time = (\d+);/.exec(res.data)[1]; + const csrfToken = /csrf_token = '(\w+)';/.exec(res.data)[1]; + + return { + csrfTime: +csrfTime, + csrfToken: csrfToken, + }; + } catch (error) { + throw new Error(`Failed to get csrf token from my account page: ${error.message}`); + } + } + + private async getPortForwardingInfo(): Promise { + try { + const sessionCookie = await this.getSession(); + + // load sub page + const res = await axios.get('https://windscribe.com/staticips/load', { + headers: { + 'Cookie': `ws_session_auth_hash=${sessionCookie};`, + 'User-Agent': userAgent, + } + }); + + // extract data from page + const epfExpires = res.data.match(/epfExpires = (\d+);/)[1]; // this is always present. set to 0 if no port is active + const ports = [...res.data.matchAll(/(?\d+)<\/span>/g)].map(x => +x[1]); // this will return an empty array when there are not pots forwarded + + return { + epfExpires: +epfExpires, + ports, + }; + } catch (error) { + throw new Error(`Failed to get port forwarding info: ${error.message}`); + } + } + + private async removeEphemeralPort(csrfInfo: CsrfInfo): Promise { + try { + const sessionCookie = await this.getSession(); + + // remove port + const res = await axios.post<{success: number, epf: boolean, message?: string}>('https://windscribe.com/staticips/deleteEphPort', qs.stringify({ + ctime: csrfInfo.csrfTime, + ctoken: csrfInfo.csrfToken + }), { + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'Cookie': `ws_session_auth_hash=${sessionCookie};`, + 'User-Agent': userAgent, + } + }); + + // check for errors + if (res.data.success == 0) { + throw new Error(`success = 0; ${res.data.message ?? 'No message'}`); + } + + // make sure we actually removed it + if (res.data.epf == false) { + console.warn('Tried to remove a non-existent ephemeral port, ignoring'); + } else { + console.log('Deleted ephemeral port'); + } + } catch (error) { + throw new Error(`Failed to delete ephemeral port: ${error.message}`); + } + } + + private async requestMatchingEphemeralPort(csrfInfo: CsrfInfo): Promise { + try { + const sessionCookie = await this.getSession(); + + // request new port + const res = await axios.post<{success: number, message?: string, epf?: {ext: number, int: number, start_ts: number}}>('https://windscribe.com/staticips/postEphPort', qs.stringify({ + ctime: csrfInfo.csrfTime, + ctoken: csrfInfo.csrfToken, + port: '', // empty string for a matching port + }), { + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'Cookie': `ws_session_auth_hash=${sessionCookie};`, + 'User-Agent': userAgent, + } + }); + + // check for errors + if (res.data.success == 0) { + throw new Error(`success = 0; ${res.data.message ?? 'No message'}`); + } + + // epf should be present by this point + const epf = res.data.epf!; + console.log(`Created new matching ephemeral port: ${epf.ext}`); + return { + epfExpires: epf.start_ts, + ports: [epf.ext, epf.int], + }; + } catch (error) { + throw new Error; + } + } + +} diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..7ca3d8f --- /dev/null +++ b/src/config.ts @@ -0,0 +1,112 @@ + +interface ConfigTemplate { + envVariableName: string, + type: T extends string ? typeof String : typeof Number, +} + +interface ConfigTemplateRequiredEntry extends ConfigTemplate { + required: true, +} + +interface ConfigTemplateOptionalEntry extends ConfigTemplate { + required: false, + default?: string, +} + +const configTemplate = { + delugeUrl: { + envVariableName: 'DELUGE_URL', + required: true, + type: String, + } as ConfigTemplateRequiredEntry, + delugePassword: { + envVariableName: 'DELUGE_PASSWORD', + required: true, + type: String, + } as ConfigTemplateRequiredEntry, + delugeHostId: { + envVariableName: 'DELUGE_HOST_ID', + required: false, + type: String, + } as ConfigTemplateOptionalEntry, + delugeRetryDelay: { + envVariableName: 'DELUGE_RETRY_DELAY', + required: false, + default: `${5 * 60 * 1000}`, // 5 minutes + type: Number, + } as ConfigTemplateOptionalEntry, + windscribeUsername: { + envVariableName: 'WINDSCRIBE_USERNAME', + required: true, + type: String, + } as ConfigTemplateRequiredEntry, + windscribePassword: { + envVariableName: 'WINDSCRIBE_PASSWORD', + required: true, + type: String, + } as ConfigTemplateRequiredEntry, + windscribeRetryDelay: { + envVariableName: 'WINDSCRIBE_RETRY_DELAY', + required: false, + default: `${60 * 60 * 1000}`, // one hour + type: Number, + } as ConfigTemplateOptionalEntry, + windscribeExtraDelay: { + envVariableName: 'WINDSCRIBE_EXTRA_DELAY', + required: false, + default: `${60 * 1000}`, // one minute + type: Number, + } as ConfigTemplateOptionalEntry, + cronSchedule: { + envVariableName: 'CRON_SCHEDULE', + required: false, + type: String, + } as ConfigTemplateOptionalEntry, + cacheDir: { + envVariableName: 'CACHE_DIR', + required: false, + default: './cache', + type: String, + } as ConfigTemplateOptionalEntry, +}; + +type entryType = + ConfigTemplateRequiredEntry | + ConfigTemplateOptionalEntry | + ConfigTemplateRequiredEntry | + ConfigTemplateOptionalEntry; + +type configTemplateType = typeof configTemplate; + +type Config = + {[key in keyof configTemplateType as configTemplateType[key] extends ConfigTemplateRequiredEntry ? key : never]: string} & + {[key in keyof configTemplateType as configTemplateType[key] extends ConfigTemplateOptionalEntry ? key : never]?: string} & + {[key in keyof configTemplateType as configTemplateType[key] extends ConfigTemplateOptionalEntry ? key : never]?: number} & + {[key in keyof configTemplateType as configTemplateType[key] extends ConfigTemplateOptionalEntry ? key : never]?: number}; + +export function getConfig(): Config { + const entries = Object.entries(configTemplate).map(([name, entry]: [string, entryType]) => { + let value = process.env[entry.envVariableName]; + + // this needs an explicit `== true` check because typescript + if (entry.required == true) { + if (!value || value.length == 0) { + throw new Error(`Missing environment variable ${entry.envVariableName}`); + } + } else { + value = value || entry.default || ''; + } + + if (entry.type == Number) { + const intValue = parseInt(value); + if (isNaN(intValue)) { + throw new Error(`Environment variable ${entry.envVariableName}`); + } + return [name, intValue]; + } + + return [name, value ? value : null]; + }); + + return Object.fromEntries(entries); +} diff --git a/src/deluge.ts b/src/deluge.ts deleted file mode 100644 index c5af67e..0000000 --- a/src/deluge.ts +++ /dev/null @@ -1,52 +0,0 @@ -import {Deluge} from '@ctrl/deluge'; - -// env variables - -const url = process.env.DELUGE_URL; -const password = process.env.DELUGE_PASSWORD; - -if (!url || url.length == 0) { - console.log('Missing environment variable DELUGE_URL'); - process.exit(1); -} - -if (!password || password.length == 0) { - console.log('Missing environment variable DELUGE_PASSWORD'); - process.exit(1); -} - -// functions - -export async function testDelugeConnection() { - try { - const client = new Deluge({ - baseUrl: url, - password: password, - }); - - await client.connect(); - } catch (error) { - throw new Error(`Failed to connect to deluge: ${error.message}`); - } -} - -export async function updateDelugePort(port: number) { - try { - const client = new Deluge({ - baseUrl: url, - password: password, - }); - - const res = await client.setConfig({ - listen_ports: [port, port], - random_port: false, // turn of random port as well - }); - - if (res.error) { - throw new Error(res.error); - } - console.log('Deluge port successfully updated'); - } catch (error) { - throw new Error(`Failed to update deluge port: ${error.message}`); - } -} diff --git a/src/index.ts b/src/index.ts index b5068e9..c0ba123 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,54 +1,130 @@ import 'dotenv/config'; -import {testDelugeConnection, updateDelugePort} from './deluge'; -import {getMyAccountCsrfToken, getPortForwardingInfo, login, removeEphemeralPort, requestMatchingEphemeralPort} from './windscribe'; +import path from 'path'; +import {KeyvFile} from 'keyv-file'; +import {getConfig} from './config.js'; +import {DelugeClient} from './DelugeClient.js'; +import {WindscribeClient, WindscribePort} from './WindscribeClient.js'; +import {schedule} from 'node-cron'; -async function run() { +// load config +const config = getConfig(); + +// init cache (if configured) +const cache = !config.cacheDir ? undefined : new KeyvFile({ + filename: path.join(config.cacheDir, 'cache.json'), +}); + +// inti windscribe client +const windscribe = new WindscribeClient(config.windscribeUsername, config.windscribePassword, cache); + +// init deluge client +const deluge = new DelugeClient(config.delugeUrl, config.delugePassword, config.delugeHostId); + +// init schedule if configured +const scheduledTask = !config.cronSchedule ? null : + schedule(config.cronSchedule, () => run('schedule'), {scheduled: false}); + +async function update() { + let nextRetry: Date = null; + let nextRun: Date = null; + + let portInfo: WindscribePort; try { - // try to connect to deluge once before we start spamming windscribe with pointless requests - await testDelugeConnection(); + // try to update ephemeral port + portInfo = await windscribe.updatePort(); - // get a new session each time - const sessionCookie = await login(); - - // get csrf token and time to pass on to future requests - const csrfToken = await getMyAccountCsrfToken(sessionCookie); - - // check for current status - let portForwardingInfo = await getPortForwardingInfo(sessionCookie); - - // check for mismatched ports if any present - if (portForwardingInfo.ports.length == 2 && portForwardingInfo.ports[0] != portForwardingInfo.ports[1]) { - console.log('detected mismatched ports, removing existing ports'); - await removeEphemeralPort(sessionCookie, csrfToken); - - // update data to match current state - portForwardingInfo.ports = []; - portForwardingInfo.epfExpires = 0; - } - - // request new port if we don't have any - if (portForwardingInfo.epfExpires == 0) { - console.log('no port configured, Requesting new matching ephemeral port'); - portForwardingInfo = await requestMatchingEphemeralPort(sessionCookie, csrfToken); - } else { - console.log(`Using existing ephemeral port: ${portForwardingInfo.ports[0]}`); - } - - // update deluge with new port - console.log('Updating deluge'); - await updateDelugePort(portForwardingInfo.ports[0]); - - // schedule next run in 7 days since the time of creation (+ 1 minute just to be sure) - // (this code is copied form the windscribe website btw) - const expiresAt = new Date((portForwardingInfo.epfExpires + 86400 * 7) * 1000 + 60000); - const diff = expiresAt.getTime() - new Date().getTime(); // time difference in milliseconds - setTimeout(run, diff); - console.log(`Port expires in ${Math.floor(diff/1000)} seconds. Next run scheduled at ${expiresAt.toLocaleString()}`); + const windscribeExtraDelay = config.windscribeExtraDelay || (60 * 1000); + nextRun = new Date(portInfo.expires.getTime() + windscribeExtraDelay); } catch (error) { + console.error('Windscribe update failed: ', error); + + // if failed, retry after some delay + const windscribeRetryDelay = config.windscribeRetryDelay || (60 * 60 * 1000); + nextRetry = new Date(Date.now() + windscribeRetryDelay); + + // get cached info if available + portInfo = await windscribe.getPort(); + } + + try { + let currentPort = await deluge.getPort(); + if (portInfo) { + if (currentPort == portInfo.port) { + // no need to update + console.log(`Current deluge port (${currentPort}) already matches windscribe port`); + } else { + // update port to a new one + console.log(`Current deluge port (${currentPort}) does not match windscribe port (${portInfo.port})`); + await deluge.updatePort(portInfo.port); + + // double check + currentPort = await deluge.getPort(); + if (currentPort != portInfo.port) { + throw new Error(`Unable to set deluge port! Current deluge port: ${currentPort}`); + } + console.log('Deluge port updated'); + } + } else { + console.log(`Windscribe port is unknown, current deluge port is ${currentPort}`); + } + } catch (error) { + console.error('Deluge update failed', error); + + // if failed, retry after some delay + const delugeRetryDelay = config.delugeRetryDelay || (5 * 60 * 1000); + nextRetry = new Date(Date.now() + delugeRetryDelay); + } + + return { + nextRun, + nextRetry, + }; +} + +let timeoutId: NodeJS.Timeout; // next run/retry timer +async function run(trigger: string) { + console.log(`starting update, trigger type: ${trigger}`); + + // clear any previous timeouts (relevant when triggered by schedule) + clearTimeout(timeoutId); + + // the magic + const {nextRun, nextRetry} = await update().catch(error => { + // in theory this should never throw, if it does we have bigger problems console.error(error); - // just kill the process on error + process.exit(1); + }); + + // reties always take priority since they block normal runs from the retry delay + if (nextRetry) { + // disable schedule if present + scheduledTask?.stop(); + + // calculate delay + const delay = nextRetry.getTime() - Date.now(); + console.log(`Next retry scheduled for ${nextRetry.toLocaleString()} (in ${Math.floor(delay / 100) / 10} seconds)`); + + // set timer + timeoutId = setTimeout(() => run('retry'), delay); + } else if (nextRun) { + // re-enable schedule if present + scheduledTask?.start(); + + // calculate delay + const delay = nextRun.getTime() - Date.now(); + console.log(`Next normal run scheduled for ${nextRun.toLocaleString()} (in ${Math.floor(delay / 100) / 10} seconds)`); + if (scheduledTask != null) { + console.log('Cron schedule is configured, there might be runs happening sooner!'); + } + + // set timer + timeoutId = setTimeout(() => run('normal'), delay); + } else { + // in theory this should never happen + console.error('Invalid state, no next retry/run date present'); process.exit(1); } } -run(); +// always run on start +run('initial'); diff --git a/src/windscribe.ts b/src/windscribe.ts deleted file mode 100644 index 539a650..0000000 --- a/src/windscribe.ts +++ /dev/null @@ -1,183 +0,0 @@ -import axios, {AxiosResponse} from 'axios'; -import * as qs from 'qs'; -import {parse as parseCookie} from 'set-cookie-parser'; - -// env variables - -const username = process.env.WINDSCRIBE_USERNAME; -const password = process.env.WINDSCRIBE_PASSWORD; - -if (!username || username.length == 0) { - console.log('Missing environment variable WINDSCRIBE_USERNAME'); - process.exit(1); -} - -if (!password || password.length == 0) { - console.log('Missing environment variable WINDSCRIBE_PASSWORD'); - process.exit(1); -} - -// interfaces - -interface CsrfInfo { - csrfTime: number; - csrfToken: string; -} - -interface PortForwardingInfo { - epfExpires: number; - ports: number[]; -} - -// constants - -const userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36'; - -// functions - -export async function login(): Promise { - try { - // get csrf token and time - const {data: csrfData} = await axios.post<{csrf_token: string, csrf_time: number}>('https://res.windscribe.com/res/logintoken', null, { - headers: {'User-Agent': userAgent}, - }); - - // log in - const res = await axios.post('https://windscribe.com/login', qs.stringify({ - login: '1', - upgrade: '0', - csrf_time: csrfData.csrf_time, - csrf_token: csrfData.csrf_token, - username: username, - password: password, - code: '' - }), { - headers: {'content-type': 'application/x-www-form-urlencoded', 'User-Agent': userAgent}, - maxRedirects: 0, - validateStatus: status => status == 302, - }); - - // extract the cookie - return parseCookie(res.headers['set-cookie'], {map: true, decodeValues: true})['ws_session_auth_hash'].value; - } catch (error) { - // try to extract windscribe message - if (error.response) { - const response = error.response as AxiosResponse; - const errorMessage = /
.*>(.*)<\/div/.exec(response.data); - if (response.status == 200 && errorMessage && errorMessage[1]) { - throw new Error(`Failed to log into windscribe: ${errorMessage[1]}`); - } - } - - // or throw a generic error if windscribe message not found - throw new Error(`Failed to log into windscribe: ${error.message}`); - } -} - -export async function getMyAccountCsrfToken(sessionCookie: string): Promise { - try { - // get page - const res = await axios.get('https://windscribe.com/myaccount', { - headers: { - 'Cookie': `ws_session_auth_hash=${sessionCookie};`, - 'User-Agent': userAgent, - }, - }); - - // extract csrf tokena and time from page content - const csrfTime = /csrf_time = (\d+);/.exec(res.data)[1]; - const csrfToken = /csrf_token = '(\w+)';/.exec(res.data)[1]; - - return { - csrfTime: +csrfTime, - csrfToken: csrfToken, - }; - } catch (error) { - throw new Error(`Failed to get csrf token from my account page: ${error.message}`); - } -} - -export async function getPortForwardingInfo(sessionCookie: string): Promise { - try { - // load sub page - const res = await axios.get('https://windscribe.com/staticips/load', { - headers: { - 'Cookie': `ws_session_auth_hash=${sessionCookie};`, - 'User-Agent': userAgent, - } - }); - - // extract data from page - const epfExpires = res.data.match(/epfExpires = (\d+);/)[1]; // this is always present. set to 0 if no port is active - const ports = [...res.data.matchAll(/(?\d+)<\/span>/g)].map(x => +x[1]); // this will return an empty array when there are not pots forwarded - - return { - epfExpires: +epfExpires, - ports, - }; - } catch (error) { - throw new Error(`Failed to get port forwarding info: ${error.message}`); - } -} - -export async function removeEphemeralPort(sessionCookie: string, csrfInfo: CsrfInfo): Promise { - try { - // remove port - const res = await axios.post<{success: number, epf: boolean, message?: string}>('https://windscribe.com/staticips/deleteEphPort', qs.stringify({ - ctime: csrfInfo.csrfTime, - ctoken: csrfInfo.csrfToken - }), { - headers: { - 'content-type': 'application/x-www-form-urlencoded', - 'Cookie': `ws_session_auth_hash=${sessionCookie};`, - 'User-Agent': userAgent, - } - }); - - // check for errors - if (res.data.success == 0) { - throw new Error(`success = 0; ${res.data.message ?? 'No message'}`); - } - - // make sure we actually removed it - if (res.data.epf == false) { - console.log('Tried to remove a non-existent ephemeral port, ignoring'); - } else { - console.log('Deleted ephemeral port'); - } - } catch (error) { - throw new Error(`Failed to delete ephemeral port: ${error.message}`); - } -} - -export async function requestMatchingEphemeralPort(sessionCookie: string, csrfInfo: CsrfInfo): Promise { - try { - // request new port - const res = await axios.post<{success: number, message?: string, epf?: {ext: number, int: number, start_ts: number}}>('https://windscribe.com/staticips/postEphPort', qs.stringify({ - ctime: csrfInfo.csrfTime, - ctoken: csrfInfo.csrfToken, - port: '', // empty string for a matching port - }), { - headers: { - 'content-type': 'application/x-www-form-urlencoded', - 'Cookie': `ws_session_auth_hash=${sessionCookie};`, - 'User-Agent': userAgent, - } - }); - - // check for errors - if (res.data.success == 0) { - throw new Error(`success = 0; ${res.data.message ?? 'No message'}`); - } - - // epf should be present by this point - const epf = res.data.epf!; - console.log(`Created new matching ephemeral port: ${epf.ext}`); - return { - epfExpires: epf.start_ts, - ports: [epf.ext, epf.int], - }; - } catch (error) { - throw new Error; - } -} diff --git a/tsconfig.json b/tsconfig.json index ad55cb3..9cf6343 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,21 +1,23 @@ { "compilerOptions": { "lib": [ - "es5", "es6", "ES2016", "ES2018", ], - "target": "es5", - "module": "commonjs", - "moduleResolution": "node", + "target": "ES2017", + "module": "Node16", + "moduleResolution": "Node16", "outDir": "./dist", "emitDecoratorMetadata": true, "experimentalDecorators": true, "sourceMap": true, - "downlevelIteration": true + "skipLibCheck": true, // needed until https://github.com/sindresorhus/got/issues/2051 gets resolved }, "include": [ "src/**/*" ], + "ts-node": { + "esm": true + }, } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index da74d91..c1e6988 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,17 +9,17 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@ctrl/deluge@^4.1.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@ctrl/deluge/-/deluge-4.1.0.tgz#f9f1404162903a32b879bdcccc4cd6d3da38d391" - integrity sha512-U/iTUZ6tHsd+aesQncaTkzqIZSs+Zap7zRpU4OO8UVg4BHiU63RVc4Z9Vh6/kvknoM7E1JCBXg8kuA8fLSK4JQ== +"@ctrl/deluge@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@ctrl/deluge/-/deluge-4.2.0.tgz#7aaa183d6e6c974fa697f9b439bbf922784619b7" + integrity sha512-vmhrVuuaq/OdIGLBRFunVmmgLrJq5mdO8RMl1IY5hN/x2aDP5bSN2/MdYysiz4MrQBEacClI8E87/s8XJEkIoA== dependencies: "@ctrl/magnet-link" "^3.1.1" - "@ctrl/shared-torrent" "^4.1.1" - "@ctrl/url-join" "^2.0.0" - formdata-node "^4.3.2" - got "^12.1.0" - tough-cookie "^4.0.0" + "@ctrl/shared-torrent" "^4.2.0" + "@ctrl/url-join" "^2.0.2" + formdata-node "^5.0.0" + got "^12.3.1" + tough-cookie "^4.1.2" "@ctrl/magnet-link@^3.1.1": version "3.1.1" @@ -28,31 +28,31 @@ dependencies: "@ctrl/ts-base32" "^2.1.1" -"@ctrl/shared-torrent@^4.1.1": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@ctrl/shared-torrent/-/shared-torrent-4.1.1.tgz#0dc627a718ee078b682a36f58c19226a1c9ee340" - integrity sha512-gDCV02liPbajkOWYxaeFkFDwnv6bmNm/v4OEvGOXrQcwKAZN7HzD4zoGb2Etnp4GO916HUMSMyZQzjRwtUy7tg== +"@ctrl/shared-torrent@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@ctrl/shared-torrent/-/shared-torrent-4.2.0.tgz#d7d817e1125d09007d016304b78ffa0ec265e718" + integrity sha512-MvhC3xavvpiEc0Qd328Qe8Cc9rteUq/hIKy8paO7TZwfEUfCQYwUShvSTWy5aIUWZqHNg9S0fSdhDTc+LPT6Og== dependencies: - got "^12.1.0" + got "^12.3.1" "@ctrl/ts-base32@^2.1.1": version "2.1.1" resolved "https://registry.yarnpkg.com/@ctrl/ts-base32/-/ts-base32-2.1.1.tgz#e84abee414044462ff06e61f2afeddd516ea690d" integrity sha512-lQ6Q0hZyhGgzAn/+7h0mseWhMh5d8nS4fxlhjhJrVSc+3U7qvITBvcLRDk2p71OcPgU55ZXVYfTfr2FqjDljGQ== -"@ctrl/url-join@^2.0.0": +"@ctrl/url-join@^2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@ctrl/url-join/-/url-join-2.0.2.tgz#c65fdc0ca61c36248566dffe78c4636a274330bd" integrity sha512-V/QZoIgCB7kLCkqWoTImtXyzD8TnDqBfAzg167bTLp7CNbHHqpum9hLezpaBolhZK+wIZjSCHqFB7ajXVaIHMw== -"@eslint/eslintrc@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" - integrity sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw== +"@eslint/eslintrc@^1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.1.tgz#de0807bfeffc37b964a7d0400e0c348ce5a2543d" + integrity sha512-OhSY22oQQdw3zgPOOwdoj01l/Dzl1Z+xyUP33tkSN+aqyEhymJCcPHyXt+ylW8FSe0TfRC2VG+ROQOapD0aZSQ== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.3.2" + espree "^9.4.0" globals "^13.15.0" ignore "^5.2.0" import-fresh "^3.2.1" @@ -74,6 +74,11 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz#316b0a63b91c10e53f242efb4ace5c3b34e8728d" integrity sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA== +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + "@humanwhocodes/object-schema@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" @@ -150,6 +155,11 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== +"@types/async-lock@^1.1.5": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@types/async-lock/-/async-lock-1.1.5.tgz#a82f33e09aef451d6ded7bffae73f9d254723124" + integrity sha512-A9ClUfmj6wwZMLRz0NaYzb98YH1exlHdf/cdDSKBfMQJnPOdO8xlEW0Eh2QsTTntGzOFWURcEjYElkZ1IY4GCQ== + "@types/cacheable-request@^6.0.2": version "6.0.2" resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.2.tgz#c324da0197de0a98a2312156536ae262429ff6b9" @@ -177,22 +187,27 @@ dependencies: "@types/node" "*" +"@types/node-cron@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/node-cron/-/node-cron-3.0.4.tgz#ade755125a5b9e409ba9598e04c7132a05c108db" + integrity sha512-A2H+uz5ry4hohYjRe5mQSE/8Dx/HGw4WZ728JxhKUZ7z8CMvRuG2tpbzGHRGQCuQzz5aCNB1iXzPZYHd4BPHvw== + "@types/node@*": version "17.0.4" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.4.tgz#fec0ce0526abb6062fd206d72a642811b887a111" integrity sha512-6xwbrW4JJiJLgF+zNypN5wr2ykM9/jHcL7rQ8fZe2vuftggjzZeRSM4OwRc6Xk8qWjwJ99qVHo/JgOGmomWRog== -"@types/node@^18.7.7": - version "18.7.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.7.tgz#2f7e3443fb434315ff594c49043486fe49937182" - integrity sha512-sTKYCtQmaUpsAT+AbUTKg0Ya0dYyh20t3TBQebWrGXQHFmkrEyeedok2/IpTthlJopPSbUoc1hAKoK6UeFRCZw== +"@types/node@^18.7.16": + version "18.7.16" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.16.tgz#0eb3cce1e37c79619943d2fd903919fc30850601" + integrity sha512-EQHhixfu+mkqHMZl1R2Ovuvn47PUw18azMJOTwSZr9/fhzHNGXAJ0ma0dayRVchprpCj0Kc1K1xKoWaATWF1qg== "@types/qs@^6.9.7": version "6.9.7" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== -"@types/responselike@*", "@types/responselike@^1.0.0": +"@types/responselike@*": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== @@ -206,14 +221,14 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@^5.33.1": - version "5.33.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.1.tgz#c0a480d05211660221eda963cc844732fe9b1714" - integrity sha512-S1iZIxrTvKkU3+m63YUOxYPKaP+yWDQrdhxTglVDVEVBf+aCSw85+BmJnyUaQQsk5TXFG/LpBu9fa+LrAQ91fQ== +"@typescript-eslint/eslint-plugin@^5.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.36.2.tgz#6df092a20e0f9ec748b27f293a12cb39d0c1fe4d" + integrity sha512-OwwR8LRwSnI98tdc2z7mJYgY60gf7I9ZfGjN5EjCwwns9bdTuQfAXcsjSB2wSQ/TVNYSGKf4kzVXbNGaZvwiXw== dependencies: - "@typescript-eslint/scope-manager" "5.33.1" - "@typescript-eslint/type-utils" "5.33.1" - "@typescript-eslint/utils" "5.33.1" + "@typescript-eslint/scope-manager" "5.36.2" + "@typescript-eslint/type-utils" "5.36.2" + "@typescript-eslint/utils" "5.36.2" debug "^4.3.4" functional-red-black-tree "^1.0.1" ignore "^5.2.0" @@ -221,69 +236,70 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.33.1": - version "5.33.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.33.1.tgz#e4b253105b4d2a4362cfaa4e184e2d226c440ff3" - integrity sha512-IgLLtW7FOzoDlmaMoXdxG8HOCByTBXrB1V2ZQYSEV1ggMmJfAkMWTwUjjzagS6OkfpySyhKFkBw7A9jYmcHpZA== +"@typescript-eslint/parser@^5.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.36.2.tgz#3ddf323d3ac85a25295a55fcb9c7a49ab4680ddd" + integrity sha512-qS/Kb0yzy8sR0idFspI9Z6+t7mqk/oRjnAYfewG+VN73opAUvmYL3oPIMmgOX6CnQS6gmVIXGshlb5RY/R22pA== dependencies: - "@typescript-eslint/scope-manager" "5.33.1" - "@typescript-eslint/types" "5.33.1" - "@typescript-eslint/typescript-estree" "5.33.1" + "@typescript-eslint/scope-manager" "5.36.2" + "@typescript-eslint/types" "5.36.2" + "@typescript-eslint/typescript-estree" "5.36.2" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.33.1": - version "5.33.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.33.1.tgz#8d31553e1b874210018ca069b3d192c6d23bc493" - integrity sha512-8ibcZSqy4c5m69QpzJn8XQq9NnqAToC8OdH/W6IXPXv83vRyEDPYLdjAlUx8h/rbusq6MkW4YdQzURGOqsn3CA== +"@typescript-eslint/scope-manager@5.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.36.2.tgz#a75eb588a3879ae659514780831370642505d1cd" + integrity sha512-cNNP51L8SkIFSfce8B1NSUBTJTu2Ts4nWeWbFrdaqjmn9yKrAaJUBHkyTZc0cL06OFHpb+JZq5AUHROS398Orw== dependencies: - "@typescript-eslint/types" "5.33.1" - "@typescript-eslint/visitor-keys" "5.33.1" + "@typescript-eslint/types" "5.36.2" + "@typescript-eslint/visitor-keys" "5.36.2" -"@typescript-eslint/type-utils@5.33.1": - version "5.33.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.33.1.tgz#1a14e94650a0ae39f6e3b77478baff002cec4367" - integrity sha512-X3pGsJsD8OiqhNa5fim41YtlnyiWMF/eKsEZGsHID2HcDqeSC5yr/uLOeph8rNF2/utwuI0IQoAK3fpoxcLl2g== +"@typescript-eslint/type-utils@5.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.36.2.tgz#752373f4babf05e993adf2cd543a763632826391" + integrity sha512-rPQtS5rfijUWLouhy6UmyNquKDPhQjKsaKH0WnY6hl/07lasj8gPaH2UD8xWkePn6SC+jW2i9c2DZVDnL+Dokw== dependencies: - "@typescript-eslint/utils" "5.33.1" + "@typescript-eslint/typescript-estree" "5.36.2" + "@typescript-eslint/utils" "5.36.2" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.33.1": - version "5.33.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.33.1.tgz#3faef41793d527a519e19ab2747c12d6f3741ff7" - integrity sha512-7K6MoQPQh6WVEkMrMW5QOA5FO+BOwzHSNd0j3+BlBwd6vtzfZceJ8xJ7Um2XDi/O3umS8/qDX6jdy2i7CijkwQ== +"@typescript-eslint/types@5.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.36.2.tgz#a5066e500ebcfcee36694186ccc57b955c05faf9" + integrity sha512-9OJSvvwuF1L5eS2EQgFUbECb99F0mwq501w0H0EkYULkhFa19Qq7WFbycdw1PexAc929asupbZcgjVIe6OK/XQ== -"@typescript-eslint/typescript-estree@5.33.1": - version "5.33.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.1.tgz#a573bd360790afdcba80844e962d8b2031984f34" - integrity sha512-JOAzJ4pJ+tHzA2pgsWQi4804XisPHOtbvwUyqsuuq8+y5B5GMZs7lI1xDWs6V2d7gE/Ez5bTGojSK12+IIPtXA== +"@typescript-eslint/typescript-estree@5.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.36.2.tgz#0c93418b36c53ba0bc34c61fe9405c4d1d8fe560" + integrity sha512-8fyH+RfbKc0mTspfuEjlfqA4YywcwQK2Amcf6TDOwaRLg7Vwdu4bZzyvBZp4bjt1RRjQ5MDnOZahxMrt2l5v9w== dependencies: - "@typescript-eslint/types" "5.33.1" - "@typescript-eslint/visitor-keys" "5.33.1" + "@typescript-eslint/types" "5.36.2" + "@typescript-eslint/visitor-keys" "5.36.2" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.33.1": - version "5.33.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.33.1.tgz#171725f924fe1fe82bb776522bb85bc034e88575" - integrity sha512-uphZjkMaZ4fE8CR4dU7BquOV6u0doeQAr8n6cQenl/poMaIyJtBu8eys5uk6u5HiDH01Mj5lzbJ5SfeDz7oqMQ== +"@typescript-eslint/utils@5.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.36.2.tgz#b01a76f0ab244404c7aefc340c5015d5ce6da74c" + integrity sha512-uNcopWonEITX96v9pefk9DC1bWMdkweeSsewJ6GeC7L6j2t0SJywisgkr9wUTtXk90fi2Eljj90HSHm3OGdGRg== dependencies: "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.33.1" - "@typescript-eslint/types" "5.33.1" - "@typescript-eslint/typescript-estree" "5.33.1" + "@typescript-eslint/scope-manager" "5.36.2" + "@typescript-eslint/types" "5.36.2" + "@typescript-eslint/typescript-estree" "5.36.2" eslint-scope "^5.1.1" eslint-utils "^3.0.0" -"@typescript-eslint/visitor-keys@5.33.1": - version "5.33.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.1.tgz#0155c7571c8cd08956580b880aea327d5c34a18b" - integrity sha512-nwIxOK8Z2MPWltLKMLOEZwmfBZReqUdbEoHQXeCpa+sRVARe5twpJGHCB4dk9903Yaf0nMAlGbQfaAH92F60eg== +"@typescript-eslint/visitor-keys@5.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.36.2.tgz#2f8f78da0a3bad3320d2ac24965791ac39dace5a" + integrity sha512-BtRvSR6dEdrNt7Net2/XDjbYKU5Ml6GqJgVfXT0CxTCJlnIqK7rAGreuWKMT2t8cFUT2Msv5oxw0GMRD7T5J7A== dependencies: - "@typescript-eslint/types" "5.33.1" + "@typescript-eslint/types" "5.36.2" eslint-visitor-keys "^3.3.0" abbrev@1: @@ -356,6 +372,11 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +async-lock@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.3.2.tgz#56668613f91c1c55432b4db73e65c9ced664e789" + integrity sha512-phnXdS3RP7PPcmP6NWWzWMU0sLTeyvtZCxBPpZdkYE3seGLKSQZs9FrmVO/qwypq98FUtWWUEYxziLkdGk5nnA== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -555,10 +576,10 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dotenv@^16.0.1: - version "16.0.1" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d" - integrity sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ== +dotenv@^16.0.2: + version "16.0.2" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.2.tgz#0b0f8652c016a3858ef795024508cddc4bffc5bf" + integrity sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA== end-of-stream@^1.1.0: version "1.4.4" @@ -610,14 +631,15 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@^8.22.0: - version "8.22.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.22.0.tgz#78fcb044196dfa7eef30a9d65944f6f980402c48" - integrity sha512-ci4t0sz6vSRKdmkOGmprBo6fmI4PrphDFMy5JEq/fNS0gQkJM3rLmrqcp8ipMcdobH3KtUP40KniAE9W19S4wA== +eslint@^8.23.0: + version "8.23.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.23.0.tgz#a184918d288820179c6041bb3ddcc99ce6eea040" + integrity sha512-pBG/XOn0MsJcKcTRLr27S5HpzQo4kLr+HjLQIyK4EiCsijDl/TB+h5uEuJU6bQ8Edvwz1XWOjpaP2qgnXGpTcA== dependencies: - "@eslint/eslintrc" "^1.3.0" + "@eslint/eslintrc" "^1.3.1" "@humanwhocodes/config-array" "^0.10.4" "@humanwhocodes/gitignore-to-minimatch" "^1.0.2" + "@humanwhocodes/module-importer" "^1.0.1" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" @@ -627,7 +649,7 @@ eslint@^8.22.0: eslint-scope "^7.1.1" eslint-utils "^3.0.0" eslint-visitor-keys "^3.3.0" - espree "^9.3.3" + espree "^9.4.0" esquery "^1.4.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -653,12 +675,11 @@ eslint@^8.22.0: strip-ansi "^6.0.1" strip-json-comments "^3.1.0" text-table "^0.2.0" - v8-compile-cache "^2.0.3" -espree@^9.3.2, espree@^9.3.3: - version "9.3.3" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.3.tgz#2dd37c4162bb05f433ad3c1a52ddf8a49dc08e9d" - integrity sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng== +espree@^9.4.0: + version "9.4.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.4.0.tgz#cd4bc3d6e9336c433265fc0aa016fc1aaf182f8a" + integrity sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw== dependencies: acorn "^8.8.0" acorn-jsx "^5.3.2" @@ -766,10 +787,10 @@ follow-redirects@^1.14.9: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== -form-data-encoder@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-2.1.0.tgz#ee9afc735186a2c897005431c13b624cede616da" - integrity sha512-njK60LnfhfDWy+AEUIf9ZQNRAcmXCdDfiNOm2emuPtzwh7U9k/mo9F3S54aPiaZ3vhqUjikVLfcPg2KuBddskQ== +form-data-encoder@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-2.1.2.tgz#5996b7c236e8c418d08316055a2235226c5e4061" + integrity sha512-FCaIOVTRA9E0siY6FeXid7D5yrCqpsErplUkE2a1BEiKj1BE9z6FbKB4ntDTwC4NVLie9p+4E9nX4mWwEOT05A== form-data@^4.0.0: version "4.0.0" @@ -780,13 +801,22 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -formdata-node@^4.3.2: - version "4.4.0" - resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.4.0.tgz#7dda5b9c2959c8c312fd69ef2b10a9c7ea40ca29" - integrity sha512-Q1LZsZggMqcmiAv1VIf4qSo1jxROFYUtuwNbJxman4UurKp3kdcDhtIK2+rKKqa0Xgqlga0qea3s+qH4I4v5kw== +formdata-node@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-5.0.0.tgz#7b4d23f8d823b88d29130366f33ff9ac58dfa7fe" + integrity sha512-zrGsVVS56jJo+htsVv7ffXuzie91a2NrU1cPamvtPaSyRX++SH+4KXlGoOt+ncgDJ4bFA2SAQ+QGA+p4l1vciw== dependencies: node-domexception "1.0.0" - web-streams-polyfill "4.0.0-beta.2" + web-streams-polyfill "4.0.0-beta.3" + +fs-extra@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" + integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" fs.realpath@^1.0.0: version "1.0.0" @@ -874,24 +904,28 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -got@^12.1.0: - version "12.3.1" - resolved "https://registry.yarnpkg.com/got/-/got-12.3.1.tgz#79d6ebc0cb8358c424165698ddb828be56e74684" - integrity sha512-tS6+JMhBh4iXMSXF6KkIsRxmloPln31QHDlcb6Ec3bzxjjFJFr/8aXdpyuLmVc9I4i2HyBHYw1QU5K1ruUdpkw== +got@^12.3.1: + version "12.4.1" + resolved "https://registry.yarnpkg.com/got/-/got-12.4.1.tgz#8598311b42591dfd2ed3ca4cdb9a591e2769a0bd" + integrity sha512-Sz1ojLt4zGNkcftIyJKnulZT/yEDvifhUjccHA8QzOuTgPs/+njXYNMFE3jR4/2OODQSSbH8SdnoLCkbh41ieA== dependencies: "@sindresorhus/is" "^5.2.0" "@szmarczak/http-timer" "^5.0.1" "@types/cacheable-request" "^6.0.2" - "@types/responselike" "^1.0.0" cacheable-lookup "^6.0.4" cacheable-request "^7.0.2" decompress-response "^6.0.0" - form-data-encoder "^2.0.1" + form-data-encoder "^2.1.0" get-stream "^6.0.1" http2-wrapper "^2.1.10" lowercase-keys "^3.0.0" p-cancelable "^3.0.0" - responselike "^2.0.0" + responselike "^3.0.0" + +graceful-fs@^4.1.2, graceful-fs@^4.1.6: + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== grapheme-splitter@^1.0.4: version "1.0.4" @@ -1020,6 +1054,22 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + optionalDependencies: + graceful-fs "^4.1.6" + +keyv-file@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/keyv-file/-/keyv-file-0.2.0.tgz#3442b07a00c1d7bd0242f4a91bcf498afbd6ea6a" + integrity sha512-zUQ11eZRmilEUpV1gJSj8mBAHjyXpleQo1iCS0khb+GFRhiPfwavWgn4eDUKNlOyMZzmExnISl8HE1hNbim0gw== + dependencies: + debug "^4.1.1" + fs-extra "^4.0.1" + tslib "^1.9.3" + keyv@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.4.tgz#f040b236ea2b06ed15ed86fbef8407e1a1c8e376" @@ -1027,6 +1077,13 @@ keyv@^4.0.0: dependencies: json-buffer "3.0.1" +keyv@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.0.tgz#dbce9ade79610b6e641a9a65f2f6499ba06b9bc6" + integrity sha512-2YvuMsA+jnFGtBareKqgANOEKe1mk3HKiXu2fRmAfyxG0MJAywNhi5ttWA3PMjl4NmpyjZNbFifR2vNjW1znfA== + dependencies: + json-buffer "3.0.1" + levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -1133,6 +1190,13 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +node-cron@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/node-cron/-/node-cron-3.0.2.tgz#bb0681342bd2dfb568f28e464031280e7f06bd01" + integrity sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ== + dependencies: + uuid "8.3.2" + node-domexception@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" @@ -1281,6 +1345,11 @@ qs@^6.11.0: dependencies: side-channel "^1.0.4" +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -1303,6 +1372,11 @@ regexpp@^3.2.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + resolve-alpn@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" @@ -1320,6 +1394,13 @@ responselike@^2.0.0: dependencies: lowercase-keys "^2.0.0" +responselike@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-3.0.0.tgz#20decb6c298aff0dbee1c355ca95461d42823626" + integrity sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg== + dependencies: + lowercase-keys "^3.0.0" + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -1439,14 +1520,15 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" -tough-cookie@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" - integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== +tough-cookie@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" + integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ== dependencies: psl "^1.1.33" punycode "^2.1.1" - universalify "^0.1.2" + universalify "^0.2.0" + url-parse "^1.5.3" ts-node@^10.9.1: version "10.9.1" @@ -1467,7 +1549,7 @@ ts-node@^10.9.1: v8-compile-cache-lib "^3.0.1" yn "3.1.1" -tslib@^1.8.1: +tslib@^1.8.1, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -1491,21 +1573,26 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -typescript@^4.7.4: - version "4.7.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" - integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== +typescript@^4.8.2: + version "4.8.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.2.tgz#e3b33d5ccfb5914e4eeab6699cf208adee3fd790" + integrity sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw== undefsafe@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== -universalify@^0.1.2: +universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== +universalify@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" + integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -1513,20 +1600,28 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +url-parse@^1.5.3: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + +uuid@8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== -v8-compile-cache@^2.0.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" - integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== - -web-streams-polyfill@4.0.0-beta.2: - version "4.0.0-beta.2" - resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.2.tgz#a534d1a8a4bbbb8d2bba07eb27722fde8ee8d077" - integrity sha512-UHhhnoe2M40uh2r0KVdJTN7qjFytm6o0Yp3VcjwV3bfo6rz8uqvxNoE5yNmGF0y3eFfXaFeb6M09MDSwwLmq4w== +web-streams-polyfill@4.0.0-beta.3: + version "4.0.0-beta.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38" + integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug== which@^2.0.1: version "2.0.2"