Merge pull request #4 from dumbasPL/rework

v2
This commit is contained in:
nezu 2022-09-10 01:51:52 +02:00 committed by GitHub
commit 50febacf59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 975 additions and 458 deletions

View file

@ -1,6 +1,8 @@
.vscode
.env
.git
.gitignore
.github
node_modules
dist
Dockerfile

45
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,45 @@
name: Docker build
on:
push:
pull_request:
jobs:
docker-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: docker/setup-buildx-action@v2
- uses: docker/build-push-action@v3
with:
context: .
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

View file

@ -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

View file

@ -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
@ -44,9 +39,9 @@ jobs:
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Build and push
uses: docker/build-push-action@v2
- uses: docker/build-push-action@v2
with:
context: .
push: true

3
.gitignore vendored
View file

@ -8,4 +8,5 @@ dist/
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
!.yarn/versions
cache/

View file

@ -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

View file

@ -8,36 +8,74 @@ This project was designed to work along side containers like [kabe0/deluge-winds
It will not help you configure windscribe to use a vpn!
It will only update the port that deluge listens on to the same port that's configured on windscribe website.
**I strongly advise against using a "restart on error" policy since windscribe will temporary block your ip address after a few failed login attempts.**
# Configuration
Configuration is done using environment variables
| Variable | Description | Required | Default |
| :-: | :-: | :-: | :-: |
| WINDSCRIBE_USERNAME | username you use to login at windscribe.com/login | YES | |
| WINDSCRIBE_PASSWORD | password you use to login at windscribe.com/login | YES | |
| DELUGE_URL | The base URL for the deluge web UI | YES | |
| DELUGE_PASSWORD | The password for the deluge web UI | YES | |
| CRON_SCHEDULE | An extra cron schedule used to periodically validate and update the port if needed. Disabled if left empty | NO | |
| DELUGE_HOST_ID | The internal host id to connect to in the deluge web UI. It will be printed in stdout after the first successful connection to deluge | Only if you have more then one connection configured in connection manager | If you have multiple configured in deluge web ui the app will print them out and crash. If you have only one that one will be used and you don't need to specify it explicitly |
| WINDSCRIBE_RETRY_DELAY | how long to wait (in milliseconds) before retrying after a windscribe error. For example a failed login. | NO | 3600000 (1 hour) |
| WINDSCRIBE_EXTRA_DELAY | how long to wait (in milliseconds) after the ephemeral port expires before trying to create a new one. | NO | 60000 (1 minute) |
| DELUGE_RETRY_DELAY | how long to wait (in milliseconds) before retrying after a deluge error. For example a failed login. | NO | 300000 (5 minutes) |
| CACHE_DIR | A directory where to store cached data like windscribe session cookies | YES | `/cache` in the docker container and `./cache` everywhere else |
# Running
## Using docker (and docker compose in this example)
```yml
version: '3'
```yaml
version: '3.8'
services:
deluge-windscribe-ephemeral-port:
image: dumbaspl/deluge-windscribe-ephemeral-port
image: dumbaspl/deluge-windscribe-ephemeral-port:2
restart: unless-stopped
volumes:
- windscribe-cache:/cache
environment:
- WINDSCRIBE_USERNAME=<your windscribe username>
- WINDSCRIBE_PASSWORD=<your windscribe password>
- DELUGE_URL=<url of your Deluge Web UI>
- DELUGE_PASSWORD=<password for the Deluge Web UI>
# optional
# - DELUGE_HOST_ID=
# - DELUGE_RETRY_DELAY=300000
# - WINDSCRIBE_RETRY_DELAY=3600000
# - WINDSCRIBE_EXTRA_DELAY=60000
# - CRON_SCHEDULE=
# - CACHE_DIR=/cache
volumes:
windscribe-cache:
```
## 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
```
**This project requires Node.js version 16 or newer**
**This project uses [yarn](https://classic.yarnpkg.com/) to manage dependencies, make sure you have it installed first.**
1. clone this repository
2. Install dependencies by running `yarn install`
3. Create a `.env` file in the root of the project with the necessary configuration
```shell
WINDSCRIBE_USERNAME=<your windscribe username>
WINDSCRIBE_PASSWORD=<your windscribe password>
DELUGE_URL=<url of your Deluge Web UI>
DELUGE_PASSWORD=<password for the Deluge Web UI>
```
3. Start using `yarn start`
Tip: you can use tools like [pm2](https://www.npmjs.com/package/pm2) to manage nodejs applications
# optional
# DELUGE_HOST_ID=
# DELUGE_RETRY_DELAY=300000
# WINDSCRIBE_RETRY_DELAY=3600000
# WINDSCRIBE_EXTRA_DELAY=60000
# CRON_SCHEDULE=
# CACHE_DIR=./cache
```
4. Build and start using `yarn install`
Tip: you can use tools like pm2 to manage nodejs applications

View file

@ -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"

View file

@ -1,34 +1,41 @@
{
"name": "project",
"version": "1.0.0",
"version": "2.0.0",
"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"
}

120
src/DelugeClient.ts Normal file
View file

@ -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<void> {
// 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');
}
}

272
src/WindscribeClient.ts Normal file
View file

@ -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<string>;
constructor(
private username: string,
private password: string,
cache?: Store<any>,
) {
this.cache = new Keyv({
store: cache,
namespace: 'windscribe',
});
}
async updatePort(): Promise<WindscribePort> {
// 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<WindscribePort | null> {
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<string> {
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<Cookie> {
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<string>;
const errorMessage = /<div class="content_message error">.*>(.*)<\/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<CsrfInfo> {
try {
const sessionCookie = await this.getSession(forceLogin);
// get page
const res = await axios.get<string>('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<PortForwardingInfo> {
try {
const sessionCookie = await this.getSession();
// load sub page
const res = await axios.get<string>('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(/<span>(?<port>\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<void> {
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<PortForwardingInfo> {
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;
}
}
}

112
src/config.ts Normal file
View file

@ -0,0 +1,112 @@
interface ConfigTemplate<T extends string | number> {
envVariableName: string,
type: T extends string ? typeof String : typeof Number,
}
interface ConfigTemplateRequiredEntry<T extends string | number> extends ConfigTemplate<T> {
required: true,
}
interface ConfigTemplateOptionalEntry<T extends string | number> extends ConfigTemplate<T> {
required: false,
default?: string,
}
const configTemplate = {
delugeUrl: {
envVariableName: 'DELUGE_URL',
required: true,
type: String,
} as ConfigTemplateRequiredEntry<string>,
delugePassword: {
envVariableName: 'DELUGE_PASSWORD',
required: true,
type: String,
} as ConfigTemplateRequiredEntry<string>,
delugeHostId: {
envVariableName: 'DELUGE_HOST_ID',
required: false,
type: String,
} as ConfigTemplateOptionalEntry<string>,
delugeRetryDelay: {
envVariableName: 'DELUGE_RETRY_DELAY',
required: false,
default: `${5 * 60 * 1000}`, // 5 minutes
type: Number,
} as ConfigTemplateOptionalEntry<number>,
windscribeUsername: {
envVariableName: 'WINDSCRIBE_USERNAME',
required: true,
type: String,
} as ConfigTemplateRequiredEntry<string>,
windscribePassword: {
envVariableName: 'WINDSCRIBE_PASSWORD',
required: true,
type: String,
} as ConfigTemplateRequiredEntry<string>,
windscribeRetryDelay: {
envVariableName: 'WINDSCRIBE_RETRY_DELAY',
required: false,
default: `${60 * 60 * 1000}`, // one hour
type: Number,
} as ConfigTemplateOptionalEntry<number>,
windscribeExtraDelay: {
envVariableName: 'WINDSCRIBE_EXTRA_DELAY',
required: false,
default: `${60 * 1000}`, // one minute
type: Number,
} as ConfigTemplateOptionalEntry<number>,
cronSchedule: {
envVariableName: 'CRON_SCHEDULE',
required: false,
type: String,
} as ConfigTemplateOptionalEntry<string>,
cacheDir: {
envVariableName: 'CACHE_DIR',
required: false,
default: './cache',
type: String,
} as ConfigTemplateOptionalEntry<string>,
};
type entryType =
ConfigTemplateRequiredEntry<string> |
ConfigTemplateOptionalEntry<string> |
ConfigTemplateRequiredEntry<number> |
ConfigTemplateOptionalEntry<number>;
type configTemplateType = typeof configTemplate;
type Config =
{[key in keyof configTemplateType as configTemplateType[key] extends ConfigTemplateRequiredEntry<string> ? key : never]: string} &
{[key in keyof configTemplateType as configTemplateType[key] extends ConfigTemplateOptionalEntry<string> ? key : never]?: string} &
{[key in keyof configTemplateType as configTemplateType[key] extends ConfigTemplateOptionalEntry<number> ? key : never]?: number} &
{[key in keyof configTemplateType as configTemplateType[key] extends ConfigTemplateOptionalEntry<number> ? 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);
}

View file

@ -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}`);
}
}

View file

@ -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');

View file

@ -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<string> {
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<string>;
const errorMessage = /<div class="content_message error">.*>(.*)<\/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<CsrfInfo> {
try {
// get page
const res = await axios.get<string>('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<PortForwardingInfo> {
try {
// load sub page
const res = await axios.get<string>('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(/<span>(?<port>\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<void> {
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<PortForwardingInfo> {
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;
}
}

View file

@ -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
},
}

341
yarn.lock
View file

@ -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"