rewrite everything

This commit is contained in:
nezu 2022-09-08 01:47:17 +02:00
parent 7fb033b8ea
commit 80ad78ac87
16 changed files with 926 additions and 475 deletions

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

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

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

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

@ -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=<your windscribe username>
- WINDSCRIBE_PASSWORD=<your windscribe password>
- DELUGE_URL=<url of your Deluge Web UI>
- DELUGE_PASSWORD=<password for the Deluge Web UI>
```
## 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=<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
TODO

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

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"