@shriyanss/js-recon 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,325 @@
1
+ import chalk from "chalk";
2
+ import {
3
+ APIGatewayClient,
4
+ CreateRestApiCommand,
5
+ DeleteRestApiCommand,
6
+ } from "@aws-sdk/client-api-gateway";
7
+ import fs from "fs";
8
+ import checkFeasibility from "./checkFeasibility.js";
9
+
10
+ // read the docs for all the methods for api gateway at https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/api-gateway/
11
+ // for the rate limits, refer to https://docs.aws.amazon.com/apigateway/latest/developerguide/limits.html
12
+
13
+ const randomRegion = () => {
14
+ const apiGatewayRegions = [
15
+ "us-east-2", // US East (Ohio)
16
+ "us-east-1", // US East (N. Virginia)
17
+ "us-west-1", // US West (N. California)
18
+ "us-west-2", // US West (Oregon)
19
+ "af-south-1", // Africa (Cape Town)
20
+ "ap-east-1", // Asia Pacific (Hong Kong)
21
+ "ap-south-2", // Asia Pacific (Hyderabad)
22
+ "ap-southeast-3", // Asia Pacific (Jakarta)
23
+ "ap-southeast-5", // Asia Pacific (Malaysia)
24
+ "ap-southeast-4", // Asia Pacific (Melbourne)
25
+ "ap-south-1", // Asia Pacific (Mumbai)
26
+ "ap-northeast-3", // Asia Pacific (Osaka)
27
+ "ap-northeast-2", // Asia Pacific (Seoul)
28
+ "ap-southeast-1", // Asia Pacific (Singapore)
29
+ "ap-southeast-2", // Asia Pacific (Sydney)
30
+ "ap-east-2", // Asia Pacific (Taipei)
31
+ "ap-southeast-7", // Asia Pacific (Thailand)
32
+ "ap-northeast-1", // Asia Pacific (Tokyo)
33
+ "ca-central-1", // Canada (Central)
34
+ "ca-west-1", // Canada West (Calgary)
35
+ "eu-central-1", // Europe (Frankfurt)
36
+ "eu-west-1", // Europe (Ireland)
37
+ "eu-west-2", // Europe (London)
38
+ "eu-south-1", // Europe (Milan)
39
+ "eu-west-3", // Europe (Paris)
40
+ "eu-south-2", // Europe (Spain)
41
+ "eu-north-1", // Europe (Stockholm)
42
+ "eu-central-2", // Europe (Zurich)
43
+ "il-central-1", // Israel (Tel Aviv)
44
+ "mx-central-1", // Mexico (Central)
45
+ "me-south-1", // Middle East (Bahrain)
46
+ "me-central-1", // Middle East (UAE)
47
+ "sa-east-1", // South America (São Paulo)
48
+ ];
49
+ return apiGatewayRegions[
50
+ Math.floor(Math.random() * apiGatewayRegions.length)
51
+ ];
52
+ };
53
+
54
+ let aws_access_key;
55
+ let aws_secret_key;
56
+ let region;
57
+ let configFile;
58
+
59
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
60
+
61
+ /**
62
+ * Create a new API Gateway.
63
+ *
64
+ * @async
65
+ * @returns {Promise<void>}
66
+ */
67
+ const createGateway = async () => {
68
+ console.log(chalk.cyan("[i] Creating API Gateway"));
69
+ const client = new APIGatewayClient({
70
+ region,
71
+ credentials: {
72
+ accessKeyId: aws_access_key,
73
+ secretAccessKey: aws_secret_key,
74
+ },
75
+ });
76
+
77
+ const apigw_created_at = Date.now();
78
+ const apigw_name = `js_recon-${apigw_created_at}-${Math.floor(Math.random() * 1000)}`;
79
+ const command = new CreateRestApiCommand({
80
+ name: apigw_name,
81
+ description: `API Gateway for JS Recon created at ${new Intl.DateTimeFormat(
82
+ "en-US",
83
+ {
84
+ year: "numeric",
85
+ month: "long",
86
+ day: "2-digit",
87
+ hour: "2-digit",
88
+ minute: "2-digit",
89
+ second: "2-digit",
90
+ timeZoneName: "short",
91
+ },
92
+ ).format(apigw_created_at)}`,
93
+ endpointConfiguration: {
94
+ ipAddressType: "dualstack",
95
+ types: ["REGIONAL"],
96
+ },
97
+ });
98
+ const response = await client.send(command);
99
+ await sleep(3000);
100
+ console.log(chalk.green(`[✓] Created API Gateway`));
101
+ console.log(chalk.bgGreen("ID:"), chalk.green(response.id));
102
+ console.log(chalk.bgGreen("Name:"), chalk.green(apigw_name));
103
+ console.log(chalk.bgGreen("Region:"), chalk.green(region));
104
+
105
+ // load the config file if any. Else, create a new one
106
+ let config = {};
107
+ try {
108
+ config = JSON.parse(fs.readFileSync(configFile));
109
+ } catch (e) {
110
+ config = {};
111
+ }
112
+
113
+ config[apigw_name] = {
114
+ id: response.id,
115
+ name: apigw_name,
116
+ description: response.description,
117
+ created_at: apigw_created_at,
118
+ region: region,
119
+ access_key: aws_access_key,
120
+ secret_key: aws_secret_key,
121
+ };
122
+
123
+ fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
124
+ console.log(chalk.green(`[✓] Config saved to ${configFile}`));
125
+ };
126
+
127
+ /**
128
+ * Destroy an API Gateway.
129
+ *
130
+ * @async
131
+ * @param {string} id - The ID of the API Gateway to destroy.
132
+ * @returns {Promise<void>}
133
+ */
134
+ const destroyGateway = async (id) => {
135
+ console.log(chalk.cyan("[i] Destroying API Gateway"));
136
+ if (!id) {
137
+ console.log(chalk.red("[!] Please provide an API Gateway ID"));
138
+ return;
139
+ }
140
+ // read the config file
141
+ let config = JSON.parse(fs.readFileSync(configFile));
142
+ // get the name of the api gateway
143
+ let name = Object.keys(config).find((key) => config[key].id === id);
144
+
145
+ console.log(chalk.bgGreen("Name:"), chalk.green(name));
146
+ console.log(chalk.bgGreen("ID:"), chalk.green(id));
147
+ console.log(chalk.bgGreen("Region:"), chalk.green(config[name].region));
148
+ region = config[name].region;
149
+
150
+ const client = new APIGatewayClient({
151
+ region,
152
+ credentials: {
153
+ accessKeyId: aws_access_key,
154
+ secretAccessKey: aws_secret_key,
155
+ },
156
+ });
157
+
158
+ const command = new DeleteRestApiCommand({
159
+ restApiId: id,
160
+ });
161
+ await client.send(command);
162
+
163
+ // remove from the config file
164
+ delete config[name];
165
+ fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
166
+
167
+ await sleep(30000);
168
+
169
+ console.log(chalk.green(`[✓] Destroyed API Gateway: ${id}`));
170
+ };
171
+
172
+ /**
173
+ * Destroy all API Gateways.
174
+ *
175
+ * @async
176
+ * @returns {Promise<void>}
177
+ */
178
+ const destroyAllGateways = async () => {
179
+ console.log(chalk.cyan("[i] Destroying all API Gateways"));
180
+ // read the config file
181
+ let config = JSON.parse(fs.readFileSync(configFile));
182
+
183
+ // destroy all the gateways
184
+ for (const [key, value] of Object.entries(config)) {
185
+ const client = new APIGatewayClient({
186
+ region: value.region,
187
+ credentials: {
188
+ accessKeyId: aws_access_key,
189
+ secretAccessKey: aws_secret_key,
190
+ },
191
+ });
192
+ console.log(
193
+ chalk.cyan(
194
+ `[i] Destroying API Gateway: ${key} : ${value.id} : ${value.region}`,
195
+ ),
196
+ );
197
+
198
+ const command = new DeleteRestApiCommand({
199
+ restApiId: value.id,
200
+ });
201
+ await sleep(30000);
202
+ await client.send(command);
203
+ console.log(
204
+ chalk.green(
205
+ `[✓] Destroyed API Gateway: ${key} : ${value.id} : ${value.region}`,
206
+ ),
207
+ );
208
+ }
209
+
210
+ // nullify the config file
211
+ fs.writeFileSync(configFile, JSON.stringify({}, null, 2));
212
+ console.log(chalk.green("[✓] Destroyed all API Gateways"));
213
+ };
214
+
215
+ /**
216
+ * List all API Gateways.
217
+ *
218
+ * @async
219
+ * @returns {Promise<void>}
220
+ */
221
+ const listGateways = async () => {
222
+ console.log(chalk.cyan("[i] Listing all API Gateways"));
223
+
224
+ // read the config file, and list these
225
+
226
+ // check if the config file exists
227
+ if (!fs.existsSync(configFile)) {
228
+ console.log(chalk.red("[!] Config file does not exist"));
229
+ return;
230
+ }
231
+
232
+ const config = JSON.parse(fs.readFileSync(configFile));
233
+
234
+ // if list is empty
235
+ if (Object.keys(config).length === 0) {
236
+ console.log(chalk.red("[!] No API Gateways found"));
237
+ return;
238
+ }
239
+
240
+ console.log(chalk.green("[✓] List of API Gateways"));
241
+
242
+ for (const [key, value] of Object.entries(config)) {
243
+ console.log(chalk.bgGreen("Name:"), chalk.green(key));
244
+ console.log(chalk.bgGreen("ID:"), chalk.green(value.id));
245
+ console.log(chalk.bgGreen("Region:"), chalk.green(value.region));
246
+ console.log("\n");
247
+ }
248
+ };
249
+
250
+ /**
251
+ * Main function for API Gateway.
252
+ *
253
+ * @async
254
+ * @param {boolean} initInput - Whether to initialize the API Gateway.
255
+ * @param {string} destroyInput - The ID of the API Gateway to destroy.
256
+ * @param {boolean} destroyAllInput - Whether to destroy all API Gateways.
257
+ * @param {boolean} listInput - Whether to list all API Gateways.
258
+ * @param {string} regionInput - The region to use.
259
+ * @param {string} accessKey - The access key to use.
260
+ * @param {string} secretKey - The secret key to use.
261
+ * @param {string} configInput - The config file to use.
262
+ * @returns {Promise<void>}
263
+ */
264
+ const apiGateway = async (
265
+ initInput,
266
+ destroyInput,
267
+ destroyAllInput,
268
+ listInput,
269
+ regionInput,
270
+ accessKey,
271
+ secretKey,
272
+ configInput,
273
+ feasibilityInput,
274
+ feasibilityUrlInput,
275
+ ) => {
276
+ console.log(chalk.cyan("[i] Loading 'API Gateway' module"));
277
+
278
+ // if feasibility is true, check feasibility
279
+ if (feasibilityInput) {
280
+ if (!feasibilityUrlInput) {
281
+ console.log(chalk.red("[!] Please provide a URL to check feasibility of"));
282
+ return;
283
+ }
284
+ await checkFeasibility(feasibilityUrlInput);
285
+ return;
286
+ }
287
+
288
+ // configure the access and secret key
289
+ aws_access_key = accessKey || process.env.AWS_ACCESS_KEY_ID || undefined;
290
+ aws_secret_key = secretKey || process.env.AWS_SECRET_ACCESS_KEY || undefined;
291
+ region = regionInput || randomRegion();
292
+ configFile = configInput || "config.json";
293
+
294
+ if (!aws_access_key || !aws_secret_key) {
295
+ console.log(chalk.red("[!] AWS Access Key or Secret Key not found"));
296
+ return;
297
+ }
298
+
299
+ console.log(chalk.cyan(`[i] Using region: ${region}`));
300
+
301
+ const keyMask = (key) => {
302
+ if (key.length < 6) return key;
303
+ return key.slice(0, 4) + "..." + key.slice(-4);
304
+ };
305
+ console.log(chalk.cyan(`[i] Using access key: ${keyMask(aws_access_key)}`));
306
+
307
+ // create a new API gateway
308
+ if (initInput) {
309
+ await createGateway();
310
+ } else if (destroyInput) {
311
+ await destroyGateway(destroyInput);
312
+ } else if (destroyAllInput) {
313
+ await destroyAllGateways();
314
+ } else if (listInput) {
315
+ await listGateways();
316
+ } else {
317
+ console.log(
318
+ chalk.red(
319
+ "[!] Please provide a valid action (-i/--init or -d/--destroy or --destroy-all)",
320
+ ),
321
+ );
322
+ }
323
+ };
324
+
325
+ export default apiGateway;
@@ -0,0 +1,7 @@
1
+ import chalk from "chalk";
2
+
3
+ const endpoints = () => {
4
+ console.log(chalk.green("Feature under development. Check back later :)"));
5
+ };
6
+
7
+ export default endpoints;
@@ -0,0 +1,12 @@
1
+ const githubURL = "https://github.com/shriyanss/js-recon"
2
+ const version = "1.0.0";
3
+ const toolDesc = "JS Recon Tool";
4
+
5
+ global.CONFIG = {
6
+ github: githubURL,
7
+ notFoundMessage: `If you believe this is an error or is a new technology, please create an issue on ${githubURL} and we'll figure it out for you`,
8
+ version: version,
9
+ toolDesc: toolDesc
10
+ };
11
+
12
+ export default CONFIG;
package/index.js ADDED
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+ import { program } from "commander";
3
+ import lazyLoad from "./lazyLoad/index.js";
4
+ import endpoints from "./endpoints/index.js";
5
+ import CONFIG from "./globalConfig.js";
6
+ import strings from "./strings/index.js";
7
+ import apiGateway from "./api_gateway/index.js";
8
+ import * as globals from "./utility/globals.js";
9
+
10
+ program.version(CONFIG.version).description(CONFIG.toolDesc);
11
+
12
+ program
13
+ .command("lazyload")
14
+ .description("Run lazy load module")
15
+ .requiredOption("-u, --url <url/file>", "Target URL or a file containing a list of URLs (one per line)")
16
+ .option("-o, --output <directory>", "Output directory", "output")
17
+ .option("--strict-scope", "Download JS files from only the input URL domain", false)
18
+ .option("-s, --scope <scope>", "Download JS files from specific domains (comma-separated)", "*")
19
+ .option("-t, --threads <threads>", "Number of threads to use", 1)
20
+ .option("--subsequent-requests", "Download JS files from subsequent requests", false)
21
+ .option("--urls-file <file>", "Input JSON file containing URLs", "extracted_urls.json")
22
+ .option("--api-gateway", "Generate requests using API Gateway", false)
23
+ .option("--api-gateway-config <file>", "API Gateway config file", ".api_gateway_config.json")
24
+ .action(async (cmd) => {
25
+ globals.setApiGatewayConfigFile(cmd.apiGatewayConfig);
26
+ globals.setUseApiGateway(cmd.apiGateway);
27
+ await lazyLoad(cmd.url, cmd.output, cmd.strictScope, cmd.scope.split(","), cmd.threads, cmd.subsequentRequests, cmd.urlsFile);
28
+ });
29
+
30
+ program
31
+ .command("endpoints")
32
+ .description("Extract API endpoints")
33
+ .requiredOption("-u, --url <url>", "Target URL")
34
+ .option("-o, --output <file>", "Output file")
35
+ .action((cmd) => {
36
+ endpoints(cmd.url, cmd.output);
37
+ });
38
+
39
+ program
40
+ .command("strings")
41
+ .description("Extract strings from JS files")
42
+ .requiredOption("-d, --directory <directory>", "Directory containing JS files")
43
+ .option("-o, --output <file>", "JSON file to save the strings", "strings.json")
44
+ .option("-e, --extract-urls", "Extract URLs from strings", false)
45
+ .option("--extracted-url-path <file>", "Output JSON file for extracted URLs and paths", "extracted_urls.json")
46
+ .action((cmd) => {
47
+ strings(cmd.directory, cmd.output, cmd.extractUrls, cmd.extractedUrlPath);
48
+ });
49
+
50
+ program
51
+ .command("api-gateway")
52
+ .description("Configure AWS API Gateway to rotate IP addresses")
53
+ .option("-i, --init", "Initialize the config file (create API)", false)
54
+ .option("-d, --destroy <id>", "Destroy API with the given ID")
55
+ .option("--destroy-all", "Destroy all the API created by this tool in all regions", false)
56
+ .option("-r, --region <region>", "AWS region (default: random region)")
57
+ .option("-a, --access-key <access-key>", "AWS access key (if not provided, AWS_ACCESS_KEY_ID environment variable will be used)")
58
+ .option("-s, --secret-key <secret-key>", "AWS secret key (if not provided, AWS_SECRET_ACCESS_KEY environment variable will be used)")
59
+ .option("-c, --config <config>", "Name of the config file", ".api_gateway_config.json")
60
+ .option("-l, --list", "List all the API created by this tool", false)
61
+ .option("--feasibility", "Check feasibility of API Gateway", false)
62
+ .option("--feasibility-url <url>", "URL to check feasibility of")
63
+ .action((cmd) => {
64
+ globals.setApiGatewayConfigFile(cmd.config);
65
+ globals.setUseApiGateway(true);
66
+ apiGateway(cmd.init, cmd.destroy, cmd.destroyAll, cmd.list, cmd.region, cmd.accessKey, cmd.secretKey, cmd.config, cmd.feasibility, cmd.feasibilityUrl);
67
+ });
68
+
69
+ program.parse(process.argv);
@@ -0,0 +1,122 @@
1
+ import chalk from "chalk";
2
+ import path from "path";
3
+ import fs from "fs";
4
+ import prettier from "prettier";
5
+ import makeRequest from "../utility/makeReq.js";
6
+ import { getURLDirectory } from "../utility/urlUtils.js";
7
+ import { getScope, getMaxReqQueue } from "./globals.js"; // Import scope and max_req_queue functions
8
+
9
+ /**
10
+ * Downloads a list of URLs and saves them as files in the specified output directory.
11
+ * It creates the necessary subdirectories based on the URL's host and path.
12
+ * If the URL does not end with `.js`, it is skipped.
13
+ * The function logs the progress and any errors to the console.
14
+ * @param {string[]} urls - An array of URLs to be downloaded.
15
+ * @param {string} output - The directory where the downloaded files will be saved.
16
+ * @returns {Promise<void>}
17
+ */
18
+ const downloadFiles = async (urls, output) => {
19
+ console.log(
20
+ chalk.cyan(`[i] Attempting to download ${urls.length} JS chunks`),
21
+ );
22
+ fs.mkdirSync(output, { recursive: true });
23
+
24
+ // to store ignored JS domain
25
+ let ignoredJSFiles = [];
26
+ let ignoredJSDomains = [];
27
+
28
+ let download_count = 0;
29
+ let queue = 0;
30
+
31
+ const downloadPromises = urls.map(async (url) => {
32
+ try {
33
+ if (url.match(/\.js/)) {
34
+ // get the directory of the url
35
+ const { host, directory } = getURLDirectory(url);
36
+
37
+ // check scope of file. Only if in scope, download it
38
+ if (!getScope().includes("*")) {
39
+ if (!getScope().includes(host)) {
40
+ ignoredJSFiles.push(url);
41
+ if (!ignoredJSDomains.includes(host)) {
42
+ ignoredJSDomains.push(host);
43
+ }
44
+ return;
45
+ }
46
+ }
47
+
48
+ // make the directory inside the output folder
49
+ const childDir = path.join(output, host, directory);
50
+ fs.mkdirSync(childDir, { recursive: true });
51
+
52
+ // check if queue is full. If so, then wait for random time between
53
+ // 50 to 300 ms. Then, check again, and loop the process
54
+ while (queue >= getMaxReqQueue()) {
55
+ await new Promise((resolve) =>
56
+ setTimeout(resolve, Math.random() * 250 + 50),
57
+ );
58
+ }
59
+ queue++;
60
+ const res = await makeRequest(url);
61
+ queue--;
62
+
63
+ const file = `// JS Source: ${url}\n${await res.text()}`;
64
+ let filename;
65
+ try {
66
+ filename = url
67
+ .split("/")
68
+ .pop()
69
+ .match(/[a-zA-Z0-9\.\-_]+\.js/)[0];
70
+ } catch (err) {
71
+ // split the URL into multiple chunks. then iterate
72
+ // through it, and find whatever matches with JS ext
73
+ const chunks = url.split("/");
74
+ for (const chunk of chunks) {
75
+ if (chunk.match(/\.js$/)) {
76
+ filename = chunk;
77
+ break;
78
+ }
79
+ }
80
+ }
81
+
82
+ if (!filename) { // Handle cases where filename might not be found
83
+ console.warn(chalk.yellow(`[!] Could not determine filename for URL: ${url}. Skipping.`));
84
+ return;
85
+ }
86
+
87
+ const filePath = path.join(childDir, filename);
88
+ try {
89
+ fs.writeFileSync(
90
+ filePath,
91
+ await prettier.format(file, { parser: "babel" }),
92
+ );
93
+ } catch (err) {
94
+ console.error(chalk.red(`[!] Failed to write file: ${filePath}`));
95
+ }
96
+ download_count++;
97
+ }
98
+ } catch (err) {
99
+ console.error(chalk.red(`[!] Failed to download: ${url}`));
100
+ }
101
+ });
102
+
103
+ await Promise.all(downloadPromises);
104
+
105
+ if (ignoredJSFiles.length > 0) {
106
+ console.log(
107
+ chalk.yellow(
108
+ `[i] Ignored ${ignoredJSFiles.length} JS files across ${ignoredJSDomains.length} domain(s) - ${ignoredJSDomains.join(", ")}`,
109
+ ),
110
+ );
111
+ }
112
+
113
+ if (download_count > 0) {
114
+ console.log(
115
+ chalk.green(
116
+ `[✓] Downloaded ${download_count} JS chunks to ${output} directory`,
117
+ ),
118
+ );
119
+ }
120
+ };
121
+
122
+ export default downloadFiles;
@@ -0,0 +1,54 @@
1
+ import chalk from "chalk";
2
+ import puppeteer from "puppeteer";
3
+
4
+ /**
5
+ * Downloads all the lazy loaded JS files from a given URL.
6
+ *
7
+ * It opens a headless browser instance, navigates to the given URL, and
8
+ * intercepts all the requests. It checks if the request is a JS file
9
+ * and if it is a GET request. If both conditions are satisfied, the URL
10
+ * is added to the array of URLs. Finally, it closes the browser instance
11
+ * and returns the array of URLs.
12
+ *
13
+ * @param {string} url - The URL of the webpage to fetch and parse.
14
+ * @returns {Promise<string[]|undefined>} - A promise that resolves to an array of
15
+ * absolute URLs pointing to JavaScript files found in the page, or undefined for invalid URL.
16
+ */
17
+ const downloadLoadedJs = async (url) => {
18
+ if (!url.match(/https?:\/\/[a-zA-Z0-9\._\-]+/)) {
19
+ console.log(chalk.red("[!] Invalid URL"));
20
+ return; // Return undefined as per JSDoc
21
+ }
22
+
23
+ const browser = await puppeteer.launch({
24
+ headless: true,
25
+ });
26
+
27
+ const page = await browser.newPage();
28
+
29
+ await page.setRequestInterception(true);
30
+
31
+ let js_urls_local = []; // Use a local variable, not the global one
32
+ page.on("request", async (request) => {
33
+ // get the request url
34
+ const req_url = request.url(); // Renamed to avoid conflict with outer 'url'
35
+
36
+ // see if the request is a JS file, and is a get request
37
+ if (
38
+ request.method() === "GET" &&
39
+ req_url.match(/https?:\/\/[a-z\._\-]+\/.+\.js\??.*/)
40
+ ) {
41
+ js_urls_local.push(req_url);
42
+ }
43
+
44
+ await request.continue();
45
+ });
46
+
47
+ await page.goto(url);
48
+
49
+ await browser.close();
50
+
51
+ return js_urls_local;
52
+ };
53
+
54
+ export default downloadLoadedJs;
@@ -0,0 +1,15 @@
1
+
2
+ let scope = [];
3
+ let js_urls = [];
4
+ let max_req_queue;
5
+
6
+ export const getScope = () => scope;
7
+ export const setScope = (newScope) => { scope = newScope; };
8
+ export const pushToScope = (item) => { scope.push(item); };
9
+
10
+ export const getJsUrls = () => js_urls;
11
+ export const clearJsUrls = () => { js_urls = []; };
12
+ export const pushToJsUrls = (url) => { js_urls.push(url); };
13
+
14
+ export const getMaxReqQueue = () => max_req_queue;
15
+ export const setMaxReqQueue = (newMax) => { max_req_queue = newMax; };