rav-xss 1.0.28
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.
- package/LICENSE +21 -0
- package/README.md +449 -0
- package/package.json +41 -0
- package/payloads/Basic/basic.txt +35 -0
- package/payloads/FilterEvasion/evasion.txt +20 -0
- package/payloads/Polyglots/polyglots.txt +11 -0
- package/payloads/PureReflex/reflex.txt +222 -0
- package/payloads/WAFBypass/wafbypass.txt +12 -0
- package/src/cli/args.js +57 -0
- package/src/cli/help.js +31 -0
- package/src/cli/wizard.js +196 -0
- package/src/config/colors.js +83 -0
- package/src/config/manager.js +112 -0
- package/src/core/browser.js +430 -0
- package/src/core/scanner.js +778 -0
- package/src/index.js +113 -0
- package/src/media/ravxss.png +0 -0
- package/src/utils/box.js +266 -0
- package/src/utils/helpers.js +22 -0
- package/src/utils/logger.js +44 -0
- package/src/utils/packageInfo.js +192 -0
- package/src/utils/reporter.js +56 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const packageInfo = require("../utils/packageInfo");
|
|
6
|
+
|
|
7
|
+
const BASE_DIR = path.join(__dirname, "..", "..");
|
|
8
|
+
const CONFIG_PATH = path.join(BASE_DIR, "config.json");
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 🏭 Obtém configuração padrão com valores fallback
|
|
12
|
+
* @returns {Object} Configuração padrão completa
|
|
13
|
+
*/
|
|
14
|
+
const getDefaultConfig = () => ({
|
|
15
|
+
targets: [
|
|
16
|
+
{
|
|
17
|
+
name: "Default Target",
|
|
18
|
+
url: "http://www.sudo.co.il/xss/level0.php?email=[XSS]",
|
|
19
|
+
notes: "Example target"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
scanner: {
|
|
23
|
+
user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
24
|
+
timeout_ms: 8000,
|
|
25
|
+
delay_between_requests_ms: 500,
|
|
26
|
+
report_dir: path.join(BASE_DIR, "reports")
|
|
27
|
+
},
|
|
28
|
+
output: {
|
|
29
|
+
verbose: false,
|
|
30
|
+
show_safe: false
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 📂 Carrega configuração do arquivo config.json
|
|
36
|
+
* @returns {Object} Configuração carregada ou padrão
|
|
37
|
+
*/
|
|
38
|
+
const loadConfig = () => {
|
|
39
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
40
|
+
const defaultConfig = getDefaultConfig();
|
|
41
|
+
saveConfig(defaultConfig);
|
|
42
|
+
return defaultConfig;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"));
|
|
47
|
+
const merged = { ...getDefaultConfig(), ...config };
|
|
48
|
+
|
|
49
|
+
if (!merged.targets || merged.targets.length === 0) {
|
|
50
|
+
merged.targets = getDefaultConfig().targets;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (merged.scanner?.report_dir && !path.isAbsolute(merged.scanner.report_dir)) {
|
|
54
|
+
merged.scanner.report_dir = path.join(BASE_DIR, merged.scanner.report_dir);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return merged;
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error(`Error loading config: ${err.message}`);
|
|
60
|
+
return getDefaultConfig();
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 💾 Salva configuração no arquivo config.json
|
|
66
|
+
* @param {Object} config - Configuração a ser salva
|
|
67
|
+
*/
|
|
68
|
+
const saveConfig = (config) => {
|
|
69
|
+
try {
|
|
70
|
+
const dir = path.dirname(CONFIG_PATH);
|
|
71
|
+
if (!fs.existsSync(dir)) {
|
|
72
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const configToSave = { ...config };
|
|
76
|
+
delete configToSave.version;
|
|
77
|
+
|
|
78
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(configToSave, null, 2));
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.error(`Error saving config: ${err.message}`);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* ✅ Valida a configuração e cria diretórios necessários
|
|
86
|
+
* @param {Object} config - Configuração a validar
|
|
87
|
+
* @returns {boolean} true se válida
|
|
88
|
+
*/
|
|
89
|
+
const validateConfig = (config) => {
|
|
90
|
+
if (config.targets && config.targets.length > 0) {
|
|
91
|
+
for (const target of config.targets) {
|
|
92
|
+
if (!target.url || !target.url.includes("[XSS]")) {
|
|
93
|
+
console.error(`Invalid target URL: ${target.url}`);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const reportDir = config.scanner.report_dir;
|
|
100
|
+
if (reportDir && !fs.existsSync(reportDir)) {
|
|
101
|
+
try {
|
|
102
|
+
fs.mkdirSync(reportDir, { recursive: true });
|
|
103
|
+
} catch (err) {
|
|
104
|
+
console.error(`Error creating report directory: ${err.message}`);
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return true;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
module.exports = { loadConfig, saveConfig, validateConfig, getDefaultConfig };
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const axios = require("axios");
|
|
4
|
+
const os = require("os");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const { execSync } = require("child_process");
|
|
8
|
+
|
|
9
|
+
let playwright = null;
|
|
10
|
+
try {
|
|
11
|
+
if (!isTermux()) {
|
|
12
|
+
playwright = require("playwright");
|
|
13
|
+
}
|
|
14
|
+
} catch (e) {
|
|
15
|
+
playwright = null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 🖥️ Gerenciador de Modos de Execução
|
|
20
|
+
*
|
|
21
|
+
* Gerencia requisições HTTP via Axios (Modo Normal) e navegação
|
|
22
|
+
* via Playwright (Modo Navegador) com detecção automática de ambiente.
|
|
23
|
+
*/
|
|
24
|
+
class BrowserManager {
|
|
25
|
+
constructor(config, args) {
|
|
26
|
+
this.config = config;
|
|
27
|
+
this.args = args;
|
|
28
|
+
this.mode = args.mode || config.mode || "axios";
|
|
29
|
+
|
|
30
|
+
if (isTermux()) {
|
|
31
|
+
this.mode = "axios";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (this.mode === "playwright" && !playwright) {
|
|
35
|
+
this.mode = "axios";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.browser = null;
|
|
39
|
+
this.responses = [];
|
|
40
|
+
this._browsersChecked = false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 🔧 Verifica se os navegadores do Playwright estão instalados
|
|
45
|
+
* @returns {boolean} true se os navegadores estão disponíveis
|
|
46
|
+
*/
|
|
47
|
+
arePlaywrightBrowsersInstalled() {
|
|
48
|
+
if (!playwright) return false;
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const registryPath = this.getPlaywrightBrowserPath();
|
|
52
|
+
if (!registryPath) return false;
|
|
53
|
+
|
|
54
|
+
return fs.existsSync(registryPath);
|
|
55
|
+
} catch (e) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 📁 Obtém o caminho esperado para os navegadores do Playwright
|
|
62
|
+
* @returns {string|null} Caminho do diretório de navegadores
|
|
63
|
+
*/
|
|
64
|
+
getPlaywrightBrowserPath() {
|
|
65
|
+
try {
|
|
66
|
+
const os = require("os");
|
|
67
|
+
const home = os.homedir();
|
|
68
|
+
|
|
69
|
+
if (process.platform === "win32") {
|
|
70
|
+
const localAppData = process.env.LOCALAPPDATA || path.join(home, "AppData", "Local");
|
|
71
|
+
return path.join(localAppData, "ms-playwright");
|
|
72
|
+
} else if (process.platform === "darwin") {
|
|
73
|
+
return path.join(home, "Library", "Caches", "ms-playwright");
|
|
74
|
+
} else {
|
|
75
|
+
const cacheDir = process.env.XDG_CACHE_HOME || path.join(home, ".cache");
|
|
76
|
+
return path.join(cacheDir, "ms-playwright");
|
|
77
|
+
}
|
|
78
|
+
} catch (e) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 🔧 Verifica e instala os navegadores do Playwright se necessário
|
|
85
|
+
* @returns {boolean} true se os navegadores estão prontos
|
|
86
|
+
*/
|
|
87
|
+
ensurePlaywrightBrowsers() {
|
|
88
|
+
if (this._browsersChecked) return true;
|
|
89
|
+
if (!playwright) return false;
|
|
90
|
+
|
|
91
|
+
const installed = this.arePlaywrightBrowsersInstalled();
|
|
92
|
+
|
|
93
|
+
if (installed) {
|
|
94
|
+
this._browsersChecked = true;
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
console.log("\n" + [
|
|
99
|
+
"═".repeat(55),
|
|
100
|
+
" ⚠️ Playwright browsers not found!",
|
|
101
|
+
" Installing Chromium automatically...",
|
|
102
|
+
"═".repeat(55)
|
|
103
|
+
].join("\n"));
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
execSync("npx playwright install chromium", {
|
|
107
|
+
stdio: "inherit",
|
|
108
|
+
timeout: 120000
|
|
109
|
+
});
|
|
110
|
+
console.log(" ✅ Chromium installed successfully!\n");
|
|
111
|
+
this._browsersChecked = true;
|
|
112
|
+
return true;
|
|
113
|
+
} catch (installError) {
|
|
114
|
+
console.log([
|
|
115
|
+
"",
|
|
116
|
+
" ❌ Failed to install Chromium automatically.",
|
|
117
|
+
" Please install manually with:",
|
|
118
|
+
" npx playwright install chromium",
|
|
119
|
+
""
|
|
120
|
+
].join("\n"));
|
|
121
|
+
this._browsersChecked = true;
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* 🚀 Inicializa o navegador Playwright
|
|
128
|
+
* @returns {Promise<Object>} Instância do navegador
|
|
129
|
+
*/
|
|
130
|
+
async launch() {
|
|
131
|
+
if (this.mode !== "playwright") return null;
|
|
132
|
+
|
|
133
|
+
if (!this.ensurePlaywrightBrowsers()) {
|
|
134
|
+
this.mode = "axios";
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const browserType = playwright.chromium;
|
|
139
|
+
|
|
140
|
+
this.browser = await browserType.launch({
|
|
141
|
+
headless: this.config.scanner.headless !== false,
|
|
142
|
+
args: [
|
|
143
|
+
'--no-sandbox',
|
|
144
|
+
'--disable-setuid-sandbox',
|
|
145
|
+
'--disable-dev-shm-usage'
|
|
146
|
+
]
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return this.browser;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 🔒 Fecha a instância do navegador
|
|
154
|
+
* @returns {Promise<void>}
|
|
155
|
+
*/
|
|
156
|
+
async close() {
|
|
157
|
+
if (this.browser) {
|
|
158
|
+
await this.browser.close();
|
|
159
|
+
this.browser = null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 🌐 Cria um novo contexto de navegação
|
|
165
|
+
* @returns {Promise<Object>} Contexto do navegador
|
|
166
|
+
*/
|
|
167
|
+
async createContext() {
|
|
168
|
+
if (!this.browser) return null;
|
|
169
|
+
|
|
170
|
+
return await this.browser.newContext({
|
|
171
|
+
userAgent: this.config.scanner.user_agent,
|
|
172
|
+
ignoreHTTPSErrors: true
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 📄 Cria uma nova página com interceptação de recursos
|
|
178
|
+
* @param {Object} context - Contexto do navegador
|
|
179
|
+
* @returns {Promise<Object>} Página configurada
|
|
180
|
+
*/
|
|
181
|
+
async createPage(context) {
|
|
182
|
+
if (!context) return null;
|
|
183
|
+
|
|
184
|
+
const page = await context.newPage();
|
|
185
|
+
|
|
186
|
+
await page.route("**/*.{png,jpg,jpeg,gif,woff,woff2,svg,css,ico,font}",
|
|
187
|
+
(route) => route.abort()
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
await page.route("**/*", (route, request) => {
|
|
191
|
+
this.captureResponse(request);
|
|
192
|
+
route.continue();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
page.on("response", (response) => {
|
|
196
|
+
this.capturePlaywrightResponse(response);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
return page;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* 📡 Executa requisição HTTP no modo Axios
|
|
204
|
+
* @param {string} url - URL alvo
|
|
205
|
+
* @param {Object} options - Opções da requisição
|
|
206
|
+
* @returns {Promise<Object>} Resposta da requisição
|
|
207
|
+
*/
|
|
208
|
+
async axiosRequest(url, options = {}) {
|
|
209
|
+
const config = {
|
|
210
|
+
timeout: this.config.scanner.timeout_ms || 8000,
|
|
211
|
+
headers: {
|
|
212
|
+
"User-Agent": this.config.scanner.user_agent,
|
|
213
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
214
|
+
"Accept-Language": "en-US,en;q=0.5",
|
|
215
|
+
...options.headers
|
|
216
|
+
},
|
|
217
|
+
validateStatus: () => true,
|
|
218
|
+
maxRedirects: 5,
|
|
219
|
+
...options
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
const response = await axios.get(url, config);
|
|
224
|
+
|
|
225
|
+
this.responses.push({
|
|
226
|
+
url: url,
|
|
227
|
+
status: response.status,
|
|
228
|
+
headers: response.headers,
|
|
229
|
+
body: response.data,
|
|
230
|
+
timestamp: new Date().toISOString()
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
return response;
|
|
234
|
+
} catch (error) {
|
|
235
|
+
return {
|
|
236
|
+
status: 0,
|
|
237
|
+
data: "",
|
|
238
|
+
headers: {},
|
|
239
|
+
error: error.message
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* 🎭 Executa navegação no modo Playwright
|
|
246
|
+
* @param {string} url - URL alvo
|
|
247
|
+
* @param {Object} options - Opções de navegação
|
|
248
|
+
* @returns {Promise<Object>} Resultado da navegação
|
|
249
|
+
*/
|
|
250
|
+
async playwrightRequest(url, options = {}) {
|
|
251
|
+
if (!this.browser) {
|
|
252
|
+
await this.launch();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (this.mode === "axios") {
|
|
256
|
+
return await this.axiosRequest(url, options);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const context = await this.createContext();
|
|
260
|
+
const page = await this.createPage(context);
|
|
261
|
+
|
|
262
|
+
let dialogMessage = null;
|
|
263
|
+
let dialogHandled = false;
|
|
264
|
+
|
|
265
|
+
page.on("dialog", async (dialog) => {
|
|
266
|
+
dialogMessage = dialog.message();
|
|
267
|
+
dialogHandled = true;
|
|
268
|
+
await dialog.accept();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
const response = await page.goto(url, {
|
|
273
|
+
waitUntil: "domcontentloaded",
|
|
274
|
+
timeout: this.config.scanner.timeout_ms || 8000,
|
|
275
|
+
...options
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
await page.waitForTimeout(1000);
|
|
279
|
+
|
|
280
|
+
const body = await page.content();
|
|
281
|
+
|
|
282
|
+
this.responses.push({
|
|
283
|
+
url: url,
|
|
284
|
+
status: response?.status() || 0,
|
|
285
|
+
headers: response?.headers() || {},
|
|
286
|
+
body: body,
|
|
287
|
+
timestamp: new Date().toISOString(),
|
|
288
|
+
dialogMessage: dialogMessage
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const result = {
|
|
292
|
+
status: response?.status() || 0,
|
|
293
|
+
data: body,
|
|
294
|
+
headers: response?.headers() || {},
|
|
295
|
+
dialogMessage: dialogMessage
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
if (this.config.scanner.headless === false) {
|
|
299
|
+
await page.waitForTimeout(1500);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
await context.close();
|
|
303
|
+
return result;
|
|
304
|
+
} catch (error) {
|
|
305
|
+
await context.close();
|
|
306
|
+
return {
|
|
307
|
+
status: 0,
|
|
308
|
+
data: "",
|
|
309
|
+
headers: {},
|
|
310
|
+
error: error.message
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* 🎯 Executa requisição no modo apropriado
|
|
317
|
+
* @param {string} url - URL alvo
|
|
318
|
+
* @param {Object} options - Opções da requisição
|
|
319
|
+
* @returns {Promise<Object>} Resposta da requisição
|
|
320
|
+
*/
|
|
321
|
+
async request(url, options = {}) {
|
|
322
|
+
if (this.mode === "playwright") {
|
|
323
|
+
return await this.playwrightRequest(url, options);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return await this.axiosRequest(url, options);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* 🔍 Captura detalhes da requisição Playwright
|
|
331
|
+
* @param {Object} request - Objeto de requisição Playwright
|
|
332
|
+
*/
|
|
333
|
+
captureResponse(request) {
|
|
334
|
+
if (!request) return;
|
|
335
|
+
|
|
336
|
+
if (this.args.verbose) {
|
|
337
|
+
console.log(` 📤 Request: ${request.method()} ${request.url()}`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* 📥 Captura detalhes da resposta Playwright
|
|
343
|
+
* @param {Object} response - Objeto de resposta Playwright
|
|
344
|
+
*/
|
|
345
|
+
capturePlaywrightResponse(response) {
|
|
346
|
+
if (!response) return;
|
|
347
|
+
|
|
348
|
+
if (this.args.verbose) {
|
|
349
|
+
console.log(` 📥 Response: ${response.status()} ${response.url()}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* 🧹 Limpa as respostas capturadas
|
|
355
|
+
*/
|
|
356
|
+
clearResponses() {
|
|
357
|
+
this.responses = [];
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* 📊 Retorna as respostas capturadas
|
|
362
|
+
* @returns {Array} Lista de respostas
|
|
363
|
+
*/
|
|
364
|
+
getResponses() {
|
|
365
|
+
return this.responses;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* 🔄 Alterna entre os modos de execução
|
|
370
|
+
* @param {string} mode - Modo de execução ("axios" ou "playwright")
|
|
371
|
+
* @returns {string} Modo configurado
|
|
372
|
+
*/
|
|
373
|
+
setMode(mode) {
|
|
374
|
+
if (mode === "playwright" && isTermux()) {
|
|
375
|
+
mode = "axios";
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (mode === "playwright" && !playwright) {
|
|
379
|
+
mode = "axios";
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (mode === "playwright") {
|
|
383
|
+
if (!this.ensurePlaywrightBrowsers()) {
|
|
384
|
+
mode = "axios";
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
this.mode = mode;
|
|
389
|
+
return this.mode;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* 📋 Retorna o modo atual
|
|
394
|
+
* @returns {string} Modo de execução atual
|
|
395
|
+
*/
|
|
396
|
+
getMode() {
|
|
397
|
+
return this.mode;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* ✅ Verifica se o modo Playwright está disponível
|
|
402
|
+
* @returns {boolean} true se disponível
|
|
403
|
+
*/
|
|
404
|
+
isPlaywrightAvailable() {
|
|
405
|
+
return !isTermux() && playwright !== null;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* 📱 Verifica se está executando no Termux (Android)
|
|
411
|
+
* @returns {boolean} true se estiver no Termux
|
|
412
|
+
*/
|
|
413
|
+
function isTermux() {
|
|
414
|
+
if (process.env.TERMUX_VERSION) return true;
|
|
415
|
+
if (process.env.PREFIX?.includes("com.termux")) return true;
|
|
416
|
+
|
|
417
|
+
const hostname = os.hostname();
|
|
418
|
+
if (hostname && (
|
|
419
|
+
hostname.toLowerCase().includes("termux") ||
|
|
420
|
+
hostname.toLowerCase().includes("android")
|
|
421
|
+
)) return true;
|
|
422
|
+
|
|
423
|
+
try {
|
|
424
|
+
if (fs.existsSync("/data/data/com.termux")) return true;
|
|
425
|
+
} catch (e) { }
|
|
426
|
+
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
module.exports = { BrowserManager, isTermux };
|