repo: init
This commit is contained in:
178
.gitignore
vendored
Normal file
178
.gitignore
vendored
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||||
|
|
||||||
|
# Castorsrm
|
||||||
|
config.json
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
|
||||||
|
logs
|
||||||
|
_.log
|
||||||
|
npm-debug.log_
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Caches
|
||||||
|
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
|
||||||
|
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
|
||||||
|
pids
|
||||||
|
_.pid
|
||||||
|
_.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
|
||||||
|
.temp
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
# IntelliJ based IDEs
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Finder (MacOS) folder config
|
||||||
|
.DS_Store
|
||||||
31
.swcrc
Normal file
31
.swcrc
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://swc.rs/schema.json",
|
||||||
|
"jsc": {
|
||||||
|
"parser": {
|
||||||
|
"syntax": "typescript",
|
||||||
|
"jsx": false,
|
||||||
|
"dynamicImport": false,
|
||||||
|
"privateMethod": false,
|
||||||
|
"functionBind": false,
|
||||||
|
"exportDefaultFrom": false,
|
||||||
|
"exportNamespaceFrom": false,
|
||||||
|
"decorators": false,
|
||||||
|
"decoratorsBeforeExport": false,
|
||||||
|
"topLevelAwait": true,
|
||||||
|
"importMeta": false
|
||||||
|
},
|
||||||
|
"minify": {
|
||||||
|
"compress": {
|
||||||
|
"unused": true
|
||||||
|
},
|
||||||
|
"mangle": true
|
||||||
|
},
|
||||||
|
"transform": null,
|
||||||
|
"target": "esnext",
|
||||||
|
"loose": false,
|
||||||
|
"externalHelpers": false,
|
||||||
|
// Requires v1.2.50 or upper and requires target to be es2016 or upper.
|
||||||
|
"keepClassNames": false
|
||||||
|
},
|
||||||
|
"minify": true
|
||||||
|
}
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 tretrauit
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
24
README.md
Normal file
24
README.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Castorsrm
|
||||||
|
|
||||||
|
Rice shirt rice money :(
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
This project uses Bun for dependency management and Node.js (or Bun on UNIX platforms) for the app execution.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.tretrauit.me/tretrauit/castorsrm
|
||||||
|
cd castorsrm
|
||||||
|
bun install
|
||||||
|
bun run dev
|
||||||
|
# or execute "bun run dev-bun" to start the application with Bun instead
|
||||||
|
# Note that Bun will not work correctly with Playwright on Windows.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
On the first launch, it'll generate a `config.json` file. You can change the application settings by editing that file.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[MIT](./LICENSE)
|
||||||
31
package.json
Normal file
31
package.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "castorsrm",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"module": "src/index.ts",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "node --env-file=.env --import @swc-node/register/esm-register ./src/index.ts",
|
||||||
|
"dev-bun": "bun ./src/index.ts",
|
||||||
|
"start": "node ./dist/index.js",
|
||||||
|
"start-bun": "bun ./dist/index.js",
|
||||||
|
"build": "swc ./src -d dist"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@swc-node/register": "^1.10.10",
|
||||||
|
"@swc/cli": "^0.6.0",
|
||||||
|
"@swc/core": "^1.11.20",
|
||||||
|
"@types/bun": "latest",
|
||||||
|
"pino-pretty": "^13.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"patchright": "^1.51.3",
|
||||||
|
"pino": "^9.6.0",
|
||||||
|
"playwright": "^1.51.1"
|
||||||
|
},
|
||||||
|
"trustedDependencies": [
|
||||||
|
"@swc/core"
|
||||||
|
]
|
||||||
|
}
|
||||||
46
src/config.ts
Normal file
46
src/config.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
class Config {
|
||||||
|
playwright: {
|
||||||
|
browser: string;
|
||||||
|
headless: boolean;
|
||||||
|
cdp: string;
|
||||||
|
url: string;
|
||||||
|
} = {
|
||||||
|
browser: "chromium",
|
||||||
|
headless: true,
|
||||||
|
cdp: "ws://127.0.0.1:9222",
|
||||||
|
url: "https://www.twitch.tv/",
|
||||||
|
};
|
||||||
|
proxy: {
|
||||||
|
mode: string,
|
||||||
|
count: number,
|
||||||
|
} = {
|
||||||
|
mode: "reflect4",
|
||||||
|
count: 0,
|
||||||
|
};
|
||||||
|
logger: {
|
||||||
|
level: string,
|
||||||
|
} = {
|
||||||
|
level: "info",
|
||||||
|
};
|
||||||
|
static fromJSON(json: string) {
|
||||||
|
const obj = JSON.parse(json);
|
||||||
|
const config = new Config();
|
||||||
|
for (const [k, v] of Object.entries(obj)) {
|
||||||
|
if (k in config) {
|
||||||
|
// @ts-ignore
|
||||||
|
config[k] = v;
|
||||||
|
} else {
|
||||||
|
console.warn(`Unknown key '${k}' in configuration file. Ignoring it.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
toJSON() {
|
||||||
|
const obj: any = {};
|
||||||
|
for (const [k, v] of Object.entries(this)) {
|
||||||
|
obj[k] = v;
|
||||||
|
}
|
||||||
|
return JSON.stringify(obj, null, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default Config;
|
||||||
12
src/constants.ts
Normal file
12
src/constants.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
const REFLECT4_SERVERS = [
|
||||||
|
"https://www.blockaway.net",
|
||||||
|
"https://www.croxyproxy.com",
|
||||||
|
"https://www.croxyproxy.rocks",
|
||||||
|
"https://www.croxy.network",
|
||||||
|
"https://www.croxy.org",
|
||||||
|
"https://www.youtubeunblocked.live",
|
||||||
|
"https://www.croxyproxy.net",
|
||||||
|
"https://proxyium.com"
|
||||||
|
];
|
||||||
|
|
||||||
|
export { REFLECT4_SERVERS };
|
||||||
79
src/index.ts
Normal file
79
src/index.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { chromium, type Browser, type LaunchOptions } from "patchright";
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import * as reflect4 from "./proxy/reflect4";
|
||||||
|
import logger from "./logger";
|
||||||
|
import Config from "./config";
|
||||||
|
|
||||||
|
const version = "0.1.0";
|
||||||
|
|
||||||
|
logger.info(`Castorsrm v${version} - https://github.com/teppyboy/castorsrm`)
|
||||||
|
logger.warn("This software is provided by the author as is, without any warranty. Use at your own risk.");
|
||||||
|
|
||||||
|
let config = new Config();
|
||||||
|
if (fs.existsSync("config.json")) {
|
||||||
|
logger.info("Reading configuration from 'config.json'...");
|
||||||
|
const text = await fs.promises.readFile("config.json", "utf-8");
|
||||||
|
config = Config.fromJSON(text);
|
||||||
|
} else {
|
||||||
|
logger.info("No configuration file found. Using the default configuration.");
|
||||||
|
await fs.promises.writeFile("config.json", config.toJSON());
|
||||||
|
logger.info("Default configuration file 'config.json' created.");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.level = process.env.LOG_LEVEL || config.logger.level;
|
||||||
|
logger.info(`Logger level set to '${logger.level}'`);
|
||||||
|
|
||||||
|
let browser: Browser;
|
||||||
|
let launchOptions: LaunchOptions = {
|
||||||
|
headless: config.playwright.headless,
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Launching browser...");
|
||||||
|
logger.debug(`Launch options: ${JSON.stringify(launchOptions)}`);
|
||||||
|
|
||||||
|
switch (config.playwright.browser) {
|
||||||
|
case "chromium":
|
||||||
|
logger.info("Using Chromium as the browser provider.");
|
||||||
|
logger.warn("Chromium is not supported by Twitch. Use at your own risk.");
|
||||||
|
browser = await chromium.launch({ ...launchOptions });
|
||||||
|
break;
|
||||||
|
case "chrome":
|
||||||
|
logger.info("Using Google Chrome as the browser provider.");
|
||||||
|
browser = await chromium.launch({ ...launchOptions, channel: "chrome" });
|
||||||
|
break;
|
||||||
|
case "cdp":
|
||||||
|
logger.info("Using Chrome DevTools Protocol (CDP) for browser connection.");
|
||||||
|
browser = await chromium.connectOverCDP(config.playwright.cdp);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger.warn(`Unsupported browser channel: '${config.playwright.browser}'`);
|
||||||
|
logger.warn("Castorsrm will try to launch the browser anyway, but it may not work as expected.");
|
||||||
|
browser = await chromium.launch({ ...launchOptions, channel: config.playwright.browser });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Browser launched successfully, spawning proxies");
|
||||||
|
if (config.proxy.mode === "reflect4") {
|
||||||
|
const context = await browser.newContext();
|
||||||
|
const tasks: Array<Promise<void>> = [];
|
||||||
|
logger.info(`Spawning ${config.proxy.count} proxies...`);
|
||||||
|
for (let i = 0; i < config.proxy.count; i++) {
|
||||||
|
logger.debug(`Spawning proxy ${i + 1}...`);
|
||||||
|
tasks.push(reflect4.spawn(context, config.playwright.url));
|
||||||
|
}
|
||||||
|
await Promise.all(tasks);
|
||||||
|
logger.info("All proxies spawned successfully.");
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on("SIGINT", () => {
|
||||||
|
logger.info("Received SIGINT. Closing browser...");
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on("SIGINT", async () => {
|
||||||
|
logger.info("Received SIGINT. Closing browser...");
|
||||||
|
for (const context of browser.contexts()) {
|
||||||
|
await context.close();
|
||||||
|
}
|
||||||
|
await browser.close();
|
||||||
|
process.exit();
|
||||||
|
});
|
||||||
15
src/logger.ts
Normal file
15
src/logger.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { type Logger, pino } from "pino";
|
||||||
|
import pretty from "pino-pretty";
|
||||||
|
|
||||||
|
const stream = pretty({
|
||||||
|
colorize: true,
|
||||||
|
translateTime: "SYS:standard",
|
||||||
|
ignore: "hostname,pid",
|
||||||
|
});
|
||||||
|
|
||||||
|
const logger: Logger<never> = pino({
|
||||||
|
name: "castorsrm",
|
||||||
|
level: "info",
|
||||||
|
}, stream);
|
||||||
|
|
||||||
|
export default logger;
|
||||||
43
src/proxy/reflect4.ts
Normal file
43
src/proxy/reflect4.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { type BrowserContext, type Locator } from "patchright";
|
||||||
|
import logger from "../logger";
|
||||||
|
import * as twitch from "../website/twitch";
|
||||||
|
import * as constants from "../constants";
|
||||||
|
|
||||||
|
async function spawn(context: BrowserContext, targetUrl: string) {
|
||||||
|
const spawnId = btoa(Math.random().toString()).substring(4,10);
|
||||||
|
const server = constants.REFLECT4_SERVERS[Math.floor(Math.random() * constants.REFLECT4_SERVERS.length)];
|
||||||
|
logger.debug(`[${spawnId}] Using reflect4 server: ${server}`);
|
||||||
|
const page = await context.newPage();
|
||||||
|
await page.goto(server);
|
||||||
|
let targetInput: Locator | null = null;
|
||||||
|
const allInput = await page.locator("input").all();
|
||||||
|
for (const input of allInput) {
|
||||||
|
const placeholder = await input.getAttribute("placeholder");
|
||||||
|
if (placeholder?.includes("URL")) {
|
||||||
|
targetInput = input;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!targetInput) {
|
||||||
|
logger.error(`[${spawnId}] Failed to find input field for URL input`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await targetInput.fill(targetUrl);
|
||||||
|
await targetInput.press("Enter");
|
||||||
|
// Keep-alive the page open for 5 minutes then refresh
|
||||||
|
if (targetUrl.startsWith("https://www.twitch.tv/")) {
|
||||||
|
twitch.keepAlive(page, spawnId);
|
||||||
|
} else {
|
||||||
|
logger.warn(`[${spawnId}] Unsupported URL: ${targetUrl}`);
|
||||||
|
logger.warn(`[${spawnId}] Will try to keep alive, but no guarantees`);
|
||||||
|
while (true) {
|
||||||
|
await page.waitForTimeout(5 * 60 * 1000); // 5 minutes
|
||||||
|
await page.reload();
|
||||||
|
logger.debug(`[${spawnId}] Reloaded page`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await page.close();
|
||||||
|
logger.info(`[${spawnId}] Proxy with the server ${server} closed`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { spawn };
|
||||||
28
src/website/twitch.ts
Normal file
28
src/website/twitch.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import type { Page } from "patchright";
|
||||||
|
import logger from "../logger";
|
||||||
|
|
||||||
|
async function keepAlive(page: Page, spawnId: string = "unknown") {
|
||||||
|
try {
|
||||||
|
let waitTime = 0;
|
||||||
|
while (true) {
|
||||||
|
// Wait for a random time between 1 and 11 seconds
|
||||||
|
const timeout = 1000 + Math.floor(Math.random() * 60 * 1000) % 10000;
|
||||||
|
logger.debug(`[${spawnId}] Waiting for ${timeout / 1000} seconds...`);
|
||||||
|
await page.waitForTimeout(timeout);
|
||||||
|
waitTime += timeout;
|
||||||
|
if ((await page.locator(".ScCoreButton-sc-ocjdkq-0.ggPgVz").all()).length > 0) {
|
||||||
|
logger.debug(`[${spawnId}] Player encountered an error, refreshing the page...`);
|
||||||
|
await page.reload({timeout: 0, waitUntil: "domcontentloaded"});
|
||||||
|
}
|
||||||
|
if (waitTime > 5 * 60 * 1000) {
|
||||||
|
logger.debug(`[${spawnId}] Waited for more than 5 minutes, refreshing the page...`);
|
||||||
|
await page.reload({timeout: 0, waitUntil: "domcontentloaded"});
|
||||||
|
waitTime = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(`[${spawnId}] Error while keeping the page alive: ${e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { keepAlive };
|
||||||
28
tsconfig.json
Normal file
28
tsconfig.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
// Enable latest features
|
||||||
|
"lib": ["ESNext", "DOM"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
|
||||||
|
// Bundler mode
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
// Best practices
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|
||||||
|
// Some stricter flags (disabled by default)
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noPropertyAccessFromIndexSignature": false,
|
||||||
|
"noImplicitAny": false
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user